feat: Misskey channel adapter, agent reliability hardening & stream error resilience (#359)

This commit is contained in:
KasuganoSora
2026-04-13 17:10:50 +08:00
committed by GitHub
parent 26b01cc463
commit a40207ab6d
87 changed files with 3972 additions and 547 deletions
@@ -24,6 +24,7 @@ import {
Wechatoa,
Wecom,
Matrix,
Misskey,
} from '@memohai/icon'
const channelIcons: Record<string, Component> = {
@@ -37,6 +38,7 @@ const channelIcons: Record<string, Component> = {
wechatoa: Wechatoa,
wecom: Wecom,
matrix: Matrix,
misskey: Misskey,
dingtalk: Dingtalk,
}
+1 -1
View File
@@ -21,7 +21,7 @@ export interface ChatWebSocket {
function resolveWebSocketUrl(botId: string): string {
const baseUrl = String(client.getConfig().baseUrl || '').trim()
const path = `/bots/${encodeURIComponent(botId)}/local/ws`
const path = `/bots/${encodeURIComponent(botId)}/web/ws`
if (!baseUrl || baseUrl.startsWith('/')) {
const loc = window.location
+4
View File
@@ -144,6 +144,8 @@
},
"chat": {
"greeting": "Hi! How can I help you today?",
"emptySubagent": "No messages recorded for this subagent task",
"emptySystemSession": "No messages recorded for this system session",
"selectBot": "Select a Bot",
"selectBotHint": "Choose a bot from the sidebar to start chatting",
"thinking": "Thinking…",
@@ -1022,6 +1024,7 @@
"discord": "Discord",
"qq": "QQ",
"matrix": "Matrix",
"misskey": "Misskey",
"telegram": "Telegram",
"weixin": "WeChat",
"wechatoa": "WeChat Official Account",
@@ -1036,6 +1039,7 @@
"discord": "DC",
"qq": "QQ",
"matrix": "MX",
"misskey": "MK",
"telegram": "TG",
"weixin": "WX",
"wechatoa": "OA",
+4
View File
@@ -140,6 +140,8 @@
},
"chat": {
"greeting": "你好!有什么我可以帮你的吗?",
"emptySubagent": "子代理任务暂无记录",
"emptySystemSession": "系统会话暂无记录",
"selectBot": "选择一个 Bot",
"selectBotHint": "从侧边栏选择一个 Bot 开始对话",
"thinking": "思考中…",
@@ -1018,6 +1020,7 @@
"discord": "Discord",
"qq": "QQ",
"matrix": "Matrix",
"misskey": "Misskey",
"telegram": "Telegram",
"weixin": "微信",
"wechatoa": "微信服务号",
@@ -1032,6 +1035,7 @@
"discord": "DC",
"qq": "QQ",
"matrix": "MX",
"misskey": "MK",
"telegram": "TG",
"weixin": "WX",
"wechatoa": "OA",
@@ -212,17 +212,16 @@
/>
</div>
<!-- Timezone -->
<div class="space-y-2">
<Label>{{ $t('bots.timezone') }}</Label>
<TimezoneSelect
v-model="form.timezone"
:model-value="form.timezone || emptyTimezoneValue"
:placeholder="$t('bots.timezonePlaceholder')"
allow-empty
:placeholder="defaultTimezone"
:empty-label="timezoneEmptyLabel"
:empty-label="$t('bots.timezoneInherited')"
@update:model-value="(val: string) => form.timezone = val === emptyTimezoneValue ? '' : val"
/>
<p class="text-xs text-muted-foreground">
{{ $t('bots.timezoneInheritedHint') }}
</p>
</div>
<Separator />
@@ -237,8 +236,21 @@
/>
</div>
<!-- Reasoning -->
<!-- Timezone -->
<div class="space-y-2">
<Label>{{ $t('bots.timezone') }}</Label>
<TimezoneSelect
:model-value="form.timezone || emptyTimezoneValue"
:placeholder="$t('bots.timezonePlaceholder')"
allow-empty
:empty-label="$t('bots.timezoneInherited')"
@update:model-value="(val: string) => form.timezone = val === emptyTimezoneValue ? '' : val"
/>
</div>
<Separator />
<!-- Reasoning -->
<div class="space-y-2">
<Label>{{ $t('bots.settings.reasoningEffort') }}</Label>
<Popover v-model:open="reasoningPopoverOpen">
@@ -348,7 +360,7 @@ import { getBotsById, putBotsById, getBotsByBotIdSettings, putBotsByBotIdSetting
import type { SettingsSettings } from '@memohai/sdk'
import type { Ref } from 'vue'
import { resolveApiErrorMessage } from '@/utils/api-error'
import { useUserStore } from '@/store/user'
import { emptyTimezoneValue } from '@/utils/timezones'
const props = defineProps<{
botId: string
@@ -356,13 +368,8 @@ const props = defineProps<{
const { t } = useI18n()
const router = useRouter()
const userStore = useUserStore()
const botIdRef = computed(() => props.botId) as Ref<string>
const defaultTimezone = computed(() => userStore.userInfo.timezone || 'UTC')
const timezoneEmptyLabel = computed(() =>
`${t('bots.timezoneInherited')} (${defaultTimezone.value})`,
)
// ---- Data ----
const queryCache = useQueryCache()
@@ -639,6 +646,7 @@ watch(settings, (val) => {
form.tts_model_id = val.tts_model_id ?? ''
form.browser_context_id = val.browser_context_id ?? ''
form.language = val.language ?? ''
form.timezone = val.timezone ?? ''
form.reasoning_enabled = val.reasoning_enabled ?? false
form.reasoning_effort = val.reasoning_effort || 'medium'
}
@@ -660,6 +668,7 @@ const hasSettingsChanges = computed(() => {
|| form.tts_model_id !== (s.tts_model_id ?? '')
|| form.browser_context_id !== (s.browser_context_id ?? '')
|| form.language !== (s.language ?? '')
|| form.timezone !== (s.timezone ?? '')
|| form.reasoning_enabled !== (s.reasoning_enabled ?? false)
|| form.reasoning_effort !== (s.reasoning_effort || 'medium')
)
@@ -58,7 +58,7 @@
</FormField>
<FormField
v-slot="{ componentField }"
v-slot="{ value, handleChange }"
name="timezone"
>
<FormItem>
@@ -67,28 +67,13 @@
<span class="text-muted-foreground text-xs ml-1">({{ $t('common.optional') }})</span>
</Label>
<FormControl>
<Select
:model-value="componentField.modelValue || emptyTimezoneValue"
@update:model-value="(value) => componentField['onUpdate:modelValue'](value === emptyTimezoneValue ? '' : value)"
>
<SelectTrigger class="w-full">
<SelectValue :placeholder="$t('bots.timezonePlaceholder')" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem :value="emptyTimezoneValue">
{{ $t('bots.timezoneInherited') }}
</SelectItem>
<SelectItem
v-for="timezoneOption in timezones"
:key="timezoneOption"
:value="timezoneOption"
>
{{ timezoneOption }}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
<TimezoneSelect
:model-value="value || emptyTimezoneValue"
:placeholder="$t('bots.timezonePlaceholder')"
allow-empty
:empty-label="$t('bots.timezoneInherited')"
@update:model-value="(val) => handleChange(val === emptyTimezoneValue ? '' : val)"
/>
</FormControl>
</FormItem>
</FormField>
@@ -133,12 +118,6 @@ import {
FormItem,
Separator,
Label,
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectTrigger,
SelectValue,
Spinner,
} from '@memohai/ui'
import { Plus } from 'lucide-vue-next'
@@ -150,7 +129,8 @@ import { useMutation, useQueryCache } from '@pinia/colada'
import { postBotsMutation, getBotsQueryKey } from '@memohai/sdk/colada'
import { useI18n } from 'vue-i18n'
import { useDialogMutation } from '@/composables/useDialogMutation'
import { emptyTimezoneValue, timezones } from '@/utils/timezones'
import { emptyTimezoneValue } from '@/utils/timezones'
import TimezoneSelect from '@/components/timezone-select/index.vue'
const open = defineModel<boolean>('open', { default: false })
const { t } = useI18n()
@@ -40,7 +40,22 @@
v-if="messages.length === 0 && !loadingChats"
class="flex items-center justify-center min-h-[300px]"
>
<p class="text-muted-foreground text-xs">
<p
v-if="activeSession?.type === 'subagent'"
class="text-muted-foreground text-xs"
>
{{ $t('chat.emptySubagent') }}
</p>
<p
v-else-if="activeSession?.type === 'heartbeat' || activeSession?.type === 'schedule'"
class="text-muted-foreground text-xs"
>
{{ $t('chat.emptySystemSession') }}
</p>
<p
v-else
class="text-muted-foreground text-xs"
>
{{ $t('chat.greeting') }}
</p>
</div>
@@ -51,11 +51,13 @@
>
<LoaderCircle class="size-3 animate-spin" />
</div>
<!-- eslint-disable vue/no-v-html -->
<div
v-else
class="shiki-diff-container overflow-x-auto text-xs [&_pre]:bg-transparent! [&_pre]:p-3 [&_pre]:m-0 [&_code]:text-xs"
v-html="shiki.html.value"
/>
<!-- eslint-enable vue/no-v-html -->
</CollapsibleContent>
</Collapsible>
</div>
@@ -51,11 +51,13 @@
>
<LoaderCircle class="size-3 animate-spin" />
</div>
<!-- eslint-disable vue/no-v-html -->
<div
v-else
class="shiki-container overflow-x-auto text-xs [&_pre]:bg-transparent! [&_pre]:p-3 [&_pre]:m-0 [&_code]:text-xs"
v-html="shiki.html.value"
/>
<!-- eslint-enable vue/no-v-html -->
</CollapsibleContent>
</Collapsible>
</div>
+1 -1
View File
@@ -571,7 +571,7 @@ export const useChatStore = defineStore('chat', () => {
} else {
const activeSessionId = sessionId.value && visible.some(session => session.id === sessionId.value)
? sessionId.value
: visible[0]!.id
: (visible.find((s) => s.type === 'chat' || s.type === 'discuss')?.id ?? visible[0]!.id)
sessionId.value = activeSessionId
await loadMessages(bid, activeSessionId)
}