diff --git a/apps/web/src/i18n/locales/en.json b/apps/web/src/i18n/locales/en.json index b113d414..b1e41b69 100644 --- a/apps/web/src/i18n/locales/en.json +++ b/apps/web/src/i18n/locales/en.json @@ -191,7 +191,18 @@ "sessionTypeChat": "Chat", "sessionFilterAll": "All", "sessionSourcePrefix": "From:", - "searchSessionPlaceholder": "Search" + "searchSessionPlaceholder": "Search", + "heartbeatTrigger": "Heartbeat Trigger", + "heartbeatTime": "Time", + "heartbeatInterval": "Interval", + "heartbeatLastAt": "Last Heartbeat", + "viewHeartbeatLogs": "View Logs", + "scheduleTrigger": "Scheduled Task Trigger", + "scheduleName": "Task Name", + "scheduleDescription": "Description", + "schedulePattern": "Cron Pattern", + "scheduleMaxCalls": "Max Calls", + "viewSchedule": "View Schedule" }, "models": { "title": "Models", diff --git a/apps/web/src/i18n/locales/zh.json b/apps/web/src/i18n/locales/zh.json index 6028293d..d7cf7af3 100644 --- a/apps/web/src/i18n/locales/zh.json +++ b/apps/web/src/i18n/locales/zh.json @@ -187,7 +187,18 @@ "sessionTypeChat": "对话", "sessionFilterAll": "全部", "sessionSourcePrefix": "来自:", - "searchSessionPlaceholder": "搜索" + "searchSessionPlaceholder": "搜索", + "heartbeatTrigger": "心跳触发", + "heartbeatTime": "触发时间", + "heartbeatInterval": "间隔", + "heartbeatLastAt": "上次心跳", + "viewHeartbeatLogs": "查看日志", + "scheduleTrigger": "定时任务触发", + "scheduleName": "任务名称", + "scheduleDescription": "描述", + "schedulePattern": "Cron 表达式", + "scheduleMaxCalls": "最大调用次数", + "viewSchedule": "查看定时任务" }, "models": { "title": "模型", diff --git a/apps/web/src/pages/home/components/chat-area.vue b/apps/web/src/pages/home/components/chat-area.vue index 12679fd5..a49cec2a 100644 --- a/apps/web/src/pages/home/components/chat-area.vue +++ b/apps/web/src/pages/home/components/chat-area.vue @@ -50,6 +50,8 @@ v-for="msg in messages" :key="msg.id" :message="msg" + :session-type="activeSession?.type" + :bot-id="currentBotId" :on-open-media="galleryOpenBySrc" /> @@ -64,8 +66,11 @@ @update:open-index="gallerySetOpenIndex" /> - -
+ {{ relativeTimestamp }} +
++ {{ relativeTimestamp }} +
++ {{ relativeTimestamp }} +
+{{ parsed.pattern }}
+
+ {{ t('chat.scheduleDescription') }}
+ {{ parsed.description }}
+ {{ t('chat.scheduleMaxCalls') }}
+ {{ parsed.maxCalls }}
+ {{ parsed.command }}
+
import { ref, computed } from 'vue'
-import { Check, LoaderCircle, GitBranch, ChevronRight, CircleCheck, CircleX } from 'lucide-vue-next'
+import { Check, LoaderCircle, GitBranch, ChevronRight, CircleCheck, CircleX, ExternalLink } from 'lucide-vue-next'
import { Badge, Collapsible, CollapsibleTrigger, CollapsibleContent } from '@memohai/ui'
+import { useRouter } from 'vue-router'
+import { useChatStore } from '@/store/chat-list'
+import { storeToRefs } from 'pinia'
import type { ToolCallBlock } from '@/store/chat-list'
interface SpawnTaskResult {
@@ -122,6 +145,10 @@ interface SpawnTaskResult {
const props = defineProps<{ block: ToolCallBlock }>()
+const router = useRouter()
+const chatStore = useChatStore()
+const { currentBotId } = storeToRefs(chatStore)
+
const resultOpen = ref(false)
const tasks = computed(() => {
@@ -146,4 +173,18 @@ const results = computed(() => {
const items = r.results
return Array.isArray(items) ? (items as SpawnTaskResult[]) : []
})
+
+const hasDetailedResults = computed(() =>
+ results.value.some(r => r.text || r.error),
+)
+
+function navigateToSession(sessionId: string) {
+ const botId = currentBotId.value
+ if (!botId || !sessionId) return
+ chatStore.selectSession(sessionId)
+ router.push({
+ name: 'chat',
+ params: { botId, sessionId },
+ })
+}
diff --git a/apps/web/src/store/chat-list.ts b/apps/web/src/store/chat-list.ts
index 01423d59..5c686263 100644
--- a/apps/web/src/store/chat-list.ts
+++ b/apps/web/src/store/chat-list.ts
@@ -121,7 +121,15 @@ export const useChatStore = defineStore('chat', () => {
sessions.value.find((s) => s.id === sessionId.value) ?? null,
)
- const activeChatReadOnly = computed(() => false)
+ const activeChatReadOnly = computed(() => {
+ const session = activeSession.value
+ if (!session) return false
+ const type = session.type ?? 'chat'
+ if (type === 'heartbeat' || type === 'schedule' || type === 'subagent') return true
+ const ct = (session.channel_type ?? '').trim().toLowerCase()
+ if (ct && ct !== 'web') return true
+ return false
+ })
watch(currentBotId, (newId) => {
if (newId) {