feat: enrich page ai ACP runtime UI
This commit is contained in:
@@ -724,6 +724,38 @@ node /home/lix/.codex/skills/page-ai-browser-verify/scripts/verify_mnote_page_ai
|
|||||||
| **边界** | Skills 展示只是可见信息;当前还不能据此认为这些 skills 都已经注入 Reasonix ACP runtime 的工具系统 |
|
| **边界** | Skills 展示只是可见信息;当前还不能据此认为这些 skills 都已经注入 Reasonix ACP runtime 的工具系统 |
|
||||||
| **下一步** | 后续若要把 Reasonix skills 变成可执行能力,需要明确 Reasonix skill schema → ToolRegistry 注册规则;当前优先保持只读展示与不误导 |
|
| **下一步** | 后续若要把 Reasonix skills 变成可执行能力,需要明确 Reasonix skill schema → ToolRegistry 注册规则;当前优先保持只读展示与不误导 |
|
||||||
|
|
||||||
|
#### 问题 4:ACP Hermes 未复用 soul/user/memory 可编辑 UI
|
||||||
|
|
||||||
|
| 项 | 详情 |
|
||||||
|
|---|---|
|
||||||
|
| **用户反馈** | ACP Hermes 页面中看不到 Hermes HTTP 已有的 `SOUL.md` / `USER.md` / `MEMORY.md` 编辑卡片 |
|
||||||
|
| **根因** | `layout.rs` 在 `isAcp` 分支把 Agent 面板替换成 runtime info 卡片,并且 `if (agentPanel && !isAcp)` 才渲染 memory 编辑器;因此 ACP Hermes 被误归入 Reasonix 风格的 runtime 信息态 |
|
||||||
|
| **参考结论** | `hermes-vscode-main` 本身没有 soul/user/memory 模块;这部分应复用 mnote 现有 Hermes HTTP BFF:`/api/hermes/client/profile-memory` |
|
||||||
|
| **已修复** | ACP Hermes (`pageAiAcpRuntime === "hermes"`) 继续显示与 Hermes HTTP 相同的三张可编辑 memory 卡片;ACP Reasonix 仍显示 runtime info;Hermes profile 下拉继续保留 |
|
||||||
|
| **边界** | 未额外把 memory 文本硬塞入 ACP prompt,避免和 `hermes -p <profile> acp` 自身 profile 加载逻辑重复;当前需求以 UI 可见、可编辑、可保存为准 |
|
||||||
|
|
||||||
|
#### 问题 5:页面 AI 会话历史刷新丢失
|
||||||
|
|
||||||
|
| 项 | 详情 |
|
||||||
|
|---|---|
|
||||||
|
| **用户反馈** | 当前会话历史刷新后没有了,不清楚真源在哪里 |
|
||||||
|
| **根因** | 前端运行期真源是 `pageUiState.pageAiSessions/pageAiMessages`;旧 `localStorage` 只保存 `activeSessionId/activeProfileName`,不保存 `messages`;ACP run 也未落 Hermes HTTP session export |
|
||||||
|
| **参考结论** | `hermes-vscode-main/src/sessionStore.ts` 把 `ChatSession[]`、`messages`、`acpSessionId` 存在 VSCode `workspaceState`,最多 20 个 session、每 session 300 条消息 |
|
||||||
|
| **已修复** | 短期真源明确为“按文档隔离的浏览器 localStorage”:`hermes_page_ai_session:<documentId>` 现在保存 `version/activeSessionId/activeProfileName/activeAcpRuntime/sessions/messages`,每 session 最多 300 条消息 |
|
||||||
|
| **验证方法** | 浏览器验证 skill 已增加 Hermes 回复后 reload,再打开页面 AI,确认 marker 回复仍可见 |
|
||||||
|
| **长期方案** | 后续应把页面 AI 会话统一落后端 session store,并让 ACP session id 支持 `session/load` 恢复;localStorage 只保留本地缓存与离线恢复 |
|
||||||
|
|
||||||
|
#### 问题 6:ACP 工具调用信息和流式输出不完整
|
||||||
|
|
||||||
|
| 项 | 详情 |
|
||||||
|
|---|---|
|
||||||
|
| **用户反馈** | 与 `hermes-vscode-main` 相比,缺工具调用信息(需可折叠、默认折叠),缺流式输出 |
|
||||||
|
| **根因** | ACP `message.delta` 已到前端,但旧 UI 只累加到局部 `assistantText`,stream 完成后才 push assistant message;工具事件虽然会生成 tool message,但卡片是普通 div,未折叠,且 ACP bridge 丢掉了 `rawInput/content/status` |
|
||||||
|
| **参考结论** | `hermes-vscode-main` 的关键是协议层 text dedup、UI 层 `pendingText/currentAgentText` 流式占位、`data-tool-id` 原地更新工具状态;参考实现本身不是折叠 UI,因此 mnote 在其基础上新增 `<details>` |
|
||||||
|
| **已修复** | 前端收到首个 `message.delta` 时创建 `streaming=true` assistant 消息并持续更新;完成后标记为普通 assistant 消息并持久化;工具卡改为 `<details>`,默认折叠,summary 显示工具名/状态/kind/call id,展开显示参数/结果/trace |
|
||||||
|
| **后端补齐** | `AcpSessionEvent::ToolCall` 保留 `raw_input`,`ToolCallUpdate` 保留 `content`;`acp_bridge` SSE 输出 `status/input/output`,让前端可展示工具参数和结果摘要 |
|
||||||
|
| **验证** | `cargo test -p mnote-web acp -- --nocapture` 已覆盖 `acp_tool_events_keep_detail_for_collapsible_ui`;浏览器验证 skill 会记录 `sawStreaming` 并保留截图 |
|
||||||
|
|
||||||
### 📋 待完成
|
### 📋 待完成
|
||||||
|
|
||||||
| Step | 工作 | 前置 | 估算 |
|
| Step | 工作 | 前置 | 估算 |
|
||||||
@@ -816,6 +848,9 @@ node /home/lix/.codex/skills/page-ai-browser-verify/scripts/verify_mnote_page_ai
|
|||||||
> - Agent 标签页新增 `<select data-page-ai-acp-runtime>` 下拉框(3 选项:默认/ACP·Hermes/ACP·Reasonix)
|
> - Agent 标签页新增 `<select data-page-ai-acp-runtime>` 下拉框(3 选项:默认/ACP·Hermes/ACP·Reasonix)
|
||||||
> - 状态:`pageAiAcpRuntime` + `pageAiAcpRuntimes`(从 `/api/hermes/client/profiles` 的 `acpRuntimes` 字段加载)
|
> - 状态:`pageAiAcpRuntime` + `pageAiAcpRuntimes`(从 `/api/hermes/client/profiles` 的 `acpRuntimes` 字段加载)
|
||||||
> - `acpRuntime` 与 `profile` 分离:ACP Hermes 保留 Hermes profile 下拉,ACP Reasonix 隐藏 Hermes profile 下拉
|
> - `acpRuntime` 与 `profile` 分离:ACP Hermes 保留 Hermes profile 下拉,ACP Reasonix 隐藏 Hermes profile 下拉
|
||||||
|
> - ACP Hermes 复用 Hermes HTTP 的 `SOUL.md` / `USER.md` / `MEMORY.md` 可编辑 UI;ACP Reasonix 保留 runtime info 卡片
|
||||||
|
> - 页面 AI 会话历史短期真源为按文档隔离的 localStorage,保存 session 列表、messages、active profile、active ACP runtime
|
||||||
|
> - `message.delta` 进入正在输出的 assistant bubble;工具调用卡片使用 `<details>` 默认折叠展示参数/结果/trace
|
||||||
> - 标题栏更新为 `ACP · Reasonix` 或 `ACP · Hermes`
|
> - 标题栏更新为 `ACP · Reasonix` 或 `ACP · Hermes`
|
||||||
> - `create_run` 同时发送 `profile` 与 `acpRuntime`;后端用 `acpRuntime` 判断是否走 ACP,用 `profile` 选择 Hermes profile
|
> - `create_run` 同时发送 `profile` 与 `acpRuntime`;后端用 `acpRuntime` 判断是否走 ACP,用 `profile` 选择 Hermes profile
|
||||||
>
|
>
|
||||||
@@ -853,11 +888,18 @@ node /home/lix/.codex/skills/page-ai-browser-verify/scripts/verify_mnote_page_ai
|
|||||||
> - Wrapper `session/prompt` → 返回 `stopReason=end_turn`,产生 `agent_message_chunk`
|
> - Wrapper `session/prompt` → 返回 `stopReason=end_turn`,产生 `agent_message_chunk`
|
||||||
> - 浏览器验证 skill:创建临时页面,切换 `ACP · Hermes` + `mnoteai` profile,发送 marker prompt,最终 assistant 正文严格等于 `ACP_HERMES_BROWSER_OK_mp9pfqf6`
|
> - 浏览器验证 skill:创建临时页面,切换 `ACP · Hermes` + `mnoteai` profile,发送 marker prompt,最终 assistant 正文严格等于 `ACP_HERMES_BROWSER_OK_mp9pfqf6`
|
||||||
> - 浏览器验证 skill:切换 `ACP · Reasonix`,发送 marker prompt,最终 assistant 正文严格等于 `ACP_REASONIX_BROWSER_OK_mp9pfqf6`
|
> - 浏览器验证 skill:切换 `ACP · Reasonix`,发送 marker prompt,最终 assistant 正文严格等于 `ACP_REASONIX_BROWSER_OK_mp9pfqf6`
|
||||||
|
> - 2026-05-17 追加:浏览器验证 skill 已扩展,检查 ACP Hermes memory 编辑器、Hermes 回复 reload 后历史恢复,并记录流式占位证据 `sawStreaming`
|
||||||
|
> - 2026-05-17 追加验证通过:ACP Hermes / ACP Reasonix 回复均严格等于 marker;两者 `sawStreaming=true`;Hermes 回复刷新后仍可见
|
||||||
>
|
>
|
||||||
> 证据:
|
> 证据:
|
||||||
> - `/mnt/Data1T/mnote/tmp/page-ai-acp-browser-UAYwyM/result.json`
|
> - `/mnt/Data1T/mnote/tmp/page-ai-acp-browser-UAYwyM/result.json`
|
||||||
> - `/mnt/Data1T/mnote/tmp/page-ai-acp-browser-UAYwyM/01-acp-hermes-reply.png`
|
> - `/mnt/Data1T/mnote/tmp/page-ai-acp-browser-UAYwyM/01-acp-hermes-reply.png`
|
||||||
> - `/mnt/Data1T/mnote/tmp/page-ai-acp-browser-UAYwyM/02-acp-reasonix-reply.png`
|
> - `/mnt/Data1T/mnote/tmp/page-ai-acp-browser-UAYwyM/02-acp-reasonix-reply.png`
|
||||||
|
> - `/mnt/Data1T/mnote/tmp/page-ai-acp-browser-wcJzrj/result.json`
|
||||||
|
> - `/mnt/Data1T/mnote/tmp/page-ai-acp-browser-wcJzrj/00-acp-hermes-memory-ui.png`
|
||||||
|
> - `/mnt/Data1T/mnote/tmp/page-ai-acp-browser-wcJzrj/01-acp-hermes-reply.png`
|
||||||
|
> - `/mnt/Data1T/mnote/tmp/page-ai-acp-browser-wcJzrj/01b-acp-hermes-history-after-reload.png`
|
||||||
|
> - `/mnt/Data1T/mnote/tmp/page-ai-acp-browser-wcJzrj/02-acp-reasonix-reply.png`
|
||||||
>
|
>
|
||||||
> 仍未纳入本阶段:`thought.delta` 可视化渲染、`usage.updated` 展示。当前页面不显示 thought 是可接受行为;关键是 thought 不能误进 `message.delta`。
|
> 仍未纳入本阶段:`thought.delta` 可视化渲染、`usage.updated` 展示。当前页面不显示 thought 是可接受行为;关键是 thought 不能误进 `message.delta`。
|
||||||
|
|
||||||
|
|||||||
@@ -194,25 +194,38 @@ pub fn acp_event_to_sse(event: AcpSessionEvent) -> Option<SseEvent> {
|
|||||||
tool_call_id,
|
tool_call_id,
|
||||||
title,
|
title,
|
||||||
kind,
|
kind,
|
||||||
..
|
status,
|
||||||
|
raw_input,
|
||||||
} => Some(SseEvent {
|
} => Some(SseEvent {
|
||||||
event: "tool.started".into(),
|
event: "tool.started".into(),
|
||||||
data: json!({
|
data: json!({
|
||||||
"tool": title,
|
"tool": title,
|
||||||
"toolCallId": tool_call_id,
|
"toolCallId": tool_call_id,
|
||||||
"kind": kind,
|
"kind": kind,
|
||||||
|
"status": status,
|
||||||
|
"input": raw_input,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
AcpSessionEvent::ToolCallUpdate {
|
AcpSessionEvent::ToolCallUpdate {
|
||||||
tool_call_id,
|
tool_call_id,
|
||||||
status,
|
status,
|
||||||
|
content,
|
||||||
} => {
|
} => {
|
||||||
let error = status == crate::acp_types::ToolCallStatus::Failed;
|
let error = status == crate::acp_types::ToolCallStatus::Failed;
|
||||||
|
let event = if error {
|
||||||
|
"tool.failed"
|
||||||
|
} else if status == crate::acp_types::ToolCallStatus::Completed {
|
||||||
|
"tool.completed"
|
||||||
|
} else {
|
||||||
|
"tool.started"
|
||||||
|
};
|
||||||
Some(SseEvent {
|
Some(SseEvent {
|
||||||
event: "tool.completed".into(),
|
event: event.into(),
|
||||||
data: json!({
|
data: json!({
|
||||||
"toolCallId": tool_call_id,
|
"toolCallId": tool_call_id,
|
||||||
|
"status": status,
|
||||||
"error": error,
|
"error": error,
|
||||||
|
"output": content,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -278,4 +291,45 @@ mod tests {
|
|||||||
assert_eq!(sse.event, "thought.delta");
|
assert_eq!(sse.event, "thought.delta");
|
||||||
assert_eq!(sse.data["delta"], "internal reasoning");
|
assert_eq!(sse.data["delta"], "internal reasoning");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn acp_tool_events_keep_detail_for_collapsible_ui() {
|
||||||
|
let started = acp_event_to_sse(AcpSessionEvent::ToolCall {
|
||||||
|
tool_call_id: "tool_1".into(),
|
||||||
|
title: "mnote.page.get".into(),
|
||||||
|
kind: "read".into(),
|
||||||
|
status: crate::acp_types::ToolCallStatus::InProgress,
|
||||||
|
raw_input: Some(json!({"documentId": "doc_1", "includeBody": true})),
|
||||||
|
})
|
||||||
|
.expect("tool start");
|
||||||
|
assert_eq!(started.event, "tool.started");
|
||||||
|
assert_eq!(started.data["tool"], "mnote.page.get");
|
||||||
|
assert_eq!(started.data["status"], "in_progress");
|
||||||
|
assert_eq!(started.data["input"]["documentId"], "doc_1");
|
||||||
|
|
||||||
|
let completed = acp_event_to_sse(AcpSessionEvent::ToolCallUpdate {
|
||||||
|
tool_call_id: "tool_1".into(),
|
||||||
|
status: crate::acp_types::ToolCallStatus::Completed,
|
||||||
|
content: Some(vec![crate::acp_types::ContentBlockWrapper {
|
||||||
|
wrapper_type: "content".into(),
|
||||||
|
content: crate::acp_types::TextContent {
|
||||||
|
content_type: "text".into(),
|
||||||
|
text: "读取完成".into(),
|
||||||
|
},
|
||||||
|
}]),
|
||||||
|
})
|
||||||
|
.expect("tool complete");
|
||||||
|
assert_eq!(completed.event, "tool.completed");
|
||||||
|
assert_eq!(completed.data["status"], "completed");
|
||||||
|
assert_eq!(completed.data["output"][0]["content"]["text"], "读取完成");
|
||||||
|
|
||||||
|
let running = acp_event_to_sse(AcpSessionEvent::ToolCallUpdate {
|
||||||
|
tool_call_id: "tool_1".into(),
|
||||||
|
status: crate::acp_types::ToolCallStatus::InProgress,
|
||||||
|
content: None,
|
||||||
|
})
|
||||||
|
.expect("tool running");
|
||||||
|
assert_eq!(running.event, "tool.started");
|
||||||
|
assert_eq!(running.data["status"], "in_progress");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
/// - `reference-code/hermes-vscode-main/src/protocol.ts` (dedup logic)
|
/// - `reference-code/hermes-vscode-main/src/protocol.ts` (dedup logic)
|
||||||
use crate::acp_client::AcpClient;
|
use crate::acp_client::AcpClient;
|
||||||
use crate::acp_types::{
|
use crate::acp_types::{
|
||||||
ContentBlock, SessionNewParams, SessionNewResult, SessionPromptParams, SessionPromptResult,
|
ContentBlock, ContentBlockWrapper, SessionNewParams, SessionNewResult, SessionPromptParams,
|
||||||
SessionUpdate, ToolCallStatus,
|
SessionPromptResult, SessionUpdate, ToolCallStatus,
|
||||||
};
|
};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
@@ -33,11 +33,13 @@ pub enum AcpSessionEvent {
|
|||||||
title: String,
|
title: String,
|
||||||
kind: String,
|
kind: String,
|
||||||
status: ToolCallStatus,
|
status: ToolCallStatus,
|
||||||
|
raw_input: Option<Value>,
|
||||||
},
|
},
|
||||||
/// Tool call status update (with optional result content).
|
/// Tool call status update (with optional result content).
|
||||||
ToolCallUpdate {
|
ToolCallUpdate {
|
||||||
tool_call_id: String,
|
tool_call_id: String,
|
||||||
status: ToolCallStatus,
|
status: ToolCallStatus,
|
||||||
|
content: Option<Vec<ContentBlockWrapper>>,
|
||||||
},
|
},
|
||||||
/// Context usage update.
|
/// Context usage update.
|
||||||
UsageUpdate { used: u64, size: u64 },
|
UsageUpdate { used: u64, size: u64 },
|
||||||
@@ -361,6 +363,7 @@ impl AcpSessionManager {
|
|||||||
title,
|
title,
|
||||||
kind,
|
kind,
|
||||||
status,
|
status,
|
||||||
|
raw_input,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let title = title.clone().unwrap_or_else(|| "tool".into());
|
let title = title.clone().unwrap_or_else(|| "tool".into());
|
||||||
@@ -374,18 +377,21 @@ impl AcpSessionManager {
|
|||||||
title,
|
title,
|
||||||
kind: kind_str,
|
kind: kind_str,
|
||||||
status,
|
status,
|
||||||
|
raw_input: raw_input.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
SessionUpdate::ToolCallUpdate {
|
SessionUpdate::ToolCallUpdate {
|
||||||
tool_call_id,
|
tool_call_id,
|
||||||
status,
|
status,
|
||||||
|
content,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let status = status.clone().unwrap_or(ToolCallStatus::Completed);
|
let status = status.clone().unwrap_or(ToolCallStatus::Completed);
|
||||||
Some(AcpSessionEvent::ToolCallUpdate {
|
Some(AcpSessionEvent::ToolCallUpdate {
|
||||||
tool_call_id: tool_call_id.clone(),
|
tool_call_id: tool_call_id.clone(),
|
||||||
status,
|
status,
|
||||||
|
content: content.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3936,6 +3936,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
id: 'sess_' + now + '_' + Math.random().toString(16).slice(2, 8),
|
id: 'sess_' + now + '_' + Math.random().toString(16).slice(2, 8),
|
||||||
title: title || '新会话',
|
title: title || '新会话',
|
||||||
profile: pageAiCurrentProfile(),
|
profile: pageAiCurrentProfile(),
|
||||||
|
acpRuntime: pageUiState.pageAiAcpRuntime || '',
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
messages: []
|
messages: []
|
||||||
@@ -3950,9 +3951,10 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
id: String(session && session.id || '').trim() || pageAiNewSession().id,
|
id: String(session && session.id || '').trim() || pageAiNewSession().id,
|
||||||
title: String(session && session.title || '').trim() || '新会话',
|
title: String(session && session.title || '').trim() || '新会话',
|
||||||
profile: String(session && session.profile || pageAiCurrentProfile()).trim() || 'default',
|
profile: String(session && session.profile || pageAiCurrentProfile()).trim() || 'default',
|
||||||
|
acpRuntime: String(session && session.acpRuntime || '').trim(),
|
||||||
createdAt: Number(session && session.createdAt || Date.now()),
|
createdAt: Number(session && session.createdAt || Date.now()),
|
||||||
updatedAt: Number(session && session.updatedAt || Date.now()),
|
updatedAt: Number(session && session.updatedAt || Date.now()),
|
||||||
messages: Array.isArray(session && session.messages) ? session.messages.slice(-40) : []
|
messages: Array.isArray(session && session.messages) ? session.messages.slice(-300) : []
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.sort(function(a, b) {
|
.sort(function(a, b) {
|
||||||
@@ -3967,7 +3969,19 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
var parsed = raw ? JSON.parse(raw) : null;
|
var parsed = raw ? JSON.parse(raw) : null;
|
||||||
var activeId = String(parsed && parsed.activeSessionId || '').trim();
|
var activeId = String(parsed && parsed.activeSessionId || '').trim();
|
||||||
var activeProfile = String(parsed && parsed.activeProfileName || '').trim();
|
var activeProfile = String(parsed && parsed.activeProfileName || '').trim();
|
||||||
|
var activeAcpRuntime = String(parsed && parsed.activeAcpRuntime || '').trim();
|
||||||
|
var sessions = pageAiNormalizeSessions(parsed && parsed.sessions);
|
||||||
|
if (activeAcpRuntime) pageUiState.pageAiAcpRuntime = activeAcpRuntime;
|
||||||
if (activeProfile) pageAiSetActiveProfile(activeProfile);
|
if (activeProfile) pageAiSetActiveProfile(activeProfile);
|
||||||
|
if (sessions.length) {
|
||||||
|
pageUiState.pageAiSessions = sessions;
|
||||||
|
var activeSession = sessions.find(function(session) { return session.id === activeId; }) || sessions[0];
|
||||||
|
pageUiState.pageAiActiveSessionId = activeSession.id;
|
||||||
|
if (activeSession.profile) pageAiSetActiveProfile(activeSession.profile);
|
||||||
|
if (activeSession.acpRuntime) pageUiState.pageAiAcpRuntime = activeSession.acpRuntime;
|
||||||
|
pageUiState.pageAiMessages = Array.isArray(activeSession.messages) ? activeSession.messages.slice() : [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (activeId) {
|
if (activeId) {
|
||||||
pageUiState.pageAiSessions = [pageAiNewSession('当前页问答')];
|
pageUiState.pageAiSessions = [pageAiNewSession('当前页问答')];
|
||||||
pageUiState.pageAiSessions[0].id = activeId;
|
pageUiState.pageAiSessions[0].id = activeId;
|
||||||
@@ -3986,9 +4000,13 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
document.documentElement.setAttribute('data-mnote-page-ai-session-owner', 'hermes');
|
document.documentElement.setAttribute('data-mnote-page-ai-session-owner', 'hermes');
|
||||||
document.documentElement.setAttribute('data-mnote-page-ai-session-key', pageAiStorageKey());
|
document.documentElement.setAttribute('data-mnote-page-ai-session-key', pageAiStorageKey());
|
||||||
try {
|
try {
|
||||||
|
pageAiSyncCurrentSessionMessages();
|
||||||
window.localStorage.setItem(pageAiStorageKey(), JSON.stringify({
|
window.localStorage.setItem(pageAiStorageKey(), JSON.stringify({
|
||||||
|
version: 2,
|
||||||
activeSessionId: pageUiState.pageAiActiveSessionId,
|
activeSessionId: pageUiState.pageAiActiveSessionId,
|
||||||
activeProfileName: pageAiCurrentProfile()
|
activeProfileName: pageAiCurrentProfile(),
|
||||||
|
activeAcpRuntime: pageUiState.pageAiAcpRuntime || '',
|
||||||
|
sessions: pageAiNormalizeSessions(pageUiState.pageAiSessions)
|
||||||
}));
|
}));
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
@@ -4016,6 +4034,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
id: String(payload.sessionId || '').trim(),
|
id: String(payload.sessionId || '').trim(),
|
||||||
title: String(payload.title || '当前页问答'),
|
title: String(payload.title || '当前页问答'),
|
||||||
profile: String(payload.profile || pageAiCurrentProfile()).trim() || 'default',
|
profile: String(payload.profile || pageAiCurrentProfile()).trim() || 'default',
|
||||||
|
acpRuntime: pageUiState.pageAiAcpRuntime || '',
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
updatedAt: Date.now(),
|
updatedAt: Date.now(),
|
||||||
messages: pageUiState.pageAiMessages.slice()
|
messages: pageUiState.pageAiMessages.slice()
|
||||||
@@ -4046,7 +4065,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
renderPageAiControls();
|
renderPageAiControls();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pageUiState.pageAiMessages = messages.slice(-40).map(function(message) {
|
pageUiState.pageAiMessages = messages.slice(-300).map(function(message) {
|
||||||
return {
|
return {
|
||||||
role: message.role === 'user' ? 'user' : (message.role === 'tool' ? 'tool' : 'assistant'),
|
role: message.role === 'user' ? 'user' : (message.role === 'tool' ? 'tool' : 'assistant'),
|
||||||
content: String(message.content || '')
|
content: String(message.content || '')
|
||||||
@@ -4064,6 +4083,15 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
}) || null;
|
}) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pageAiSyncCurrentSessionMessages() {
|
||||||
|
var session = pageAiCurrentSession();
|
||||||
|
if (!session) return;
|
||||||
|
session.messages = Array.isArray(pageUiState.pageAiMessages) ? pageUiState.pageAiMessages.slice(-300) : [];
|
||||||
|
session.profile = pageAiCurrentProfile();
|
||||||
|
session.acpRuntime = pageUiState.pageAiAcpRuntime || '';
|
||||||
|
session.updatedAt = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
function pageAiSetActiveSession(sessionId) {
|
function pageAiSetActiveSession(sessionId) {
|
||||||
var session = pageUiState.pageAiSessions.find(function(item) { return item.id === sessionId; });
|
var session = pageUiState.pageAiSessions.find(function(item) { return item.id === sessionId; });
|
||||||
if (!session) return;
|
if (!session) return;
|
||||||
@@ -4359,9 +4387,14 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
} catch (_) {
|
} catch (_) {
|
||||||
toolEvent = {};
|
toolEvent = {};
|
||||||
}
|
}
|
||||||
var toolName = String(toolEvent && (toolEvent.name || toolEvent.tool || toolEvent.toolName || eventName) || eventName);
|
var rawToolName = toolEvent && (toolEvent.name || toolEvent.tool || toolEvent.toolName || '');
|
||||||
|
var toolName = String(rawToolName || eventName);
|
||||||
var toolCallId = String(toolEvent && (toolEvent.tool_call_id || toolEvent.toolCallId || toolEvent.id) || (runId + ':' + toolName));
|
var toolCallId = String(toolEvent && (toolEvent.tool_call_id || toolEvent.toolCallId || toolEvent.id) || (runId + ':' + toolName));
|
||||||
var status = eventName === 'tool.completed' ? 'completed' : (eventName === 'tool.failed' ? 'failed' : 'running');
|
var eventStatus = String(toolEvent && toolEvent.status || '').trim();
|
||||||
|
var status = eventName === 'tool.completed'
|
||||||
|
? (toolEvent && toolEvent.error ? 'failed' : 'completed')
|
||||||
|
: (eventName === 'tool.failed' ? 'failed' : (eventStatus || 'running'));
|
||||||
|
if (status === 'in_progress' || status === 'pending') status = 'running';
|
||||||
var traceId = String(toolEvent && (toolEvent.trace_id || toolEvent.traceId) || runTraceId || '');
|
var traceId = String(toolEvent && (toolEvent.trace_id || toolEvent.traceId) || runTraceId || '');
|
||||||
var auditId = String(toolEvent && (toolEvent.audit_id || toolEvent.auditId) || '');
|
var auditId = String(toolEvent && (toolEvent.audit_id || toolEvent.auditId) || '');
|
||||||
var argsSummary = pageAiPreviewValue(toolEvent && (toolEvent.arguments || toolEvent.args || toolEvent.input));
|
var argsSummary = pageAiPreviewValue(toolEvent && (toolEvent.arguments || toolEvent.args || toolEvent.input));
|
||||||
@@ -4380,12 +4413,14 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
var existing = pageUiState.pageAiMessages.find(function(item) {
|
var existing = pageUiState.pageAiMessages.find(function(item) {
|
||||||
return item.role === 'tool' && item.toolCallId === toolCallId;
|
return item.role === 'tool' && item.toolCallId === toolCallId;
|
||||||
});
|
});
|
||||||
|
if (!rawToolName && existing && existing.toolName) toolName = existing.toolName;
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
existing = {
|
existing = {
|
||||||
role: 'tool',
|
role: 'tool',
|
||||||
content: toolName,
|
content: toolName,
|
||||||
toolCallId: toolCallId,
|
toolCallId: toolCallId,
|
||||||
toolName: toolName,
|
toolName: toolName,
|
||||||
|
toolKind: String(toolEvent && toolEvent.kind || ''),
|
||||||
status: status,
|
status: status,
|
||||||
argsSummary: '',
|
argsSummary: '',
|
||||||
resultSummary: '',
|
resultSummary: '',
|
||||||
@@ -4396,6 +4431,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
}
|
}
|
||||||
existing.content = toolName;
|
existing.content = toolName;
|
||||||
existing.toolName = toolName;
|
existing.toolName = toolName;
|
||||||
|
existing.toolKind = String(toolEvent && toolEvent.kind || existing.toolKind || '');
|
||||||
existing.status = status;
|
existing.status = status;
|
||||||
existing.traceId = traceId || existing.traceId || '';
|
existing.traceId = traceId || existing.traceId || '';
|
||||||
existing.auditId = auditId || existing.auditId || '';
|
existing.auditId = auditId || existing.auditId || '';
|
||||||
@@ -4864,7 +4900,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
// When ACP is selected, populate agent panel with ACP runtime info
|
// When ACP is selected, populate agent panel with ACP runtime info
|
||||||
var agentPanel = drawer.querySelector('[data-page-ai-agent-panel]');
|
var agentPanel = drawer.querySelector('[data-page-ai-agent-panel]');
|
||||||
if (agentPanel instanceof HTMLElement) {
|
if (agentPanel instanceof HTMLElement) {
|
||||||
if (isAcp) {
|
if (pageUiState.pageAiAcpRuntime === 'reasonix') {
|
||||||
var rt = pageUiState.pageAiAcpRuntimes.find(function(r) { return r.name === pageUiState.pageAiAcpRuntime; }) || {};
|
var rt = pageUiState.pageAiAcpRuntimes.find(function(r) { return r.name === pageUiState.pageAiAcpRuntime; }) || {};
|
||||||
var keyStatus = rt.apiKeyConfigured ? '已配置' : '未检测到 DEEPSEEK_API_KEY';
|
var keyStatus = rt.apiKeyConfigured ? '已配置' : '未检测到 DEEPSEEK_API_KEY';
|
||||||
agentPanel.innerHTML = '' +
|
agentPanel.innerHTML = '' +
|
||||||
@@ -4966,8 +5002,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
memoryError.hidden = !memoryErrorText;
|
memoryError.hidden = !memoryErrorText;
|
||||||
}
|
}
|
||||||
var agentPanel = drawer.querySelector('[data-page-ai-agent-panel]');
|
var agentPanel = drawer.querySelector('[data-page-ai-agent-panel]');
|
||||||
if (agentPanel instanceof HTMLElement && !isAcp) {
|
if (agentPanel instanceof HTMLElement && pageUiState.pageAiAcpRuntime !== 'reasonix') {
|
||||||
// Only populate Hermes memory when NOT in ACP mode (ACP handles this earlier in the function)
|
|
||||||
agentPanel.innerHTML = ['soul', 'user', 'memory'].map(function(section) {
|
agentPanel.innerHTML = ['soul', 'user', 'memory'].map(function(section) {
|
||||||
var label = pageAiMemoryFileLabel(section);
|
var label = pageAiMemoryFileLabel(section);
|
||||||
var value = pageUiState.pageAiProfileMemoryDrafts[section] || '';
|
var value = pageUiState.pageAiProfileMemoryDrafts[section] || '';
|
||||||
@@ -5322,20 +5357,28 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
var roleLabel = item.role === 'user' ? '你' : (item.role === 'tool' ? '工具' : 'AI');
|
var roleLabel = item.role === 'user' ? '你' : (item.role === 'tool' ? '工具' : 'AI');
|
||||||
if (item.role === 'tool') {
|
if (item.role === 'tool') {
|
||||||
var statusLabel = item.status === 'completed' ? '完成' : (item.status === 'failed' ? '失败' : '运行中');
|
var statusLabel = item.status === 'completed' ? '完成' : (item.status === 'failed' ? '失败' : '运行中');
|
||||||
|
var detailRows = [
|
||||||
|
item.argsSummary ? '<div class="wolai-page-ai-tool-meta">参数 ' + escapeHtml(item.argsSummary) + '</div>' : '',
|
||||||
|
item.resultSummary ? '<div class="wolai-page-ai-tool-meta">结果 ' + escapeHtml(item.resultSummary) + '</div>' : '',
|
||||||
|
(item.traceId || item.auditId) ? '<div class="wolai-page-ai-tool-meta">' + escapeHtml([item.traceId, item.auditId].filter(Boolean).join(' · ')) + '</div>' : ''
|
||||||
|
].filter(Boolean).join('');
|
||||||
return '' +
|
return '' +
|
||||||
'<div class="wolai-page-ai-message wolai-page-ai-message--tool" data-page-ai-tool-card data-page-ai-tool-call-id="' + escapeHtml(item.toolCallId || '') + '" data-page-ai-tool-status="' + escapeHtml(item.status || '') + '">' +
|
'<div class="wolai-page-ai-message wolai-page-ai-message--tool" data-page-ai-tool-card data-page-ai-tool-call-id="' + escapeHtml(item.toolCallId || '') + '" data-page-ai-tool-status="' + escapeHtml(item.status || '') + '">' +
|
||||||
'<div class="wolai-page-ai-message-role">' + escapeHtml(roleLabel) + '</div>' +
|
'<div class="wolai-page-ai-message-role">' + escapeHtml(roleLabel) + '</div>' +
|
||||||
'<div class="wolai-page-ai-message-text">' +
|
'<div class="wolai-page-ai-message-text">' +
|
||||||
'<strong>' + escapeHtml(item.toolName || item.content || 'tool') + '</strong>' +
|
'<details class="wolai-page-ai-tool-details">' +
|
||||||
'<div class="wolai-page-ai-tool-meta">' + escapeHtml(statusLabel) + (item.toolCallId ? ' · ' + escapeHtml(item.toolCallId) : '') + '</div>' +
|
'<summary>' +
|
||||||
(item.argsSummary ? '<div class="wolai-page-ai-tool-meta">参数 ' + escapeHtml(item.argsSummary) + '</div>' : '') +
|
'<strong>' + escapeHtml(item.toolName || item.content || 'tool') + '</strong>' +
|
||||||
(item.resultSummary ? '<div class="wolai-page-ai-tool-meta">结果 ' + escapeHtml(item.resultSummary) + '</div>' : '') +
|
'<span>' + escapeHtml([statusLabel, item.toolKind, item.toolCallId].filter(Boolean).join(' · ')) + '</span>' +
|
||||||
((item.traceId || item.auditId) ? '<div class="wolai-page-ai-tool-meta">' + escapeHtml([item.traceId, item.auditId].filter(Boolean).join(' · ')) + '</div>' : '') +
|
'</summary>' +
|
||||||
|
(detailRows || '<div class="wolai-page-ai-tool-meta">暂无参数或结果详情。</div>') +
|
||||||
|
'</details>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
}
|
}
|
||||||
|
var streamingAttr = item.streaming ? ' data-page-ai-streaming="true"' : '';
|
||||||
return '' +
|
return '' +
|
||||||
'<div class="wolai-page-ai-message wolai-page-ai-message--' + escapeHtml(item.role || 'assistant') + '">' +
|
'<div class="wolai-page-ai-message wolai-page-ai-message--' + escapeHtml(item.role || 'assistant') + '"' + streamingAttr + '>' +
|
||||||
'<div class="wolai-page-ai-message-role">' + escapeHtml(roleLabel) + '</div>' +
|
'<div class="wolai-page-ai-message-role">' + escapeHtml(roleLabel) + '</div>' +
|
||||||
'<div class="wolai-page-ai-message-text">' + escapeHtml(item.content || '') + '</div>' +
|
'<div class="wolai-page-ai-message-text">' + escapeHtml(item.content || '') + '</div>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
@@ -5419,6 +5462,62 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pageAiDecodeDeltaText(payloadText) {
|
||||||
|
try {
|
||||||
|
var payload = JSON.parse(payloadText || 'null');
|
||||||
|
return String((payload && (payload.text || payload.delta)) || '');
|
||||||
|
} catch (_) {
|
||||||
|
return String(payloadText || '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageAiEnsureStreamingAssistantMessage(runId) {
|
||||||
|
var id = String(runId || pageUiState.pageAiCurrentRunId || '');
|
||||||
|
var existing = pageUiState.pageAiMessages.find(function(item) {
|
||||||
|
return item.role === 'assistant' && item.streaming === true && item.runId === id;
|
||||||
|
});
|
||||||
|
if (existing) return existing;
|
||||||
|
existing = {
|
||||||
|
role: 'assistant',
|
||||||
|
content: '',
|
||||||
|
runId: id,
|
||||||
|
streaming: true
|
||||||
|
};
|
||||||
|
pageUiState.pageAiMessages.push(existing);
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageAiAppendStreamingAssistantDelta(runId, deltaText) {
|
||||||
|
var delta = String(deltaText || '');
|
||||||
|
if (!delta) return '';
|
||||||
|
var message = pageAiEnsureStreamingAssistantMessage(runId);
|
||||||
|
message.content = String(message.content || '') + delta;
|
||||||
|
pageAiSyncCurrentSessionMessages();
|
||||||
|
renderPageAiConversation();
|
||||||
|
return message.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pageAiFinishStreamingAssistantMessage(runId, finalText, promptText) {
|
||||||
|
var id = String(runId || pageUiState.pageAiCurrentRunId || '');
|
||||||
|
var message = pageUiState.pageAiMessages.find(function(item) {
|
||||||
|
return item.role === 'assistant' && item.streaming === true && item.runId === id;
|
||||||
|
});
|
||||||
|
var text = String(finalText || (message && message.content) || '');
|
||||||
|
var content = humanizePageAiResponse(text, promptText);
|
||||||
|
if (message) {
|
||||||
|
message.content = content;
|
||||||
|
message.streaming = false;
|
||||||
|
} else if (content) {
|
||||||
|
pageUiState.pageAiMessages.push({
|
||||||
|
role: 'assistant',
|
||||||
|
content: content,
|
||||||
|
runId: id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pageAiSyncCurrentSessionMessages();
|
||||||
|
pageAiPersistSessions();
|
||||||
|
}
|
||||||
|
|
||||||
function pageAiLooksLikeBlockEdit(prompt) {
|
function pageAiLooksLikeBlockEdit(prompt) {
|
||||||
var text = searchText(prompt);
|
var text = searchText(prompt);
|
||||||
return ['新增', '添加', '插入', '删除', '删掉', '修改', '替换', '改成', '移动', '移到', 'move', 'replace', 'delete', 'insert'].some(function(word) {
|
return ['新增', '添加', '插入', '删除', '删掉', '修改', '替换', '改成', '移动', '移到', 'move', 'replace', 'delete', 'insert'].some(function(word) {
|
||||||
@@ -5496,6 +5595,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
currentSession.messages = pageUiState.pageAiMessages.slice();
|
currentSession.messages = pageUiState.pageAiMessages.slice();
|
||||||
currentSession.updatedAt = Date.now();
|
currentSession.updatedAt = Date.now();
|
||||||
}
|
}
|
||||||
|
pageAiPersistSessions();
|
||||||
renderPageAiConversation();
|
renderPageAiConversation();
|
||||||
renderPageAiControls();
|
renderPageAiControls();
|
||||||
return true;
|
return true;
|
||||||
@@ -5523,6 +5623,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
currentSession.messages = pageUiState.pageAiMessages.slice();
|
currentSession.messages = pageUiState.pageAiMessages.slice();
|
||||||
currentSession.updatedAt = Date.now();
|
currentSession.updatedAt = Date.now();
|
||||||
}
|
}
|
||||||
|
pageAiPersistSessions();
|
||||||
renderPageAiConversation();
|
renderPageAiConversation();
|
||||||
if (!allowQueue && await pageAiTryBlockEditWorkflow(prompt, scopedContext)) {
|
if (!allowQueue && await pageAiTryBlockEditWorkflow(prompt, scopedContext)) {
|
||||||
return;
|
return;
|
||||||
@@ -5559,6 +5660,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
currentSession.messages = pageUiState.pageAiMessages.slice();
|
currentSession.messages = pageUiState.pageAiMessages.slice();
|
||||||
currentSession.updatedAt = Date.now();
|
currentSession.updatedAt = Date.now();
|
||||||
}
|
}
|
||||||
|
pageAiPersistSessions();
|
||||||
renderPageAiConversation();
|
renderPageAiConversation();
|
||||||
renderPageAiControls();
|
renderPageAiControls();
|
||||||
return;
|
return;
|
||||||
@@ -5583,24 +5685,19 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
await streamPageAiResponse(eventResponse, function(eventName, payloadText) {
|
await streamPageAiResponse(eventResponse, function(eventName, payloadText) {
|
||||||
if (eventName === 'message.delta') {
|
if (eventName === 'message.delta') {
|
||||||
if (pageUiState.pageAiStoppedRunIds[runId]) return;
|
if (pageUiState.pageAiStoppedRunIds[runId]) return;
|
||||||
try {
|
assistantText = pageAiAppendStreamingAssistantDelta(runId, pageAiDecodeDeltaText(payloadText));
|
||||||
var payload = JSON.parse(payloadText || 'null');
|
|
||||||
assistantText += searchText((payload && (payload.text || payload.delta)) || '');
|
|
||||||
} catch (_) {
|
|
||||||
assistantText += searchText(payloadText);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (eventName === 'run.completed') {
|
if (eventName === 'run.completed') {
|
||||||
try {
|
try {
|
||||||
var completed = JSON.parse(payloadText || 'null');
|
var completed = JSON.parse(payloadText || 'null');
|
||||||
if (completed && completed.output) assistantText = searchText(completed.output);
|
if (completed && completed.output) assistantText = String(completed.output || '');
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
pageAiSetRunStatus('completed', runId);
|
pageAiSetRunStatus('completed', runId);
|
||||||
}
|
}
|
||||||
if (eventName === 'run.failed') {
|
if (eventName === 'run.failed') {
|
||||||
try {
|
try {
|
||||||
var failed = JSON.parse(payloadText || 'null');
|
var failed = JSON.parse(payloadText || 'null');
|
||||||
assistantText = searchText((failed && (failed.message || failed.code)) || 'Hermes run failed');
|
assistantText = String((failed && (failed.message || failed.code || failed.error)) || 'Hermes run failed');
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
pageAiSetRunStatus('failed', runId);
|
pageAiSetRunStatus('failed', runId);
|
||||||
}
|
}
|
||||||
@@ -5610,22 +5707,22 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
}
|
}
|
||||||
if (eventName === 'tool.started' || eventName === 'tool.completed' || eventName === 'tool.failed') {
|
if (eventName === 'tool.started' || eventName === 'tool.completed' || eventName === 'tool.failed') {
|
||||||
pageAiApplyToolEvent(eventName, payloadText, runId, runTraceId);
|
pageAiApplyToolEvent(eventName, payloadText, runId, runTraceId);
|
||||||
|
pageAiSyncCurrentSessionMessages();
|
||||||
|
pageAiPersistSessions();
|
||||||
renderPageAiConversation();
|
renderPageAiConversation();
|
||||||
pageAiSetRunStatus('tool_calling', runId);
|
pageAiSetRunStatus('tool_calling', runId);
|
||||||
renderPageAiControls();
|
renderPageAiControls();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!pageUiState.pageAiStoppedRunIds[runId]) {
|
if (!pageUiState.pageAiStoppedRunIds[runId]) {
|
||||||
pageUiState.pageAiMessages.push({
|
pageAiFinishStreamingAssistantMessage(runId, assistantText, prompt);
|
||||||
role: 'assistant',
|
|
||||||
content: humanizePageAiResponse(assistantText, prompt)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
currentSession = pageAiCurrentSession();
|
currentSession = pageAiCurrentSession();
|
||||||
if (currentSession) {
|
if (currentSession) {
|
||||||
currentSession.messages = pageUiState.pageAiMessages.slice();
|
currentSession.messages = pageUiState.pageAiMessages.slice();
|
||||||
currentSession.updatedAt = Date.now();
|
currentSession.updatedAt = Date.now();
|
||||||
}
|
}
|
||||||
|
pageAiPersistSessions();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
pageAiSetRunStatus('failed');
|
pageAiSetRunStatus('failed');
|
||||||
pageUiState.pageAiMessages.push({
|
pageUiState.pageAiMessages.push({
|
||||||
@@ -5637,6 +5734,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
currentSession.messages = pageUiState.pageAiMessages.slice();
|
currentSession.messages = pageUiState.pageAiMessages.slice();
|
||||||
currentSession.updatedAt = Date.now();
|
currentSession.updatedAt = Date.now();
|
||||||
}
|
}
|
||||||
|
pageAiPersistSessions();
|
||||||
} finally {
|
} finally {
|
||||||
if (!allowQueue) pageUiState.pageAiBusy = false;
|
if (!allowQueue) pageUiState.pageAiBusy = false;
|
||||||
renderPageAiConversation();
|
renderPageAiConversation();
|
||||||
@@ -6579,7 +6677,9 @@ const SIDEBAR_TREE_JS: &str = r##"
|
|||||||
pageUiState.pageAiAcpRuntime = next;
|
pageUiState.pageAiAcpRuntime = next;
|
||||||
pageUiState.pageAiSkills = { categories: [], archived: [] };
|
pageUiState.pageAiSkills = { categories: [], archived: [] };
|
||||||
pageUiState.pageAiSkillError = '';
|
pageUiState.pageAiSkillError = '';
|
||||||
|
pageAiPersistSessions();
|
||||||
void pageAiLoadProfiles();
|
void pageAiLoadProfiles();
|
||||||
|
if (next !== 'reasonix') void pageAiLoadProfileMemory();
|
||||||
void pageAiLoadSkills();
|
void pageAiLoadSkills();
|
||||||
renderPageAiControls();
|
renderPageAiControls();
|
||||||
renderPageAiProviderButtons();
|
renderPageAiProviderButtons();
|
||||||
|
|||||||
@@ -3388,6 +3388,10 @@ body {
|
|||||||
background: #F4F8FF;
|
background: #F4F8FF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wolai-page-ai-message[data-page-ai-streaming="true"] .wolai-page-ai-message-role::after {
|
||||||
|
content: " · 输出中";
|
||||||
|
}
|
||||||
|
|
||||||
.wolai-page-ai-message-text {
|
.wolai-page-ai-message-text {
|
||||||
color: #1B1C1C;
|
color: #1B1C1C;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
@@ -3395,6 +3399,52 @@ body {
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wolai-page-ai-tool-details {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wolai-page-ai-tool-details summary {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
color: #1B1C1C;
|
||||||
|
cursor: pointer;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wolai-page-ai-tool-details summary::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wolai-page-ai-tool-details summary::before {
|
||||||
|
content: "▸";
|
||||||
|
color: #8B8782;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wolai-page-ai-tool-details[open] summary::before {
|
||||||
|
content: "▾";
|
||||||
|
}
|
||||||
|
|
||||||
|
.wolai-page-ai-tool-details summary strong {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wolai-page-ai-tool-details summary span {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
color: #8B8782;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 16px;
|
||||||
|
text-align: right;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.wolai-page-ai-footer {
|
.wolai-page-ai-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
Reference in New Issue
Block a user