@ai-zen/cli 1.0.0-alpha.20 → 1.0.0-alpha.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +158 -0
- package/README.md +43 -17
- package/README.zh.md +43 -17
- package/dist/agent-creator.d.ts.map +1 -1
- package/dist/agent-creator.js +32 -10
- package/dist/agent-creator.js.map +1 -1
- package/dist/agent-switch.d.ts +61 -0
- package/dist/agent-switch.d.ts.map +1 -0
- package/dist/agent-switch.js +83 -0
- package/dist/agent-switch.js.map +1 -0
- package/dist/agents-store.d.ts +6 -1
- package/dist/agents-store.d.ts.map +1 -1
- package/dist/agents-store.js +6 -1
- package/dist/agents-store.js.map +1 -1
- package/dist/auto-migrate-confirm.d.ts.map +1 -1
- package/dist/auto-migrate-confirm.js +2 -1
- package/dist/auto-migrate-confirm.js.map +1 -1
- package/dist/config-editor.d.ts +9 -1
- package/dist/config-editor.d.ts.map +1 -1
- package/dist/config-editor.js +30 -19
- package/dist/config-editor.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +29 -13
- package/dist/config.js.map +1 -1
- package/dist/conversation-commands/registry.d.ts +123 -4
- package/dist/conversation-commands/registry.d.ts.map +1 -1
- package/dist/conversation-commands/registry.js +92 -17
- package/dist/conversation-commands/registry.js.map +1 -1
- package/dist/conversation-persist-plugin.d.ts.map +1 -1
- package/dist/conversation-persist-plugin.js +6 -14
- package/dist/conversation-persist-plugin.js.map +1 -1
- package/dist/conversation-repository.d.ts +68 -14
- package/dist/conversation-repository.d.ts.map +1 -1
- package/dist/conversation-repository.js +81 -23
- package/dist/conversation-repository.js.map +1 -1
- package/dist/cwd-tracker-plugin.d.ts.map +1 -1
- package/dist/cwd-tracker-plugin.js +6 -2
- package/dist/cwd-tracker-plugin.js.map +1 -1
- package/dist/format-time.d.ts +17 -6
- package/dist/format-time.d.ts.map +1 -1
- package/dist/format-time.js +32 -14
- package/dist/format-time.js.map +1 -1
- package/dist/handoff-service.d.ts.map +1 -1
- package/dist/handoff-service.js +22 -19
- package/dist/handoff-service.js.map +1 -1
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +69 -15
- package/dist/hook.js.map +1 -1
- package/dist/i18n/en-wizard.d.ts +8 -0
- package/dist/i18n/en-wizard.d.ts.map +1 -0
- package/dist/i18n/en-wizard.js +290 -0
- package/dist/i18n/en-wizard.js.map +1 -0
- package/dist/i18n/en.d.ts +7 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/en.js +224 -0
- package/dist/i18n/en.js.map +1 -0
- package/dist/i18n/index.d.ts +57 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +109 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/preferences.d.ts +34 -0
- package/dist/i18n/preferences.d.ts.map +1 -0
- package/dist/i18n/preferences.js +69 -0
- package/dist/i18n/preferences.js.map +1 -0
- package/dist/i18n/zh-wizard.d.ts +11 -0
- package/dist/i18n/zh-wizard.d.ts.map +1 -0
- package/dist/i18n/zh-wizard.js +293 -0
- package/dist/i18n/zh-wizard.js.map +1 -0
- package/dist/i18n/zh.d.ts +10 -0
- package/dist/i18n/zh.d.ts.map +1 -0
- package/dist/i18n/zh.js +228 -0
- package/dist/i18n/zh.js.map +1 -0
- package/dist/index.js +41 -23
- package/dist/index.js.map +1 -1
- package/dist/mcp-command.d.ts +32 -0
- package/dist/mcp-command.d.ts.map +1 -0
- package/dist/mcp-command.js +37 -0
- package/dist/mcp-command.js.map +1 -0
- package/dist/mode.d.ts +31 -4
- package/dist/mode.d.ts.map +1 -1
- package/dist/mode.js +42 -7
- package/dist/mode.js.map +1 -1
- package/dist/model-switch.d.ts.map +1 -1
- package/dist/model-switch.js +5 -4
- package/dist/model-switch.js.map +1 -1
- package/dist/reasoning-effort.d.ts +23 -4
- package/dist/reasoning-effort.d.ts.map +1 -1
- package/dist/reasoning-effort.js +51 -24
- package/dist/reasoning-effort.js.map +1 -1
- package/dist/sdk-log.d.ts +23 -16
- package/dist/sdk-log.d.ts.map +1 -1
- package/dist/sdk-log.js +43 -19
- package/dist/sdk-log.js.map +1 -1
- package/dist/stdio-runner.d.ts.map +1 -1
- package/dist/stdio-runner.js +19 -19
- package/dist/stdio-runner.js.map +1 -1
- package/dist/tui/chat-session.d.ts +27 -0
- package/dist/tui/chat-session.d.ts.map +1 -1
- package/dist/tui/chat-session.js +120 -28
- package/dist/tui/chat-session.js.map +1 -1
- package/dist/tui/components.d.ts +43 -2
- package/dist/tui/components.d.ts.map +1 -1
- package/dist/tui/components.js +97 -4
- package/dist/tui/components.js.map +1 -1
- package/dist/tui/config-wizard/detail.d.ts +12 -6
- package/dist/tui/config-wizard/detail.d.ts.map +1 -1
- package/dist/tui/config-wizard/detail.js +33 -32
- package/dist/tui/config-wizard/detail.js.map +1 -1
- package/dist/tui/config-wizard/engine.d.ts.map +1 -1
- package/dist/tui/config-wizard/engine.js +26 -20
- package/dist/tui/config-wizard/engine.js.map +1 -1
- package/dist/tui/config-wizard/fields.d.ts.map +1 -1
- package/dist/tui/config-wizard/fields.js +176 -164
- package/dist/tui/config-wizard/fields.js.map +1 -1
- package/dist/tui/config-wizard/kv-editor.d.ts.map +1 -1
- package/dist/tui/config-wizard/kv-editor.js +9 -5
- package/dist/tui/config-wizard/kv-editor.js.map +1 -1
- package/dist/tui/config-wizard/list.d.ts.map +1 -1
- package/dist/tui/config-wizard/list.js +2 -1
- package/dist/tui/config-wizard/list.js.map +1 -1
- package/dist/tui/config-wizard/mcp-helpers.d.ts.map +1 -1
- package/dist/tui/config-wizard/mcp-helpers.js +4 -3
- package/dist/tui/config-wizard/mcp-helpers.js.map +1 -1
- package/dist/tui/config-wizard/menu.d.ts.map +1 -1
- package/dist/tui/config-wizard/menu.js +32 -19
- package/dist/tui/config-wizard/menu.js.map +1 -1
- package/dist/tui/config-wizard/prompt-editor.d.ts.map +1 -1
- package/dist/tui/config-wizard/prompt-editor.js +4 -3
- package/dist/tui/config-wizard/prompt-editor.js.map +1 -1
- package/dist/tui/config-wizard/prompt.d.ts.map +1 -1
- package/dist/tui/config-wizard/prompt.js +8 -7
- package/dist/tui/config-wizard/prompt.js.map +1 -1
- package/dist/tui/config-wizard/step-agents.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-agents.js +29 -20
- package/dist/tui/config-wizard/step-agents.js.map +1 -1
- package/dist/tui/config-wizard/step-defaults.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-defaults.js +21 -17
- package/dist/tui/config-wizard/step-defaults.js.map +1 -1
- package/dist/tui/config-wizard/step-endpoints.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-endpoints.js +16 -6
- package/dist/tui/config-wizard/step-endpoints.js.map +1 -1
- package/dist/tui/config-wizard/step-mcp.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-mcp.js +17 -19
- package/dist/tui/config-wizard/step-mcp.js.map +1 -1
- package/dist/tui/config-wizard/step-models.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-models.js +41 -26
- package/dist/tui/config-wizard/step-models.js.map +1 -1
- package/dist/tui/config-wizard/step-new-endpoint.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-new-endpoint.js +16 -12
- package/dist/tui/config-wizard/step-new-endpoint.js.map +1 -1
- package/dist/tui/config-wizard/step-simple.d.ts.map +1 -1
- package/dist/tui/config-wizard/step-simple.js +18 -17
- package/dist/tui/config-wizard/step-simple.js.map +1 -1
- package/dist/tui/index.d.ts +17 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +25 -14
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/input-editing.d.ts +4 -1
- package/dist/tui/input-editing.d.ts.map +1 -1
- package/dist/tui/input-editing.js +7 -3
- package/dist/tui/input-editing.js.map +1 -1
- package/dist/tui/phase.d.ts +64 -0
- package/dist/tui/phase.d.ts.map +1 -0
- package/dist/tui/phase.js +112 -0
- package/dist/tui/phase.js.map +1 -0
- package/dist/tui/screens/chat-commands.d.ts +54 -3
- package/dist/tui/screens/chat-commands.d.ts.map +1 -1
- package/dist/tui/screens/chat-commands.js +469 -330
- package/dist/tui/screens/chat-commands.js.map +1 -1
- package/dist/tui/screens/chat.d.ts +8 -0
- package/dist/tui/screens/chat.d.ts.map +1 -1
- package/dist/tui/screens/chat.js +206 -405
- package/dist/tui/screens/chat.js.map +1 -1
- package/dist/tui/screens/common-keys.d.ts +36 -0
- package/dist/tui/screens/common-keys.d.ts.map +1 -0
- package/dist/tui/screens/common-keys.js +67 -0
- package/dist/tui/screens/common-keys.js.map +1 -0
- package/dist/tui/screens/handoff-choice.d.ts.map +1 -1
- package/dist/tui/screens/handoff-choice.js +4 -3
- package/dist/tui/screens/handoff-choice.js.map +1 -1
- package/dist/tui/screens/index.d.ts +1 -1
- package/dist/tui/screens/index.d.ts.map +1 -1
- package/dist/tui/screens/index.js +1 -1
- package/dist/tui/screens/index.js.map +1 -1
- package/dist/tui/screens/input-row.d.ts +64 -0
- package/dist/tui/screens/input-row.d.ts.map +1 -0
- package/dist/tui/screens/input-row.js +67 -0
- package/dist/tui/screens/input-row.js.map +1 -0
- package/dist/tui/screens/input.d.ts +11 -1
- package/dist/tui/screens/input.d.ts.map +1 -1
- package/dist/tui/screens/input.js +21 -12
- package/dist/tui/screens/input.js.map +1 -1
- package/dist/tui/screens/layout.d.ts +8 -0
- package/dist/tui/screens/layout.d.ts.map +1 -1
- package/dist/tui/screens/layout.js +6 -2
- package/dist/tui/screens/layout.js.map +1 -1
- package/dist/tui/screens/lines.d.ts.map +1 -1
- package/dist/tui/screens/lines.js +6 -6
- package/dist/tui/screens/lines.js.map +1 -1
- package/dist/tui/screens/messages-view.d.ts.map +1 -1
- package/dist/tui/screens/messages-view.js +5 -4
- package/dist/tui/screens/messages-view.js.map +1 -1
- package/dist/tui/screens/notice-bar.d.ts +5 -0
- package/dist/tui/screens/notice-bar.d.ts.map +1 -0
- package/dist/tui/screens/notice-bar.js +15 -0
- package/dist/tui/screens/notice-bar.js.map +1 -0
- package/dist/tui/screens/notice.d.ts +40 -8
- package/dist/tui/screens/notice.d.ts.map +1 -1
- package/dist/tui/screens/notice.js +75 -15
- package/dist/tui/screens/notice.js.map +1 -1
- package/dist/tui/screens/overlays.d.ts.map +1 -1
- package/dist/tui/screens/overlays.js +3 -2
- package/dist/tui/screens/overlays.js.map +1 -1
- package/dist/tui/screens/splash.d.ts +1 -1
- package/dist/tui/screens/splash.d.ts.map +1 -1
- package/dist/tui/screens/splash.js +5 -2
- package/dist/tui/screens/splash.js.map +1 -1
- package/dist/tui/screens/use-chat-viewport.d.ts +22 -0
- package/dist/tui/screens/use-chat-viewport.d.ts.map +1 -0
- package/dist/tui/screens/use-chat-viewport.js +50 -0
- package/dist/tui/screens/use-chat-viewport.js.map +1 -0
- package/dist/tui/screens/use-input-editing.d.ts +44 -0
- package/dist/tui/screens/use-input-editing.d.ts.map +1 -0
- package/dist/tui/screens/use-input-editing.js +192 -0
- package/dist/tui/screens/use-input-editing.js.map +1 -0
- package/dist/tui/screens/use-overlays.d.ts +25 -0
- package/dist/tui/screens/use-overlays.d.ts.map +1 -0
- package/dist/tui/screens/use-overlays.js +32 -0
- package/dist/tui/screens/use-overlays.js.map +1 -0
- package/dist/tui/screens/use-wizard-host.d.ts +35 -0
- package/dist/tui/screens/use-wizard-host.d.ts.map +1 -0
- package/dist/tui/screens/use-wizard-host.js +103 -0
- package/dist/tui/screens/use-wizard-host.js.map +1 -0
- package/dist/tui/status-segments.d.ts +37 -0
- package/dist/tui/status-segments.d.ts.map +1 -0
- package/dist/tui/status-segments.js +166 -0
- package/dist/tui/status-segments.js.map +1 -0
- package/dist/tui/text.d.ts +8 -5
- package/dist/tui/text.d.ts.map +1 -1
- package/dist/tui/text.js +95 -35
- package/dist/tui/text.js.map +1 -1
- package/dist/tui/token-rate.d.ts +83 -0
- package/dist/tui/token-rate.d.ts.map +1 -0
- package/dist/tui/token-rate.js +195 -0
- package/dist/tui/token-rate.js.map +1 -0
- package/dist/types.d.ts +0 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// 对话视口 —— 「翻页 / 按行 / 滚轮 / 回底」的唯一入口
|
|
2
|
+
//
|
|
3
|
+
// 从 `chat.tsx` 抽出来的理由:视口是**不依赖会话**的独立状态机(一个 `viewTop` 状态
|
|
4
|
+
// 加一份最近布局的引用),却被塞在 God Component 里,与消息、输入、向导混在一起。
|
|
5
|
+
// 抽出来后,「滚动」这件事只有这一个地方能改。
|
|
6
|
+
//
|
|
7
|
+
// 关键点:所有动作都走 `scrollTo()` → `clampViewTop()`,因此
|
|
8
|
+
// · 越界方向被自然吃掉,不会出现「滚过头回不来」;
|
|
9
|
+
// · 回到底部时 `clampViewTop()` 返回 `null`,视口**自动**恢复「跟随最新内容」,
|
|
10
|
+
// 不需要各处手写「滚到底就清空滚动状态」。
|
|
11
|
+
import { useCallback, useRef, useState } from "react";
|
|
12
|
+
import { WHEEL_STEP } from "../mouse.js";
|
|
13
|
+
import { clampViewTop } from "./layout.js";
|
|
14
|
+
export function useChatViewport() {
|
|
15
|
+
const [viewTop, setViewTop] = useState(null);
|
|
16
|
+
const layoutRef = useRef(null);
|
|
17
|
+
// 下面的回调只读 `layoutRef`(布局每帧更新)与 `setViewTop`,没有闭包捕获的旧状态,
|
|
18
|
+
// 所以依赖数组为空即可保持引用稳定(可安全传给 memo 化的子组件)。
|
|
19
|
+
// `scrollTo` 是下面三个动作的公共实现,**不对外暴露**:调用方只需要「翻页 / 按行 / 滚轮」,
|
|
20
|
+
// 直接设任意顶部值既没必要,也绕过了「以布局实际生效的顶部为基准」这条约束。
|
|
21
|
+
const scrollTo = useCallback((nextTop) => {
|
|
22
|
+
const L = layoutRef.current;
|
|
23
|
+
if (!L)
|
|
24
|
+
return;
|
|
25
|
+
setViewTop(clampViewTop(L.totalLines, L.budget, nextTop));
|
|
26
|
+
}, []);
|
|
27
|
+
const scrollByPage = useCallback((dir) => {
|
|
28
|
+
const L = layoutRef.current;
|
|
29
|
+
if (!L)
|
|
30
|
+
return;
|
|
31
|
+
// 按「布局实际生效的顶部」算,而不是原始 `viewTop`:贴底时 `viewTop` 是
|
|
32
|
+
// `null`,从 `layout.viewTop` 起算才能从底部往上翻一屏。
|
|
33
|
+
scrollTo(L.viewTop + dir * Math.max(1, L.budget - 1));
|
|
34
|
+
}, [scrollTo]);
|
|
35
|
+
const scrollByLine = useCallback((dir) => {
|
|
36
|
+
const L = layoutRef.current;
|
|
37
|
+
if (!L)
|
|
38
|
+
return;
|
|
39
|
+
scrollTo(L.viewTop + dir);
|
|
40
|
+
}, [scrollTo]);
|
|
41
|
+
const scrollByWheel = useCallback((dir) => {
|
|
42
|
+
const L = layoutRef.current;
|
|
43
|
+
if (!L)
|
|
44
|
+
return;
|
|
45
|
+
scrollTo(L.viewTop + dir * WHEEL_STEP);
|
|
46
|
+
}, [scrollTo]);
|
|
47
|
+
const scrollToBottom = useCallback(() => setViewTop(null), []);
|
|
48
|
+
return { viewTop, layoutRef, scrollByPage, scrollByLine, scrollByWheel, scrollToBottom };
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=use-chat-viewport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-chat-viewport.js","sourceRoot":"","sources":["../../../src/tui/screens/use-chat-viewport.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,EAAE;AACF,yDAAyD;AACzD,kDAAkD;AAClD,yBAAyB;AACzB,EAAE;AACF,gDAAgD;AAChD,8BAA8B;AAC9B,2DAA2D;AAC3D,2BAA2B;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAkB,MAAM,OAAO,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAmB,MAAM,aAAa,CAAC;AAqB5D,MAAM,UAAU,eAAe;IAC7B,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC,CAAC;IAC5D,MAAM,SAAS,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IAElD,wDAAwD;IACxD,sCAAsC;IACtC,0DAA0D;IAC1D,wCAAwC;IACxC,MAAM,QAAQ,GAAG,WAAW,CAAC,CAAC,OAAe,EAAE,EAAE;QAC/C,MAAM,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC;QAC5B,IAAI,CAAC,CAAC;YAAE,OAAO;QACf,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC5D,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,YAAY,GAAG,WAAW,CAC9B,CAAC,GAAW,EAAE,EAAE;QACd,MAAM,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC;QAC5B,IAAI,CAAC,CAAC;YAAE,OAAO;QACf,gDAAgD;QAChD,0CAA0C;QAC1C,QAAQ,CAAC,CAAC,CAAC,OAAO,GAAG,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IACxD,CAAC,EACD,CAAC,QAAQ,CAAC,CACX,CAAC;IAEF,MAAM,YAAY,GAAG,WAAW,CAC9B,CAAC,GAAW,EAAE,EAAE;QACd,MAAM,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC;QAC5B,IAAI,CAAC,CAAC;YAAE,OAAO;QACf,QAAQ,CAAC,CAAC,CAAC,OAAO,GAAG,GAAG,CAAC,CAAC;IAC5B,CAAC,EACD,CAAC,QAAQ,CAAC,CACX,CAAC;IAEF,MAAM,aAAa,GAAG,WAAW,CAC/B,CAAC,GAAW,EAAE,EAAE;QACd,MAAM,CAAC,GAAG,SAAS,CAAC,OAAO,CAAC;QAC5B,IAAI,CAAC,CAAC;YAAE,OAAO;QACf,QAAQ,CAAC,CAAC,CAAC,OAAO,GAAG,GAAG,GAAG,UAAU,CAAC,CAAC;IACzC,CAAC,EACD,CAAC,QAAQ,CAAC,CACX,CAAC;IAEF,MAAM,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IAE/D,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC;AAC3F,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Notice } from "./notice.js";
|
|
2
|
+
export interface InputEditingDeps {
|
|
3
|
+
/** 初始草稿(`/back` 撤回后重挂载用的预填内容),缺省空 */
|
|
4
|
+
initialText?: string;
|
|
5
|
+
/** 通知出口(暂存 / 取回草稿的提示,走通知通道而不是转录) */
|
|
6
|
+
notify: (text: string, tone?: Notice["tone"]) => void;
|
|
7
|
+
}
|
|
8
|
+
export interface InputEditing {
|
|
9
|
+
/** 当前草稿 */
|
|
10
|
+
input: string;
|
|
11
|
+
/** 光标位置:以字素簇计的索引(0..字数),可落在末尾之后一格 */
|
|
12
|
+
cursor: number;
|
|
13
|
+
/** 整体替换草稿(光标移到末尾) */
|
|
14
|
+
setInputText: (text: string) => void;
|
|
15
|
+
insertAtCursor: (text: string) => void;
|
|
16
|
+
deleteBeforeCursor: () => void;
|
|
17
|
+
deleteAfterCursor: () => void;
|
|
18
|
+
deleteWordBeforeCursor: () => void;
|
|
19
|
+
deleteWordAfterCursor: () => void;
|
|
20
|
+
deleteToStartOfLine: () => void;
|
|
21
|
+
deleteToEndOfLine: () => void;
|
|
22
|
+
/** Ctrl+W:删到前一空白(标点不切分) */
|
|
23
|
+
deletePrevWhitespace: () => void;
|
|
24
|
+
/** Ctrl+Y / Alt+Y:取回 kill ring 里的一段;`advance` 为真时循环到更早的一段 */
|
|
25
|
+
yank: (advance: boolean) => void;
|
|
26
|
+
/** Ctrl+_:撤销上一次编辑 */
|
|
27
|
+
undoEdit: () => void;
|
|
28
|
+
/** Ctrl+S:暂存草稿 / 取回草稿 */
|
|
29
|
+
toggleStash: () => void;
|
|
30
|
+
moveCursor: (delta: number) => void;
|
|
31
|
+
moveCursorToLineStart: () => void;
|
|
32
|
+
moveCursorToLineEnd: () => void;
|
|
33
|
+
/** 按词左右移动:`dir` 为 -1 往前、1 往后 */
|
|
34
|
+
moveCursorByWord: (dir: -1 | 1) => void;
|
|
35
|
+
/** 历史:上一条 / 下一条(没有可翻的时候自动空转) */
|
|
36
|
+
historyPrev: () => void;
|
|
37
|
+
historyNext: () => void;
|
|
38
|
+
/** 提交时调用:清空草稿并退出历史浏览(历史游标复位到底部) */
|
|
39
|
+
clearDraft: () => void;
|
|
40
|
+
/** 提交成功时调用:记进历史并把历史游标复位到底部 */
|
|
41
|
+
rememberSubmitted: (text: string) => void;
|
|
42
|
+
}
|
|
43
|
+
export declare function useInputEditing({ initialText, notify }: InputEditingDeps): InputEditing;
|
|
44
|
+
//# sourceMappingURL=use-input-editing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-input-editing.d.ts","sourceRoot":"","sources":["../../../src/tui/screens/use-input-editing.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,WAAW,gBAAgB;IAC/B,qCAAqC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oCAAoC;IACpC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;CACvD;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW;IACX,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,kBAAkB,EAAE,MAAM,IAAI,CAAC;IAC/B,iBAAiB,EAAE,MAAM,IAAI,CAAC;IAC9B,sBAAsB,EAAE,MAAM,IAAI,CAAC;IACnC,qBAAqB,EAAE,MAAM,IAAI,CAAC;IAClC,mBAAmB,EAAE,MAAM,IAAI,CAAC;IAChC,iBAAiB,EAAE,MAAM,IAAI,CAAC;IAC9B,2BAA2B;IAC3B,oBAAoB,EAAE,MAAM,IAAI,CAAC;IACjC,6DAA6D;IAC7D,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACjC,qBAAqB;IACrB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,yBAAyB;IACzB,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,qBAAqB,EAAE,MAAM,IAAI,CAAC;IAClC,mBAAmB,EAAE,MAAM,IAAI,CAAC;IAChC,gCAAgC;IAChC,gBAAgB,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC;IACxC,gCAAgC;IAChC,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,mCAAmC;IACnC,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,8BAA8B;IAC9B,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AAED,wBAAgB,eAAe,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,gBAAgB,GAAG,YAAY,CAoLvF"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// 输入编辑 —— 草稿文本与光标、历史、kill ring、撤销栈、暂存区的唯一实现
|
|
2
|
+
//
|
|
3
|
+
// 从 `chat.tsx` 抽出来的理由:这一整块(`input` / `cursor` / `history` / kill ring /
|
|
4
|
+
// undo / stash + 20 个编辑动作)是**纯编辑器语义**,与「对话屏」毫无关系,却和会话、
|
|
5
|
+
// 向导、渲染挤在同一个组件里 —— 于是既不能单测,也每次都被误认为「对话的一部分」。
|
|
6
|
+
//
|
|
7
|
+
// 两条贯穿全篇的约定:
|
|
8
|
+
//
|
|
9
|
+
// 1. **按字素簇编辑**:所有位置计算都走 `graphemes()`。`❤️` 是 2 个 code point、
|
|
10
|
+
// `👨👩👧` 是 5 个,但它们都是**一个**字符 —— 按 code point 删会切出半个字素簇。
|
|
11
|
+
// 2. **纯函数算、hook 只套用**:具体「删哪个词、删到哪」在 `input.ts` /
|
|
12
|
+
// `input-editing.ts` 里(有独立测试),这里只负责「拿到结果 → 更新状态 + 记撤销」。
|
|
13
|
+
import { useRef, useState } from "react";
|
|
14
|
+
import { deleteToLineEnd, deleteToLineStart, deleteWordLeft, deleteWordRight, wordLeft, wordRight, } from "./input.js";
|
|
15
|
+
import { deleteToPrevWhitespace, killedText, killAt, popUndo, pushKill, pushUndo, } from "../input-editing.js";
|
|
16
|
+
import { graphemes } from "../text.js";
|
|
17
|
+
import { t } from "../../i18n/index.js";
|
|
18
|
+
export function useInputEditing({ initialText, notify }) {
|
|
19
|
+
const [input, setInput] = useState(initialText ?? "");
|
|
20
|
+
const [cursor, setCursor] = useState(graphemes(initialText ?? "").length);
|
|
21
|
+
const [history, setHistory] = useState([]);
|
|
22
|
+
/** 历史游标:-1 = 不在浏览历史(当前是草稿) */
|
|
23
|
+
const historyIndex = useRef(-1);
|
|
24
|
+
/** kill ring:Ctrl+W/U/K 删掉的文本,Ctrl+Y 取回、Alt+Y 循环 */
|
|
25
|
+
const killRingRef = useRef([]);
|
|
26
|
+
const yankIndexRef = useRef(-1);
|
|
27
|
+
/** 编辑撤销栈(Ctrl+_) */
|
|
28
|
+
const undoStackRef = useRef([]);
|
|
29
|
+
/** 暂存区(Ctrl+S) */
|
|
30
|
+
const stashRef = useRef(null);
|
|
31
|
+
const setInputText = (text) => {
|
|
32
|
+
setInput(text);
|
|
33
|
+
setCursor(graphemes(text).length);
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* 取「当前文本 + 钳制后的光标」,所有编辑动作的统一起点。
|
|
37
|
+
*
|
|
38
|
+
* 钳制防的**不是**「cursor 越界」——`cursor` 的每个写入点都已保证落在 `[0, len]`;
|
|
39
|
+
* 防的是**文本与光标不同步**:`input` 与 `cursor` 是两个 state,任何一处只改其一
|
|
40
|
+
* (或将来新增编辑动作忘了同步)都会让 `cp > chars.length` 成立,那时 `slice`
|
|
41
|
+
* 会静默切少、`cp - 1` 会变负。钳一下把后果限制在「本次编辑落点不对」。
|
|
42
|
+
*/
|
|
43
|
+
const clampCursor = () => {
|
|
44
|
+
const chars = graphemes(input);
|
|
45
|
+
return { chars, cp: Math.max(0, Math.min(cursor, chars.length)) };
|
|
46
|
+
};
|
|
47
|
+
const insertAtCursor = (text) => {
|
|
48
|
+
const { chars, cp } = clampCursor();
|
|
49
|
+
setInput(chars.slice(0, cp).join("") + text + chars.slice(cp).join(""));
|
|
50
|
+
setCursor(cp + graphemes(text).length);
|
|
51
|
+
};
|
|
52
|
+
const deleteBeforeCursor = () => {
|
|
53
|
+
const { chars, cp } = clampCursor();
|
|
54
|
+
if (cp === 0)
|
|
55
|
+
return;
|
|
56
|
+
setInput(chars.slice(0, cp - 1).join("") + chars.slice(cp).join(""));
|
|
57
|
+
setCursor(cp - 1);
|
|
58
|
+
};
|
|
59
|
+
const deleteAfterCursor = () => {
|
|
60
|
+
const { chars, cp } = clampCursor();
|
|
61
|
+
if (cp >= chars.length)
|
|
62
|
+
return;
|
|
63
|
+
setInput(chars.slice(0, cp).join("") + chars.slice(cp + 1).join(""));
|
|
64
|
+
};
|
|
65
|
+
/** 套用纯函数算好的「文本 + 光标」结果(无变化则不触发重渲染,也不进撤销栈) */
|
|
66
|
+
const applyEdit = (r) => {
|
|
67
|
+
if (r.text === input && r.cursor === cursor)
|
|
68
|
+
return;
|
|
69
|
+
undoStackRef.current = pushUndo(undoStackRef.current, { text: input, cursor });
|
|
70
|
+
setInput(r.text);
|
|
71
|
+
setCursor(r.cursor);
|
|
72
|
+
};
|
|
73
|
+
/** 删除类编辑:额外把删掉的文本放进 kill ring(Ctrl+Y 可取回) */
|
|
74
|
+
const applyKill = (r) => {
|
|
75
|
+
if (r.text === input && r.cursor === cursor)
|
|
76
|
+
return;
|
|
77
|
+
killRingRef.current = pushKill(killRingRef.current, killedText(input, r.text));
|
|
78
|
+
yankIndexRef.current = -1;
|
|
79
|
+
applyEdit(r);
|
|
80
|
+
};
|
|
81
|
+
const yank = (advance) => {
|
|
82
|
+
const ring = killRingRef.current;
|
|
83
|
+
if (ring.length === 0)
|
|
84
|
+
return;
|
|
85
|
+
const next = advance ? yankIndexRef.current + 1 : 0;
|
|
86
|
+
// 只用 `killAt` 的 undefined 做越界守卫(环已到头),不再先判一遍 `next >= ring.length`
|
|
87
|
+
const text = killAt(ring, next);
|
|
88
|
+
if (text === undefined)
|
|
89
|
+
return;
|
|
90
|
+
yankIndexRef.current = next;
|
|
91
|
+
insertAtCursor(text);
|
|
92
|
+
};
|
|
93
|
+
const undoEdit = () => {
|
|
94
|
+
const popped = popUndo(undoStackRef.current);
|
|
95
|
+
if (!popped)
|
|
96
|
+
return;
|
|
97
|
+
undoStackRef.current = popped.rest;
|
|
98
|
+
setInput(popped.state.text);
|
|
99
|
+
setCursor(popped.state.cursor);
|
|
100
|
+
};
|
|
101
|
+
const toggleStash = () => {
|
|
102
|
+
if (input.length > 0) {
|
|
103
|
+
stashRef.current = { text: input, cursor };
|
|
104
|
+
setInputText("");
|
|
105
|
+
notify(t("notice.draftSaved"));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const stashed = stashRef.current;
|
|
109
|
+
if (!stashed)
|
|
110
|
+
return;
|
|
111
|
+
stashRef.current = null;
|
|
112
|
+
setInput(stashed.text);
|
|
113
|
+
setCursor(stashed.cursor);
|
|
114
|
+
notify(t("notice.draftRestored"));
|
|
115
|
+
};
|
|
116
|
+
const moveCursor = (delta) => {
|
|
117
|
+
const len = graphemes(input).length;
|
|
118
|
+
setCursor((c) => Math.max(0, Math.min(len, c + delta)));
|
|
119
|
+
};
|
|
120
|
+
const moveCursorByWord = (dir) => {
|
|
121
|
+
setCursor(dir === -1 ? wordLeft(input, cursor) : wordRight(input, cursor));
|
|
122
|
+
};
|
|
123
|
+
const moveCursorToLineStart = () => {
|
|
124
|
+
const { chars, cp } = clampCursor();
|
|
125
|
+
let i = cp;
|
|
126
|
+
while (i > 0 && chars[i - 1] !== "\n")
|
|
127
|
+
i -= 1;
|
|
128
|
+
setCursor(i);
|
|
129
|
+
};
|
|
130
|
+
const moveCursorToLineEnd = () => {
|
|
131
|
+
const { chars, cp } = clampCursor();
|
|
132
|
+
let i = cp;
|
|
133
|
+
while (i < chars.length && chars[i] !== "\n")
|
|
134
|
+
i += 1;
|
|
135
|
+
setCursor(i);
|
|
136
|
+
};
|
|
137
|
+
// 历史浏览:「上一条」从最新开始往回走,「下一条」走到底就回到空草稿。
|
|
138
|
+
// 上下两处按键(Ctrl+P/N 与 ↑/↓)共用这一对,不再各写一遍。
|
|
139
|
+
const historyPrev = () => {
|
|
140
|
+
if (history.length === 0)
|
|
141
|
+
return;
|
|
142
|
+
const idx = historyIndex.current < 0 ? history.length - 1 : Math.max(0, historyIndex.current - 1);
|
|
143
|
+
historyIndex.current = idx;
|
|
144
|
+
setInputText(history[idx]);
|
|
145
|
+
};
|
|
146
|
+
const historyNext = () => {
|
|
147
|
+
if (historyIndex.current < 0)
|
|
148
|
+
return;
|
|
149
|
+
const idx = historyIndex.current + 1;
|
|
150
|
+
if (idx >= history.length) {
|
|
151
|
+
historyIndex.current = -1;
|
|
152
|
+
setInputText("");
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
historyIndex.current = idx;
|
|
156
|
+
setInputText(history[idx]);
|
|
157
|
+
};
|
|
158
|
+
const clearDraft = () => {
|
|
159
|
+
setInput("");
|
|
160
|
+
setCursor(0);
|
|
161
|
+
historyIndex.current = -1;
|
|
162
|
+
};
|
|
163
|
+
const rememberSubmitted = (text) => {
|
|
164
|
+
setHistory((h) => [...h, text]);
|
|
165
|
+
historyIndex.current = -1;
|
|
166
|
+
};
|
|
167
|
+
return {
|
|
168
|
+
input,
|
|
169
|
+
cursor,
|
|
170
|
+
setInputText,
|
|
171
|
+
insertAtCursor,
|
|
172
|
+
deleteBeforeCursor,
|
|
173
|
+
deleteAfterCursor,
|
|
174
|
+
deleteWordBeforeCursor: () => applyKill(deleteWordLeft(input, cursor)),
|
|
175
|
+
deleteWordAfterCursor: () => applyKill(deleteWordRight(input, cursor)),
|
|
176
|
+
deleteToStartOfLine: () => applyKill(deleteToLineStart(input, cursor)),
|
|
177
|
+
deleteToEndOfLine: () => applyKill(deleteToLineEnd(input, cursor)),
|
|
178
|
+
deletePrevWhitespace: () => applyKill(deleteToPrevWhitespace(input, cursor)),
|
|
179
|
+
yank,
|
|
180
|
+
undoEdit,
|
|
181
|
+
toggleStash,
|
|
182
|
+
moveCursor,
|
|
183
|
+
moveCursorToLineStart,
|
|
184
|
+
moveCursorToLineEnd,
|
|
185
|
+
moveCursorByWord,
|
|
186
|
+
historyPrev,
|
|
187
|
+
historyNext,
|
|
188
|
+
clearDraft,
|
|
189
|
+
rememberSubmitted,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=use-input-editing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-input-editing.js","sourceRoot":"","sources":["../../../src/tui/screens/use-input-editing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Dispatch, type SetStateAction } from "react";
|
|
2
|
+
import type { SelectItem } from "../components.js";
|
|
3
|
+
/** 确认框请求:`question` 上屏,用户作答后由 `resolve` 结算 */
|
|
4
|
+
export interface ConfirmState {
|
|
5
|
+
question: string;
|
|
6
|
+
resolve: (value: boolean) => void;
|
|
7
|
+
}
|
|
8
|
+
/** 选择器请求:`items` 上屏,用户选中 / 取消后由 `resolve` 结算(取消为 `null`) */
|
|
9
|
+
export interface PickerState {
|
|
10
|
+
title: string;
|
|
11
|
+
items: SelectItem<string>[];
|
|
12
|
+
resolve: (value: string | null) => void;
|
|
13
|
+
}
|
|
14
|
+
export interface ChatOverlays {
|
|
15
|
+
confirmState: ConfirmState | null;
|
|
16
|
+
pickerState: PickerState | null;
|
|
17
|
+
/** 命令层推送覆盖层用(`runCommand` / `chooseHandoffMode` 的入参类型) */
|
|
18
|
+
setConfirmState: Dispatch<SetStateAction<ConfirmState | null>>;
|
|
19
|
+
setPickerState: Dispatch<SetStateAction<PickerState | null>>;
|
|
20
|
+
/** 作答:结算 Promise 并关掉覆盖层(无覆盖层时为空操作) */
|
|
21
|
+
resolveConfirm: (value: boolean) => void;
|
|
22
|
+
resolvePicker: (value: string | null) => void;
|
|
23
|
+
}
|
|
24
|
+
export declare function useChatOverlays(): ChatOverlays;
|
|
25
|
+
//# sourceMappingURL=use-overlays.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-overlays.d.ts","sourceRoot":"","sources":["../../../src/tui/screens/use-overlays.ts"],"names":[],"mappings":"AAWA,OAAO,EAAyB,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,OAAO,CAAC;AAClF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,8CAA8C;AAC9C,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACnC;AAED,4DAA4D;AAC5D,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;IAC5B,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;CACzC;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,0DAA0D;IAC1D,eAAe,EAAE,QAAQ,CAAC,cAAc,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC,CAAC;IAC/D,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC,CAAC;IAC7D,sCAAsC;IACtC,cAAc,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;CAC/C;AAED,wBAAgB,eAAe,IAAI,YAAY,CAqB9C"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// 覆盖层(确认框 / 选择器)—— 「弹一个、答一个、再关掉」的唯一实现
|
|
2
|
+
//
|
|
3
|
+
// 两种覆盖层共用同一套契约:调用方给一个 `resolve` 回调,拿到一个 Promise,
|
|
4
|
+
// **用户在覆盖层上作答后 Promise 才结算**。这正是命令处理器能写成
|
|
5
|
+
// `const ok = await confirm(...)` 的原因。
|
|
6
|
+
//
|
|
7
|
+
// 从 `chat.tsx` 抽出来的理由:这套 `resolve` 生命周期被埋在 God Component 里,
|
|
8
|
+
// 既没法单测(要从键盘打到命令层才能碰到),也容易被下一个人的 `setState` 写出
|
|
9
|
+
// 「先清状态、后调 resolve」的竞态。抽出来后:
|
|
10
|
+
// · 关状态与调 resolve 在**同一次**函数式更新里完成,不会读到过期状态;
|
|
11
|
+
// · 已经是 `null` 时重复 resolve 是安全空操作(Esc 连按不会炸)。
|
|
12
|
+
import { useCallback, useState } from "react";
|
|
13
|
+
export function useChatOverlays() {
|
|
14
|
+
const [confirmState, setConfirmState] = useState(null);
|
|
15
|
+
const [pickerState, setPickerState] = useState(null);
|
|
16
|
+
// 用函数式更新而不是闭包里的 `confirmState`:连按两下 Esc / 两个键同一帧到达时,
|
|
17
|
+
// 第二次更新仍能看到「已被第一次清掉」的真实状态,不会重复 resolve。
|
|
18
|
+
const resolveConfirm = useCallback((value) => {
|
|
19
|
+
setConfirmState((cs) => {
|
|
20
|
+
cs?.resolve(value);
|
|
21
|
+
return null;
|
|
22
|
+
});
|
|
23
|
+
}, []);
|
|
24
|
+
const resolvePicker = useCallback((value) => {
|
|
25
|
+
setPickerState((ps) => {
|
|
26
|
+
ps?.resolve(value);
|
|
27
|
+
return null;
|
|
28
|
+
});
|
|
29
|
+
}, []);
|
|
30
|
+
return { confirmState, pickerState, setConfirmState, setPickerState, resolveConfirm, resolvePicker };
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=use-overlays.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-overlays.js","sourceRoot":"","sources":["../../../src/tui/screens/use-overlays.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,iDAAiD;AACjD,yCAAyC;AACzC,uCAAuC;AACvC,EAAE;AACF,4DAA4D;AAC5D,+CAA+C;AAC/C,6BAA6B;AAC7B,+CAA+C;AAC/C,gDAAgD;AAChD,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAsC,MAAM,OAAO,CAAC;AA2BlF,MAAM,UAAU,eAAe;IAC7B,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,QAAQ,CAAsB,IAAI,CAAC,CAAC;IAC5E,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAqB,IAAI,CAAC,CAAC;IAEzE,qDAAqD;IACrD,wCAAwC;IACxC,MAAM,cAAc,GAAG,WAAW,CAAC,CAAC,KAAc,EAAE,EAAE;QACpD,eAAe,CAAC,CAAC,EAAE,EAAE,EAAE;YACrB,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;YACnB,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,aAAa,GAAG,WAAW,CAAC,CAAC,KAAoB,EAAE,EAAE;QACzD,cAAc,CAAC,CAAC,EAAE,EAAE,EAAE;YACpB,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;YACnB,OAAO,IAAI,CAAC;QACd,CAAC,CAAC,CAAC;IACL,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,cAAc,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC;AACvG,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { AppConfig, ReasoningPresets } from "@ai-zen/agents-sdk";
|
|
2
|
+
import { type AgentStore } from "../../agents-store.js";
|
|
3
|
+
import type { McpSnapshot, WizardCloseResult, WizardStep } from "../config-wizard/index.js";
|
|
4
|
+
import type { Notice } from "./notice.js";
|
|
5
|
+
export interface WizardHostDeps {
|
|
6
|
+
/** 当前模型 id:`/key` 直达它所用端点的密钥;也是「当前端点是否被改动」的比较基准 */
|
|
7
|
+
modelId: string;
|
|
8
|
+
/** 通知出口(读写失败、变更摘要都从这里出去,不进食转录) */
|
|
9
|
+
notify: (text: string, tone?: Notice["tone"]) => void;
|
|
10
|
+
/** 会话是否已建好(未建好时改配置只能重启) */
|
|
11
|
+
hasSession: () => boolean;
|
|
12
|
+
/** 会话还没建好就改了配置 → 只能重启进程让新配置生效 */
|
|
13
|
+
restart: () => void;
|
|
14
|
+
/** 改了当前端点 / MCP / Agent 定义 → 重载会话(消息保留) */
|
|
15
|
+
reloadSession: () => void;
|
|
16
|
+
}
|
|
17
|
+
export interface WizardHost {
|
|
18
|
+
wizardStep: WizardStep | null;
|
|
19
|
+
wizardConfig: AppConfig | null;
|
|
20
|
+
wizardPresets: ReasoningPresets;
|
|
21
|
+
wizardAgents: {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
}[];
|
|
25
|
+
wizardMcp: McpSnapshot | null;
|
|
26
|
+
wizardAgentStore: AgentStore | null;
|
|
27
|
+
/** 打开向导:先读齐快照再接管整屏(读失败则只报错、不进向导) */
|
|
28
|
+
openWizard: (step: WizardStep) => Promise<void>;
|
|
29
|
+
/** `/key`:直达当前模型所用端点的 API Key 编辑步骤 */
|
|
30
|
+
openKeyEditor: () => Promise<void>;
|
|
31
|
+
/** 向导关闭:渲染变更摘要,并让改动生效 */
|
|
32
|
+
closeWizard: (result: WizardCloseResult) => void;
|
|
33
|
+
}
|
|
34
|
+
export declare function useWizardHost({ modelId, notify, hasSession, restart, reloadSession, }: WizardHostDeps): WizardHost;
|
|
35
|
+
//# sourceMappingURL=use-wizard-host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-wizard-host.d.ts","sourceRoot":"","sources":["../../../src/tui/screens/use-wizard-host.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtE,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAExE,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5F,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,kCAAkC;IAClC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC;IACtD,2BAA2B;IAC3B,UAAU,EAAE,MAAM,OAAO,CAAC;IAC1B,iCAAiC;IACjC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,2CAA2C;IAC3C,aAAa,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,SAAS,GAAG,IAAI,CAAC;IAC/B,aAAa,EAAE,gBAAgB,CAAC;IAChC,YAAY,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC7C,SAAS,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9B,gBAAgB,EAAE,UAAU,GAAG,IAAI,CAAC;IACpC,oCAAoC;IACpC,UAAU,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,sCAAsC;IACtC,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,yBAAyB;IACzB,WAAW,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAClD;AAED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,MAAM,EACN,UAAU,EACV,OAAO,EACP,aAAa,GACd,EAAE,cAAc,GAAG,UAAU,CA0F7B"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// 配置向导宿主 —— 「打开向导 / 读齐快照 / 关闭后让改动生效」的唯一实现
|
|
2
|
+
//
|
|
3
|
+
// 从 `chat.tsx` 抽出来的理由:向导本身(`config-wizard/`)是独立状态机,但**宿主**
|
|
4
|
+
// 的活儿散落在对话屏里 —— 6 个 `useState`、3 个入口函数,还要顺手记「当前模型用的是
|
|
5
|
+
// 哪个端点」以便关闭时判断要不要重建会话。这些既不属于「对话」,也没法单测(要一路
|
|
6
|
+
// 从键盘打到向导关闭才能碰到)。
|
|
7
|
+
//
|
|
8
|
+
// 依赖方向是**注入**而非 import 会话:宿主要做的事(通知、重启、重载会话)都通过
|
|
9
|
+
// `WizardHostDeps` 传进来,于是 hook 可以在测试里用假依赖跑完整生命周期。
|
|
10
|
+
import { useRef, useState } from "react";
|
|
11
|
+
import { readConfig, readMcpConfig, readReasoningPresets } from "../../config.js";
|
|
12
|
+
import { readAgentStore } from "../../agents-store.js";
|
|
13
|
+
import { resolveCredential } from "../../config-editor.js";
|
|
14
|
+
import { t } from "../../i18n/index.js";
|
|
15
|
+
export function useWizardHost({ modelId, notify, hasSession, restart, reloadSession, }) {
|
|
16
|
+
const [wizardStep, setWizardStep] = useState(null);
|
|
17
|
+
const [wizardConfig, setWizardConfig] = useState(null);
|
|
18
|
+
const [wizardPresets, setWizardPresets] = useState({});
|
|
19
|
+
const [wizardAgents, setWizardAgents] = useState([]);
|
|
20
|
+
const [wizardMcp, setWizardMcp] = useState(null);
|
|
21
|
+
const [wizardAgentStore, setWizardAgentStore] = useState(null);
|
|
22
|
+
/**
|
|
23
|
+
* 打开向导时当前模型所用的端点 id —— 关闭时据此判断「当前端点有没有被改」。
|
|
24
|
+
*
|
|
25
|
+
* 用 ref 而不是 state:它只在 `openWizard` / `closeWizard` 之间传递,不上屏,
|
|
26
|
+
* 放 state 会多一次无意义渲染。
|
|
27
|
+
*/
|
|
28
|
+
const currentEndpointRef = useRef(null);
|
|
29
|
+
const openWizard = async (step) => {
|
|
30
|
+
try {
|
|
31
|
+
const config = await readConfig();
|
|
32
|
+
currentEndpointRef.current = resolveCredential(config, modelId)?.endpointId ?? null;
|
|
33
|
+
const agentStore = await readAgentStore();
|
|
34
|
+
setWizardConfig(config);
|
|
35
|
+
setWizardPresets(await readReasoningPresets());
|
|
36
|
+
setWizardAgents(agentStore.agents.map((agent) => ({ id: agent.id, name: agent.name || agent.id })));
|
|
37
|
+
setWizardAgentStore(agentStore);
|
|
38
|
+
setWizardMcp({ global: await readMcpConfig("global"), project: await readMcpConfig("project") });
|
|
39
|
+
setWizardStep(step);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
notify(t("chat.configReadFailed", { error: error?.message ?? error }), "error");
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
const openKeyEditor = async () => {
|
|
46
|
+
try {
|
|
47
|
+
const config = await readConfig();
|
|
48
|
+
const credential = resolveCredential(config, modelId);
|
|
49
|
+
if (!credential) {
|
|
50
|
+
notify(t("chat.modelNoEndpoint", { id: modelId }), "error");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
currentEndpointRef.current = credential.endpointId;
|
|
54
|
+
setWizardConfig(config);
|
|
55
|
+
setWizardStep({ kind: "edit", field: "apiKey", endpointId: credential.endpointId });
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
notify(t("chat.configReadFailed", { error: error?.message ?? error }), "error");
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
/** 向导关闭:渲染变更摘要;若改到当前端点 / MCP / Agent 定义则重建会话(消息保留) */
|
|
62
|
+
const closeWizard = (result) => {
|
|
63
|
+
setWizardStep(null);
|
|
64
|
+
setWizardConfig(null);
|
|
65
|
+
setWizardMcp(null);
|
|
66
|
+
setWizardAgentStore(null);
|
|
67
|
+
// 同一批摘要合成**一条**(多行):既能全部显示,又只占通知队列的一个槽位。
|
|
68
|
+
// 逐条 push 在单槽时代只剩最后一条,在队列时代则会挤掉更早的进度提示。
|
|
69
|
+
const { summary } = result;
|
|
70
|
+
if (summary.length === 1) {
|
|
71
|
+
notify(t("chat.configUpdated", { line: summary[0] }));
|
|
72
|
+
}
|
|
73
|
+
else if (summary.length > 1) {
|
|
74
|
+
// 子项缩进 = 一个汉字宽度(两个半角空格),不加 `·` 之类的项目符号:
|
|
75
|
+
// 通知正文本身就左对齐于行首,子项只需要一点层级差,符号是多余装饰。
|
|
76
|
+
notify(t("chat.configUpdatedMulti", { lines: summary.map((line) => ` ${line}`).join("\n") }));
|
|
77
|
+
}
|
|
78
|
+
if (!result.dirty)
|
|
79
|
+
return;
|
|
80
|
+
// 会话都没建好 → 只能重启让新配置生效。
|
|
81
|
+
if (!hasSession()) {
|
|
82
|
+
restart();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// 只有「当前端点 / MCP / Agent 定义」变了才需要重建会话;改的是别的模型、
|
|
86
|
+
// 别的端点的参数,当前会话不受影响,不该白重连一次。
|
|
87
|
+
const endpointChanged = !!currentEndpointRef.current && result.changedEndpointIds.includes(currentEndpointRef.current);
|
|
88
|
+
if (endpointChanged || result.mcpChanged || result.agentsChanged)
|
|
89
|
+
reloadSession();
|
|
90
|
+
};
|
|
91
|
+
return {
|
|
92
|
+
wizardStep,
|
|
93
|
+
wizardConfig,
|
|
94
|
+
wizardPresets,
|
|
95
|
+
wizardAgents,
|
|
96
|
+
wizardMcp,
|
|
97
|
+
wizardAgentStore,
|
|
98
|
+
openWizard,
|
|
99
|
+
openKeyEditor,
|
|
100
|
+
closeWizard,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=use-wizard-host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-wizard-host.js","sourceRoot":"","sources":["../../../src/tui/screens/use-wizard-host.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 状态栏的折行 —— 输入是「按优先级排好的信息块」,输出是「确定的若干行」
|
|
3
|
+
*
|
|
4
|
+
* 为什么不用 Ink 自己的 wrap:状态栏高度必须是**确定值**,`computeChatLayout` 要靠它
|
|
5
|
+
* 算出对话区还能占几行(整帧高度恒等于终端行数,输入框才吸得住底)。让 Ink 在渲染时
|
|
6
|
+
* 折行,行数只有渲染完才知道,布局预算就会算错——轻则顶部留白多一行,重则把输入框
|
|
7
|
+
* 顶出屏幕。所以这里先在纯逻辑里折好,渲染层只负责按行铺颜色。
|
|
8
|
+
*
|
|
9
|
+
* 折行以「块」为单位(见 `packSegments`):身份、用量、快捷键各是一块,窄屏时整块
|
|
10
|
+
* 顺延到下一行,而不是把一块劈成两半——也不再用 `justify-content: space-between` 把
|
|
11
|
+
* 两侧各切一半(左边「思考 高」被吃掉、右边提示只剩半截)。
|
|
12
|
+
*/
|
|
13
|
+
/** 一段带样式的文本(颜色由调用方给,这里只管切分) */
|
|
14
|
+
export interface StatusSegment {
|
|
15
|
+
text: string;
|
|
16
|
+
color?: string;
|
|
17
|
+
bold?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 把片段流按显示宽度折成若干行。
|
|
21
|
+
*
|
|
22
|
+
* 断行策略与 `wrapText` 一致:优先在**空格**处断(英文单词不被劈开),没有空格可断
|
|
23
|
+
* 时按字符硬断(CJK 本来就能任意断行)。折行后的行首空格会被吃掉,避免出现
|
|
24
|
+
* 「第二行无故缩进两格」。
|
|
25
|
+
*/
|
|
26
|
+
export declare function wrapSegments(segments: StatusSegment[], width: number): StatusSegment[][];
|
|
27
|
+
/** 片段流的总显示宽度(宽字符算 2 列) */
|
|
28
|
+
export declare function segmentsWidth(segments: StatusSegment[]): number;
|
|
29
|
+
/**
|
|
30
|
+
* 以**块**为单位装行:能塞进当前行剩余宽度就并排(用 `separator` 连起来),
|
|
31
|
+
* 塞不下就整块挪到下一行;只有单块本身就比一整行还宽时,才退化成块内折行
|
|
32
|
+
* (`wrapSegments`),此时优先在空格 / CJK 边界断,尽量不劈开英文单词。
|
|
33
|
+
*
|
|
34
|
+
* 这样「换行时一块占一行」是默认行为:块是原子的排版单位,行的边界只会落在块边界上。
|
|
35
|
+
*/
|
|
36
|
+
export declare function packSegments(blocks: StatusSegment[][], width: number, separator?: StatusSegment[]): StatusSegment[][];
|
|
37
|
+
//# sourceMappingURL=status-segments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status-segments.d.ts","sourceRoot":"","sources":["../../src/tui/status-segments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,+BAA+B;AAC/B,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAwCD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,aAAa,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,EAAE,EAAE,CAyDxF;AAED,0BAA0B;AAC1B,wBAAgB,aAAa,CAAC,QAAQ,EAAE,aAAa,EAAE,GAAG,MAAM,CAM/D;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,aAAa,EAAE,EAAE,EACzB,KAAK,EAAE,MAAM,EACb,SAAS,GAAE,aAAa,EAAsB,GAC7C,aAAa,EAAE,EAAE,CAwCnB"}
|