@@ -744,6 +749,7 @@ const { openPlanWindow } = usePlanWindow()
const settingNavItems = [
{ key: 'desktop', label: '桌面行为', hint: '启动 / 关闭 / 端口' },
+ { key: 'ai', label: 'AI 服务', hint: '模型 / 密钥 / 默认能力' },
{ key: 'voice', label: '语音转文字', hint: 'CPU / NVIDIA GPU' },
{ key: 'mcp', label: 'MCP 接入', hint: '局域网 / Skill / 工具' },
{ key: 'keys', label: '数据库与密钥', hint: '密钥查看 / 复制' },
@@ -758,6 +764,7 @@ const contentScrollRef = ref(null)
const desktopSectionRef = ref(null)
const desktopLogFileRef = ref(null)
const voiceSectionRef = ref(null)
+const aiSectionRef = ref(null)
const mcpSectionRef = ref(null)
const keysSectionRef = ref(null)
const startupSectionRef = ref(null)
@@ -1068,6 +1075,7 @@ const refreshDesktopOutputDirProgress = async () => {
const sectionElements = computed(() => [
{ key: 'desktop', el: desktopSectionRef.value },
+ { key: 'ai', el: aiSectionRef.value },
{ key: 'voice', el: voiceSectionRef.value },
{ key: 'mcp', el: mcpSectionRef.value },
{ key: 'keys', el: keysSectionRef.value },
@@ -1082,14 +1090,21 @@ const scrollToSection = (key) => {
const target = sectionElements.value.find((item) => item.key === key)?.el
activeSection.value = key
if (!scrollHost || !target) return
+ // offsetTop 相对定位祖先计算,会把设置页固定标题栏的高度重复计入。
+ const targetTop = scrollHost.scrollTop + target.getBoundingClientRect().top - scrollHost.getBoundingClientRect().top
scrollHost.scrollTo({
- top: Math.max(0, target.offsetTop - 10),
+ top: Math.max(0, targetTop - 10),
behavior: 'smooth',
})
}
const scrollToFocusTarget = async () => {
const focusTarget = String(props.focusTarget || '').trim()
+ if (focusTarget === 'ai' || focusTarget === 'local-search') {
+ await nextTick()
+ scrollToSection('ai')
+ return
+ }
if (focusTarget === 'voice') {
await nextTick()
scrollToSection('voice')
@@ -1124,10 +1139,11 @@ const onContentScroll = () => {
}
}
const position = scrollHost.scrollTop + 120
+ const hostTop = scrollHost.getBoundingClientRect().top
let current = settingNavItems[0].key
for (const section of sectionElements.value) {
if (!section.el) continue
- if (section.el.offsetTop <= position) current = section.key
+ if (scrollHost.scrollTop + section.el.getBoundingClientRect().top - hostTop <= position) current = section.key
}
activeSection.value = current
}
diff --git a/frontend/components/UiSelect.vue b/frontend/components/UiSelect.vue
new file mode 100644
index 00000000..77dce129
--- /dev/null
+++ b/frontend/components/UiSelect.vue
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/components/chat/AgentAnswer.vue b/frontend/components/chat/AgentAnswer.vue
new file mode 100644
index 00000000..599e3b1c
--- /dev/null
+++ b/frontend/components/chat/AgentAnswer.vue
@@ -0,0 +1,115 @@
+
+
+
+
+
+
{{ selected.sender }} · {{ new Date(selected.time * 1000).toLocaleString() }}
+
{{ selected.text }}
+
{{ locateError }}
+
+
+
+
+
diff --git a/frontend/components/chat/AgentEvidence.vue b/frontend/components/chat/AgentEvidence.vue
new file mode 100644
index 00000000..b03a5fad
--- /dev/null
+++ b/frontend/components/chat/AgentEvidence.vue
@@ -0,0 +1,49 @@
+
+
+ 回答依据
+ 已找到 {{ run.source_count ?? sources.length }} 条消息 · 本次回答请求包含 {{ context.sources?.length || 0 }} 条原文 · 回答标注引用 {{ cited.size }} 条
+ 本次回答使用了 {{ context.summary_segments }} 个分段的汇总,其他原文和分段发现可在详细结果中检索。
+ 来源已组装,回答调用尚未完成。
+ {{ run.answer ? '此轮未记录回答请求的来源明细,无法确认哪些原文传入了模型。' : '尚未生成回答请求。' }}
+ {{ context.omitted }} 条已读消息因上下文长度限制,未放入本次回答请求的原文资料。
+
+ 查看来源与引用情况
+ 标记表示检索和请求记录,不代表模型一定采纳了内容。历史对话或摘要可能另含相关信息。
+
+ {{ source.name || source.username }} · {{ new Date(source.time * 1000).toLocaleString() }}
+
+ {{ method(source) }}
+ {{ inputLabel(source) }}
+ 回答已引用
+ 回答未标注引用
+
+ {{ source.text }}
+
+
+
+
+
+
+
+
diff --git a/frontend/components/chat/AgentMaterials.vue b/frontend/components/chat/AgentMaterials.vue
new file mode 100644
index 00000000..5bf83d45
--- /dev/null
+++ b/frontend/components/chat/AgentMaterials.vue
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+ {{ error }}
+ 正在读取…
+
+ 已读取 {{ page.total_messages || 0 }} 条消息的精确计数{{ run.analysis?.complete ? '' : '(范围尚未读完)' }}
+ 共 {{ page.total }} 条{{ kind === 'findings' ? '分析结果' : '来源' }}
+ 当前没有结果。
+
+ {{ item.name || item.username }} · {{ date(item.time) }} · {{ item.sender }}{{ item.text }}
+ 需要核对前后文
+ {{ statisticKind === 'daily' ? item.day : statisticKind === 'sender' ? `${offset + index + 1}. ${item.sender || item.sender_id || '未知发言人'}` : `${item.day} · ${nameFor(item.username)} · ${item.sender || item.sender_id || '未知发言人'}` }}
{{ item.count }} 条消息
+
+
+
+
+
+
+
+
+
diff --git a/frontend/components/chat/AgentRun.vue b/frontend/components/chat/AgentRun.vue
new file mode 100644
index 00000000..3abd0906
--- /dev/null
+++ b/frontend/components/chat/AgentRun.vue
@@ -0,0 +1,68 @@
+
+
+ 查询范围:{{ date(run.time_range.start) }} — {{ date(run.time_range.end) }}
+ 已读取 {{ run.read_count || 0 }} 条 · 已分析 {{ run.analysis.analyzed || 0 }} 条 · {{ run.analysis.complete ? '范围处理完成' : '范围尚未处理完成' }}
{{ nameFor(c.username) }}:读取 {{ c.read }} 条,分析 {{ c.analyzed }} 条 · {{ c.complete ? '已处理完成' : '待继续' }} · {{ c.warning }}
已生成 {{ run.analysis.segments }} 个分段结果,{{ run.analysis.findings }} 条分析发现。
+ 此轮未记录完整遍历进度,范围覆盖情况未知。
+
+
+
本次用量 · {{ run.usage.calls }} 次模型调用
输入 {{ run.usage.input_tokens }} · 输出 {{ run.usage.output_tokens }} Token
{{ run.usage.unknown }} 次调用未返回完整用量,以上为已知部分。
+
+
+ {{ item.text }}{{ item.cached ? '复用已读结果 · ' : '' }}{{ item.result.returned || 0 }} 条{{ item.result.has_more ? ' · 还有更多' : '' }}{{ retrievalLabel(item) }}
+
+
+
+ {{ item.text }}
{{ item.status === 'applied' ? '补充要求已应用' : '已收到补充要求' }}
+ {{ item.text }} · 第 {{ item.attempt }} 次尝试
+ 旧答案已根据补充要求调整
+ {{ item.text }}
+ {{ item.text }} · {{ duration((item.finished_at || now / 1000) - item.started_at) }}
+
+
+ {{ run.stage }}当前步骤 {{ duration(stageElapsed) }} · 已读取 {{ run.read_count || 0 }} 条消息 · 媒体 {{ run.used?.media || 0 }}这一步仍在处理中,可以补充要求或切换聊天。
+ {{ run.error }}诊断信息
{{ run.error_info.category }} · {{ run.error_info.diagnostic_id }}
+ 回答尚未完成部分资料未读取 · 查看说明
{{ warning }}
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/components/chat/AgentSourceInspector.vue b/frontend/components/chat/AgentSourceInspector.vue
new file mode 100644
index 00000000..d5e8b72f
--- /dev/null
+++ b/frontend/components/chat/AgentSourceInspector.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
diff --git a/frontend/components/chat/AiSidebar.vue b/frontend/components/chat/AiSidebar.vue
new file mode 100644
index 00000000..9d21c413
--- /dev/null
+++ b/frontend/components/chat/AiSidebar.vue
@@ -0,0 +1,318 @@
+
+
+
+
+
+
+
diff --git a/frontend/components/chat/AiSummaryResult.vue b/frontend/components/chat/AiSummaryResult.vue
new file mode 100644
index 00000000..fd453bae
--- /dev/null
+++ b/frontend/components/chat/AiSummaryResult.vue
@@ -0,0 +1,17 @@
+
+
+
{{ summary.overview }}
+
+ {{ group.label }}
+
+
{{ point.text || point.reason }}
+
+
+
+
+
+
diff --git a/frontend/components/chat/ChatAgentPanel.vue b/frontend/components/chat/ChatAgentPanel.vue
new file mode 100644
index 00000000..3238b73f
--- /dev/null
+++ b/frontend/components/chat/ChatAgentPanel.vue
@@ -0,0 +1,290 @@
+
+
+
+
+
diff --git a/frontend/components/chat/ChatOverlays.vue b/frontend/components/chat/ChatOverlays.vue
index c25f19f4..eaade280 100644
--- a/frontend/components/chat/ChatOverlays.vue
+++ b/frontend/components/chat/ChatOverlays.vue
@@ -138,6 +138,12 @@
-
+
+
+
{{ hit.matchMethods.includes('semantic') ? (hit.matchMethods.includes('keyword') ? '关键词+语义匹配' : '语义相关(按意思找到)') : '关键词匹配' }}