diff --git a/AiServer.ServiceInterface/GenerationServices.cs b/AiServer.ServiceInterface/GenerationServices.cs index f2548b8..0e13e97 100644 --- a/AiServer.ServiceInterface/GenerationServices.cs +++ b/AiServer.ServiceInterface/GenerationServices.cs @@ -245,7 +245,7 @@ public static async Task ProcessGeneration(this CreateGeneration diffReq // Handle failed jobs if (queuedJob.Failed != null) - throw new Exception($"Job failed: {queuedJob.Failed.Error}"); + throw new Exception($"Job failed: {queuedJob.Failed.Error?.Message}"); // Handle cancelled jobs if (queuedJob.Job?.State == BackgroundJobState.Cancelled) diff --git a/AiServer/wwwroot/mjs/components/TextToSpeech.mjs b/AiServer/wwwroot/mjs/components/TextToSpeech.mjs index 483049a..f426a5e 100644 --- a/AiServer/wwwroot/mjs/components/TextToSpeech.mjs +++ b/AiServer/wwwroot/mjs/components/TextToSpeech.mjs @@ -1,8 +1,295 @@ +import { ref, computed, onMounted, inject, watch, nextTick } from "vue" +import { useClient, useFiles } from "@servicestack/vue" +import { createErrorStatus } from "@servicestack/client" +import { TextToSpeech } from "dtos" +import { UiLayout, ThreadStorage, HistoryTitle, HistoryGroups, useUiLayout, icons, toArtifacts, acceptedImages } from "../utils.mjs" +import { ArtifactGallery } from "./Artifacts.mjs" +import FileUpload from "./FileUpload.mjs" + export default { + components: { + UiLayout, + HistoryTitle, + HistoryGroups, + ArtifactGallery, + FileUpload, + }, template: ` -

Text to Speech

+ + + + + `, setup() { - return {} + const client = useClient() + const routes = inject('routes') + const refUi = ref() + const refForm = ref() + const refImage = ref() + const ui = useUiLayout(refUi) + const renderKey = ref(0) + + const storage = new ThreadStorage(`txt2spch`, { + text: '', + tag: '', + seed: '', + }) + const error = ref() + + const prefs = ref(storage.getPrefs()) + const history = ref(storage.getHistory()) + const thread = ref() + const threadRef = ref() + + const validPrompt = () => !!request.value.text + const refMessage = ref() + const visibleFields = 'text'.split(',') + const request = ref(new TextToSpeech()) + const activeModels = ref([]) + + function savePrefs() { + storage.savePrefs(Object.assign({}, request.value, { tag:'' })) + } + function loadHistory() { + history.value = storage.getHistory() + } + function saveHistory() { + storage.saveHistory(history.value) + } + function saveThread() { + if (thread.value) { + storage.saveThread(thread.value) + } + } + + async function send() { + console.log('send', validPrompt(), client.loading.value) + if (!validPrompt() || client.loading.value) return + + savePrefs() + console.debug(`${storage.prefix}.request`, Object.assign({}, request.value)) + + error.value = null + + const api = await client.api(request.value, { jsconfig: 'eccn' }) + /** @type {GenerationResponse} */ + const r = api.response + if (r) { + console.debug(`${storage.prefix}.response`, r) + + if (!r.outputs?.length) { + error.value = createErrorStatus("no results were returned") + } else { + const id = parseInt(routes.id) || storage.createId() + thread.value = thread.value ?? storage.createThread(Object.assign({ + id: storage.getThreadId(id), + title: request.text, + }, request.value)) + + const result = { + id: storage.createId(), + request: Object.assign({}, request.value), + response: r, + } + thread.value.results.push(result) + saveThread() + + if (!history.value.find(x => x.id === id)) { + history.value.push({ + id, + title: thread.value.title, + icon: r.outputs[0].url + }) + } + saveHistory() + + if (routes.id !== id) { + routes.to({ id }) + } + } + + } else { + console.error('send.error', api.error) + error.value = api.error + } + } + + function getThreadResults() { + const ret = Array.from(thread.value?.results ?? []) + ret.reverse() + return ret + } + + function selectRequest(req) { + Object.assign(request.value, req) + ui.scrollTop() + } + + function discardResult(result) { + thread.value.results = thread.value.results.filter(x => x.id != result.id) + saveThread() + } + + function toggleIcon(item) { + threadRef.value.icon = item.url + saveHistory() + } + + function onRouteChange() { + console.log('onRouteChange', routes.id) + refImage.value?.clear() + loadHistory() + if (routes.id) { + const id = parseInt(routes.id) + thread.value = storage.getThread(storage.getThreadId(id)) + threadRef.value = history.value.find(x => x.id === parseInt(routes.id)) + console.debug('thread', thread.value, threadRef.value) + if (thread.value) { + Object.keys(storage.defaults).forEach(k => + request.value[k] = thread.value[k] ?? storage.defaults[k]) + } + } else { + thread.value = null + Object.keys(storage.defaults).forEach(k => request.value[k] = storage.defaults[k]) + } + } + + function updated() { + onRouteChange() + } + + function saveHistoryItem(item) { + storage.saveHistory(history.value) + if (thread.value && item.title) { + thread.value.title = item.title + saveThread() + } + } + + function removeHistoryItem(item) { + const idx = history.value.findIndex(x => x.id === item.id) + if (idx >= 0) { + history.value.splice(idx, 1) + storage.saveHistory(history.value) + storage.deleteThread(storage.getThreadId(item.id)) + if (routes.id == item.id) { + routes.to({ id:undefined }) + } + } + } + + + watch(() => routes.id, updated) + watch(() => [ + request.value.seed, + request.value.tag, + ], () => { + if (!thread.value) return + Object.keys(storage.defaults).forEach(k => + thread.value[k] = request.value[k] ?? storage.defaults[k]) + saveThread() + }) + + onMounted(async () => { + updated() + }) + + return { + refForm, + refImage, + storage, + routes, + client, + history, + request, + visibleFields, + validPrompt, + refMessage, + activeModels, + thread, + threadRef, + icons, + send, + saveHistory, + saveThread, + toggleIcon, + selectRequest, + discardResult, + getThreadResults, + saveHistoryItem, + removeHistoryItem, + toArtifacts, + acceptedImages, + renderKey, + } } }