Skip to content

Commit

Permalink
feat(ui): added core system error message
Browse files Browse the repository at this point in the history
Signed-off-by: Neko Ayaka <neko@ayaka.moe>
  • Loading branch information
nekomeowww committed Mar 3, 2025
1 parent 0528239 commit f52b8d8
Show file tree
Hide file tree
Showing 12 changed files with 285 additions and 122 deletions.
1 change: 1 addition & 0 deletions apps/stage-tamagotchi/src/renderer/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ stage:
character-name:
airi: Airi
you: You
core-system: Core System
tabs:
chat: Chat
clothes: Clothes
Expand Down
15 changes: 15 additions & 0 deletions apps/stage-tamagotchi/src/renderer/locales/zh-CN.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,18 @@ stage:
select-a-model: 选择一个模型
select-a-voice: 选择一个声线
waiting: 等待中
chat:
message:
character-name:
airi: Airi
you:
core-system: 核心系统
tabs:
chat: 聊天
clothes: 换装
custom: 自定义
operations:
load-models: 加载推理模型
load-models-status:
loading: 加载中
ready: 已就绪
50 changes: 35 additions & 15 deletions apps/stage-tamagotchi/src/renderer/src/components/ChatHistory.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { nextTick, ref } from 'vue'
const chatHistoryRef = ref<HTMLDivElement>()
const { messages } = storeToRefs(useChatStore())
const { messages, sending } = storeToRefs(useChatStore())
const bounding = useElementBounding(chatHistoryRef, { immediate: true, windowScroll: true, windowResize: true })
const { y: chatHistoryContainerY } = useScroll(chatHistoryRef)
Expand All @@ -34,38 +34,58 @@ onTokenLiteral(async () => {
<template>
<div py="1" flex="~ col" rounded="lg" overflow-hidden>
<div flex-1 /> <!-- spacer -->
<div ref="chatHistoryRef" v-auto-animate max-h="30vh" flex="~ col" scrollbar="~ w-2 track-color-transparent thumb-color-pink-300 rounded" h-full w-full overflow-scroll>
<div
ref="chatHistoryRef" v-auto-animate max-h="30vh" flex="~ col"
scrollbar="~ w-2 track-color-transparent thumb-color-pink-300 rounded" h-full w-full overflow-scroll
>
<div flex-1 /> <!-- spacer -->
<div v-for="(message, index) in messages" :key="index" mb-2>
<div v-if="message.role === 'error'" flex mr="12">
<div
flex="~ col" border="4 solid violet-200/50 dark:violet-500/50" shadow="md violet-200/50 dark:none"
min-w-20 rounded-lg px-2 py-1 h="unset <sm:fit" bg="<md:violet-500/25"
>
<div flex="~ row" gap-2>
<div flex-1>
<span text-xs text="violet-400/90 dark:violet-600/90" font-semibold class="inline <sm:hidden">{{
$t('stage.chat.message.character-name.core-system') }}</span>
</div>
<div i-solar:danger-triangle-bold-duotone text-violet-500 />
</div>
<div v-if="sending" i-eos-icons:three-dots-loading />
<div
v-else class="markdown-content text-violet-500" text="base <sm:xs"
v-html="process(message.content as string)"
/>
</div>
</div>
<div v-if="message.role === 'assistant'" flex mr="12">
<div
flex="~ col"
border="4 solid pink-200"
shadow="md pink-200/50"
min-w-20 rounded-lg px-2 py-1
h="fit"
flex="~ col" border="4 solid pink-200" shadow="md pink-200/50" min-w-20 rounded-lg px-2 py-1 h="fit"
bg="pink-100"
>
<div>
<span text-xs text="pink-400/90" font-semibold class="inline hidden">Airi</span>
</div>
<div v-if="message.content" class="markdown-content" text="xs pink-400" v-html="process(message.content as string)" />
<div
v-if="message.content" class="markdown-content" text="xs pink-400"
v-html="process(message.content as string)"
/>
<div v-else i-eos-icons:three-dots-loading />
</div>
</div>
<div v-else-if="message.role === 'user'" flex="~">
<div
flex="~ col"
border="4 solid cyan-200"
shadow="md cyan-200/50"
px="2"
h="fit" min-w-20 rounded-lg px-2 py-1
bg="cyan-100"
flex="~ col" border="4 solid cyan-200" shadow="md cyan-200/50" px="2" h="fit" min-w-20 rounded-lg px-2
py-1 bg="cyan-100"
>
<div>
<span text-xs text="cyan-600/90" font-semibold class="hidden">You</span>
</div>
<div v-if="message.content" class="markdown-content" text="xs cyan-600" v-html="process(message.content as string)" />
<div
v-if="message.content" class="markdown-content" text="xs cyan-600"
v-html="process(message.content as string)"
/>
<div v-else />
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,24 @@ const listening = ref(false)
// const { selectedAudioDevice, isAudioInputOn, selectedAudioDeviceId } = storeToRefs(useSettings())
const { isAudioInputOn, selectedAudioDeviceId } = storeToRefs(useSettings())
const { send, onAfterSend } = useChatStore()
const { messages } = storeToRefs(useChatStore())
const { t } = useI18n()
async function handleSend() {
if (!messageInput.value.trim()) {
return
}
await send(messageInput.value)
try {
await send(messageInput.value)
}
catch (error) {
messages.value.pop()
messages.value.push({
role: 'error',
content: (error as Error).message,
})
}
}
const { destroy, start } = useMicVAD(selectedAudioDeviceId, {
Expand Down
19 changes: 10 additions & 9 deletions apps/stage-web/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,21 @@ settings:
chinese: 简体中文
english: English
title: Language
microphone: Microphone
model-provider:
title: Model Providers
live2d:
title: Live2D Settings
change-model:
title: Change Model
from-url: Load from URL
from-url-placeholder: Enter Live2D model URL
from-url-confirm: Load
from-file: Load from File
from-file-select: Select
from-url: Load from URL
from-url-confirm: Load
from-url-placeholder: Enter Live2D model URL
title: Change Model
map-motions:
title: Map Motions
play: Play Motion
title: Map Motions
title: Live2D Settings
microphone: Microphone
model-provider:
title: Model Providers
models: Model
openai-api-key:
label: OpenAI API Key
Expand All @@ -83,6 +83,7 @@ stage:
message:
character-name:
airi: Airi
core-system: Core System
you: You
tabs:
chat: Chat
Expand Down
30 changes: 22 additions & 8 deletions apps/stage-web/locales/zh-CN.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,21 @@ settings:
chinese: 简体中文
english: English
title: 语言
model-provider:
title: 模型提供商
live2d:
title: Live2D 设置
change-model:
title: 更换模型
from-url: 从 URL 加载
from-url-placeholder: 输入 Live2D 模型 URL
from-url-confirm: 加载
from-file: 从文件加载
from-file-select: 选择
from-url: 从 URL 加载
from-url-confirm: 加载
from-url-placeholder: 输入 Live2D 模型 URL
title: 更换模型
map-motions:
title: 映射动作
play: 播放动作
title: 映射动作
title: Live2D 设置
microphone: 麦克风
model-provider:
title: 模型提供商
models: 模型
openai-api-key:
label: OpenAI API 密钥
Expand All @@ -70,9 +71,22 @@ stage:
message:
character-name:
airi: Airi
core-system: 核心系统
you:
tabs:
chat: 聊天
clothes: 换装
custom: 自定义
message: 消息
operations:
load-models: 加载推理模型
load-models-status:
loading: 加载中
ready: 已就绪
select-a-audio-input: 选择一个音频输入设备
select-a-model: 选择一个模型
select-a-voice: 选择一个声线
viewers:
debug-menu:
emotions: 表情
waiting: 等待中
20 changes: 19 additions & 1 deletion apps/stage-web/src/components/Layouts/InteractiveArea.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,35 @@ const showMicrophoneSelect = ref(false)
const { audioInputs } = useDevicesList({ constraints: { audio: true }, requestPermissions: true })
const { selectedAudioDevice, isAudioInputOn, selectedAudioDeviceId } = storeToRefs(useSettings())
const { send, onAfterSend } = useChatStore()
const { messages } = storeToRefs(useChatStore())
const { audioContext } = useAudioContext()
const { t } = useI18n()
const { transcribe: generate, load: loadWhisper, status: whisperStatus, terminate } = useWhisper(WhisperWorker, {
onComplete: async (res) => {
if (!res || !res.trim()) {
return
}
await send(res)
},
})
async function handleSend() {
await send(messageInput.value)
if (!messageInput.value.trim()) {
return
}
try {
await send(messageInput.value)
}
catch (error) {
messages.value.pop()
messages.value.push({
role: 'error',
content: (error as Error).message,
})
}
}
const { destroy, start } = useMicVAD(selectedAudioDeviceId, {
Expand Down
55 changes: 33 additions & 22 deletions apps/stage-web/src/components/Widgets/ChatHistory.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { nextTick, ref } from 'vue'
const chatHistoryRef = ref<HTMLDivElement>()
const { messages } = storeToRefs(useChatStore())
const { messages, sending } = storeToRefs(useChatStore())
const bounding = useElementBounding(chatHistoryRef, { immediate: true, windowScroll: true, windowResize: true })
const { y: chatHistoryContainerY } = useScroll(chatHistoryRef)
Expand All @@ -32,43 +32,54 @@ onTokenLiteral(async () => {
</script>

<template>
<div
relative px="<sm:2" py="<sm:2" flex="~ col" rounded="lg" overflow-hidden
>
<div relative px="<sm:2" py="<sm:2" flex="~ col" rounded="lg" overflow-hidden>
<div flex-1 /> <!-- spacer -->
<div ref="chatHistoryRef" v-auto-animate h-full w-full flex="~ col" overflow-scroll>
<div flex-1 /> <!-- spacer -->
<div v-for="(message, index) in messages" :key="index" mb-2>
<div v-if="message.role === 'error'" flex mr="12">
<div
flex="~ col" border="4 solid violet-200/50 dark:violet-500/50" shadow="md violet-200/50 dark:none"
min-w-20 rounded-lg px-2 py-1 h="unset <sm:fit" bg="<md:violet-500/25"
>
<div flex="~ row" gap-2>
<div flex-1>
<span text-xs text="violet-400/90 dark:violet-600/90" font-semibold class="inline <sm:hidden">{{
$t('stage.chat.message.character-name.core-system') }}</span>
</div>
<div i-solar:danger-triangle-bold-duotone text-violet-500 />
</div>
<div v-if="sending" i-eos-icons:three-dots-loading />
<div
v-else class="markdown-content text-violet-500" text="base <sm:xs"
v-html="process(message.content as string)"
/>
</div>
</div>
<div v-if="message.role === 'assistant'" flex mr="12">
<div
flex="~ col"
border="4 solid pink-200/50 dark:pink-500/50"
shadow="md pink-200/50 dark:none"
min-w-20 rounded-lg px-2 py-1
h="unset <sm:fit"
bg="<md:pink-500/25"
flex="~ col" border="4 solid pink-200/50 dark:pink-500/50" shadow="md pink-200/50 dark:none" min-w-20
rounded-lg px-2 py-1 h="unset <sm:fit" bg="<md:pink-500/25"
>
<div>
<span text-xs text="pink-400/90 dark:pink-600/90" font-semibold class="inline <sm:hidden">{{ $t('stage.chat.message.character-name.airi') }}</span>
<span text-xs text="pink-400/90 dark:pink-600/90" font-semibold class="inline <sm:hidden">{{
$t('stage.chat.message.character-name.airi') }}</span>
</div>
<div v-if="message.content" class="markdown-content" text="base <sm:xs" v-html="process(message.content as string)" />
<div v-else i-eos-icons:three-dots-loading />
<div v-if="sending" i-eos-icons:three-dots-loading />
<div v-else class="markdown-content" text="base <sm:xs" v-html="process(message.content as string)" />
</div>
</div>
<div v-else-if="message.role === 'user'" flex="~ row-reverse" ml="12">
<div
flex="~ col"
border="4 solid cyan-200/50 dark:cyan-500/50"
shadow="md cyan-200/50 dark:none"
px="2"
h="unset <sm:fit" min-w-20 rounded-lg px-2 py-1
bg="<md:cyan-500/25"
flex="~ col" border="4 solid cyan-200/50 dark:cyan-500/50" shadow="md cyan-200/50 dark:none" px="2"
h="unset <sm:fit" min-w-20 rounded-lg px-2 py-1 bg="<md:cyan-500/25"
>
<div>
<span text-xs text="cyan-400/90 dark:cyan-600/90" font-semibold class="inline <sm:hidden">{{ $t('stage.chat.message.character-name.you') }}</span>
<span text-xs text="cyan-400/90 dark:cyan-600/90" font-semibold class="inline <sm:hidden">{{
$t('stage.chat.message.character-name.you') }}</span>
</div>
<div v-if="message.content" class="markdown-content" text="base <sm:xs" v-html="process(message.content as string)" />
<div v-else />
<div v-if="sending" i-eos-icons:three-dots-loading />
<div v-else class="markdown-content" text="base <sm:xs" v-html="process(message.content as string)" />
</div>
</div>
</div>
Expand Down
Loading

0 comments on commit f52b8d8

Please sign in to comment.