@ai-zen/cli 1.0.0-alpha.20 → 1.0.0-alpha.23
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 +205 -0
- package/README.md +45 -17
- package/README.zh.md +45 -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 +223 -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 +227 -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/sub-agent-guard-plugin.d.ts +5 -2
- package/dist/sub-agent-guard-plugin.d.ts.map +1 -1
- package/dist/sub-agent-guard-plugin.js +5 -2
- package/dist/sub-agent-guard-plugin.js.map +1 -1
- package/dist/tui/chat-session.d.ts +73 -0
- package/dist/tui/chat-session.d.ts.map +1 -1
- package/dist/tui/chat-session.js +167 -33
- 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/agent-tabs.d.ts +53 -0
- package/dist/tui/screens/agent-tabs.d.ts.map +1 -0
- package/dist/tui/screens/agent-tabs.js +81 -0
- package/dist/tui/screens/agent-tabs.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 +263 -406
- 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 +3 -1
- package/dist/tui/screens/index.d.ts.map +1 -1
- package/dist/tui/screens/index.js +2 -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 +13 -1
- package/dist/tui/screens/input.d.ts.map +1 -1
- package/dist/tui/screens/input.js +26 -12
- package/dist/tui/screens/input.js.map +1 -1
- package/dist/tui/screens/layout.d.ts +15 -0
- package/dist/tui/screens/layout.d.ts.map +1 -1
- package/dist/tui/screens/layout.js +14 -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/theme.d.ts +2 -0
- package/dist/tui/theme.d.ts.map +1 -1
- package/dist/tui/theme.js +2 -0
- package/dist/tui/theme.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
package/dist/tui/screens/chat.js
CHANGED
|
@@ -3,28 +3,35 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useEffect, useRef, useState } from "react";
|
|
4
4
|
import { Box, Text, useApp, useCursor, useInput, useWindowSize } from "ink";
|
|
5
5
|
import { keyDebug } from "../key-debug.js";
|
|
6
|
-
import {
|
|
6
|
+
import { phaseLabel } from "../phase.js";
|
|
7
7
|
import { AgentNS } from "@ai-zen/agents-core";
|
|
8
8
|
import { theme } from "../theme.js";
|
|
9
|
-
import { Spinner, StatusBar, SlashMenu } from "../components.js";
|
|
10
|
-
import { layoutInput, usableFrameRows
|
|
9
|
+
import { Spinner, StatusBar, statusBarRows, SlashMenu } from "../components.js";
|
|
10
|
+
import { graphemes, layoutInput, usableFrameRows } from "../text.js";
|
|
11
11
|
import { ChatSession } from "../chat-session.js";
|
|
12
|
-
import { matchCommandHints } from "../../conversation-commands/registry.js";
|
|
13
|
-
import { readConfig,
|
|
12
|
+
import { commandBlocksInput, matchCommandHints, parseCommandInput, } from "../../conversation-commands/registry.js";
|
|
13
|
+
import { readConfig, readReasoningPresets, saveConfig, writeMcpConfig } from "../../config.js";
|
|
14
14
|
import { readAgentStore } from "../../agents-store.js";
|
|
15
|
-
import { resolveCredential } from "../../config-editor.js";
|
|
16
15
|
import { ConfigWizard } from "../config-wizard/index.js";
|
|
17
|
-
import { computeInputCursorPosition
|
|
18
|
-
import {
|
|
19
|
-
import { clampViewTop, computeChatLayout } from "./layout.js";
|
|
16
|
+
import { computeInputCursorPosition } from "./input.js";
|
|
17
|
+
import { AGENT_TABS_LINES, computeChatLayout } from "./layout.js";
|
|
20
18
|
import { FrameBuffer } from "./frame-buffer.js";
|
|
21
19
|
import { LineView } from "./render.js";
|
|
22
20
|
import { messageToLines, transcriptLines } from "./messages-view.js";
|
|
23
21
|
import { PickerOverlay, Confirm } from "./overlays.js";
|
|
22
|
+
import { CursorInputRow } from "./input-row.js";
|
|
24
23
|
import { chooseHandoffMode } from "./handoff-choice.js";
|
|
25
24
|
import { runCommand } from "./chat-commands.js";
|
|
26
|
-
import {
|
|
25
|
+
import { describeEffortView } from "../../reasoning-effort.js";
|
|
26
|
+
import { t } from "../../i18n/index.js";
|
|
27
27
|
import { NoticeChannel, noticeLines } from "./notice.js";
|
|
28
|
+
import { NoticeBar } from "./notice-bar.js";
|
|
29
|
+
import { AgentTabs } from "./agent-tabs.js";
|
|
30
|
+
import { useChatViewport } from "./use-chat-viewport.js";
|
|
31
|
+
import { useChatOverlays } from "./use-overlays.js";
|
|
32
|
+
import { useWizardHost } from "./use-wizard-host.js";
|
|
33
|
+
import { useInputEditing } from "./use-input-editing.js";
|
|
34
|
+
import { handleCtrlDExit, handleScrollKey } from "./common-keys.js";
|
|
28
35
|
/**
|
|
29
36
|
* 计算回车时应提交的文本。
|
|
30
37
|
*
|
|
@@ -44,34 +51,17 @@ export function Chat(props) {
|
|
|
44
51
|
const { exit, suspendTerminal } = useApp();
|
|
45
52
|
const { setCursorPosition } = useCursor();
|
|
46
53
|
const { columns, rows } = useWindowSize();
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const [cursor, setCursor] = useState(Array.from(props.initialInput ?? "").length);
|
|
50
|
-
const [menuIndex, setMenuIndex] = useState(0);
|
|
51
|
-
useEffect(() => {
|
|
52
|
-
setMenuIndex(0);
|
|
53
|
-
}, [input]);
|
|
54
|
-
const [confirmState, setConfirmState] = useState(null);
|
|
55
|
-
const [pickerState, setPickerState] = useState(null);
|
|
56
|
-
/** 配置向导:非 null 时整屏接管(凭据设置 / 配置中心) */
|
|
57
|
-
const [wizardStep, setWizardStep] = useState(null);
|
|
58
|
-
const [wizardConfig, setWizardConfig] = useState(null);
|
|
59
|
-
const [wizardPresets, setWizardPresets] = useState({});
|
|
60
|
-
const [wizardAgents, setWizardAgents] = useState([]);
|
|
61
|
-
const [wizardMcp, setWizardMcp] = useState(null);
|
|
62
|
-
const [wizardAgentStore, setWizardAgentStore] = useState(null);
|
|
63
|
-
const currentEndpointRef = useRef(null);
|
|
64
|
-
const [history, setHistory] = useState([]);
|
|
65
|
-
const historyIndex = useRef(-1);
|
|
54
|
+
/** 覆盖层(确认框 / 选择器):弹窗与 resolve 生命周期见 use-overlays.ts */
|
|
55
|
+
const { confirmState, pickerState, setConfirmState, setPickerState, resolveConfirm, resolvePicker } = useChatOverlays();
|
|
66
56
|
const sessionRef = useRef(null);
|
|
67
57
|
const [bootError, setBootError] = useState(null);
|
|
68
58
|
const [ready, setReady] = useState(false);
|
|
69
|
-
/**
|
|
70
|
-
const
|
|
71
|
-
/** 最近一次布局,供键盘回调读取总行数 / 视口高度(应用内翻页) */
|
|
72
|
-
const layoutRef = useRef(null);
|
|
59
|
+
/** 视口:滚动状态与翻页 / 按行 / 滚轮换算(唯一入口见 use-chat-viewport.ts) */
|
|
60
|
+
const { viewTop, layoutRef, scrollByPage, scrollByLine, scrollByWheel, scrollToBottom } = useChatViewport();
|
|
73
61
|
/** 本轮是否在生成中(spinner / 输入禁用) */
|
|
74
62
|
const [busy, setBusy] = useState(false);
|
|
63
|
+
/** 生成速率快照(token/s):生成中为实时值,结束后保留本轮平均 */
|
|
64
|
+
const [rate, setRate] = useState(null);
|
|
75
65
|
/**
|
|
76
66
|
* 当前模型 id —— **从会话(即 Agent 定义)读**,不作为 prop 传入。
|
|
77
67
|
*
|
|
@@ -85,19 +75,28 @@ export function Chat(props) {
|
|
|
85
75
|
* 真相在配置里(模型的 `defaultParams`),这里只镜像一份供状态栏用:
|
|
86
76
|
* `/think` 写入后由命令层回调刷新,切模型(会话重建)时靠 `modelId` 变化重读。
|
|
87
77
|
*/
|
|
88
|
-
const [
|
|
78
|
+
const [effortView, setEffortView] = useState(null);
|
|
79
|
+
/**
|
|
80
|
+
* 语言切换计数(`/lang`):`t()` 是模块级函数,语言变了已渲染的文案不会自更新,
|
|
81
|
+
* 递增这个计数只为逼 React 重跑一遍渲染函数。
|
|
82
|
+
*/
|
|
83
|
+
const [localeTick, bumpLocale] = useState(0);
|
|
84
|
+
/**
|
|
85
|
+
* IME 光标位置:由光标所在行**渲染后实测**而来(渲染器给的帧内坐标)。
|
|
86
|
+
* 带上 key —— 只有「产生它的那个输入状态」与当前一致时才能用,否则就是上一帧的陈旧值。
|
|
87
|
+
*/
|
|
88
|
+
const [imePosition, setImePosition] = useState(null);
|
|
89
89
|
/** 乐观回显:提交后、真实 user 消息落进 messages 之前,先显示用户提问 */
|
|
90
90
|
const [pendingUser, setPendingUser] = useState(null);
|
|
91
91
|
/** 转录显示起点(/clear 清屏用:隐藏此索引之前的消息,但不改上下文) */
|
|
92
92
|
const [transcriptFrom, setTranscriptFrom] = useState(0);
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const stashRef = useRef(null);
|
|
93
|
+
/**
|
|
94
|
+
* 当前查看的 Agent 视图:`"main"` 或某个子 Agent 的视图 id。
|
|
95
|
+
*
|
|
96
|
+
* 只影响**消息列表的数据源**(主 Agent 的 `messages` / 子 Agent 自己的 `messages`),
|
|
97
|
+
* 不改上下文、不影响发送目标:发出的消息永远属于主 Agent。
|
|
98
|
+
*/
|
|
99
|
+
const [activeAgentId, setActiveAgentId] = useState("main");
|
|
101
100
|
/** Ctrl+D 双击退出计时 */
|
|
102
101
|
const ctrlDAtRef = useRef(0);
|
|
103
102
|
/**
|
|
@@ -107,8 +106,28 @@ export function Chat(props) {
|
|
|
107
106
|
if (noticeChannelRef.current == null)
|
|
108
107
|
noticeChannelRef.current = new NoticeChannel();
|
|
109
108
|
const noticeChannel = noticeChannelRef.current;
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
/**
|
|
110
|
+
* 通知队列(最近的在最后)。通道内部保留最近 `MAX_NOTICE_HISTORY` 条,
|
|
111
|
+
* 这里只做镜像:单槽 state 会让「同一批摘要 / 分阶段进度」只剩最后一条。
|
|
112
|
+
*/
|
|
113
|
+
const [notices, setNotices] = useState(noticeChannel.notices);
|
|
114
|
+
useEffect(() => noticeChannel.subscribe(setNotices), [noticeChannel]);
|
|
115
|
+
/**
|
|
116
|
+
* 配置向导宿主(凭据 / 配置中心):整屏接管与「关闭后让改动生效」见
|
|
117
|
+
* use-wizard-host.ts。这里只提供出口:通知走通知通道、重启 / 重载走会话。
|
|
118
|
+
*/
|
|
119
|
+
const { wizardStep, wizardConfig, wizardPresets, wizardAgents, wizardMcp, wizardAgentStore, openWizard, openKeyEditor, closeWizard, } = useWizardHost({
|
|
120
|
+
modelId,
|
|
121
|
+
notify: (text, tone) => noticeChannel.push(text, tone),
|
|
122
|
+
hasSession: () => sessionRef.current != null,
|
|
123
|
+
restart: () => exit({ restart: true, messages: props.messages }),
|
|
124
|
+
reloadSession: () => {
|
|
125
|
+
void sessionRef.current
|
|
126
|
+
?.reload()
|
|
127
|
+
.then(() => noticeChannel.push(t("notice.reloaded")))
|
|
128
|
+
.catch((error) => noticeChannel.push(t("chat.sessionReloadFailed", { error: error?.message ?? error }), "error"));
|
|
129
|
+
},
|
|
130
|
+
});
|
|
112
131
|
/**
|
|
113
132
|
* 重挂载附带的系统提示(如 `/model` 的切换结果)。
|
|
114
133
|
*
|
|
@@ -116,6 +135,8 @@ export function Chat(props) {
|
|
|
116
135
|
* 会把「展示一条提示」误变成「重建会话」。
|
|
117
136
|
*/
|
|
118
137
|
const initialNoticeRef = useRef(props.initialNotice);
|
|
138
|
+
/** 钩子带来的首条消息:只在会话建好后发一次,重挂载不再重发 */
|
|
139
|
+
const initialPromptRef = useRef(props.initialPrompt);
|
|
119
140
|
/**
|
|
120
141
|
* 渲染信号 + 合帧:SDK 内容变更 → FrameBuffer 合帧 → setTick 触发重渲染。
|
|
121
142
|
* 转录内容始终从 `agent.messages` 现取,tick 只负责「告诉 React 该重画了」。
|
|
@@ -138,16 +159,17 @@ export function Chat(props) {
|
|
|
138
159
|
const agentId = props.agentId ?? "default";
|
|
139
160
|
const definition = store.agents.find((item) => item.id === agentId) ?? null;
|
|
140
161
|
if (alive)
|
|
141
|
-
|
|
162
|
+
setEffortView(describeEffortView(model, presets, definition));
|
|
142
163
|
})
|
|
143
164
|
.catch(() => {
|
|
144
165
|
if (alive)
|
|
145
|
-
|
|
166
|
+
setEffortView(null);
|
|
146
167
|
});
|
|
147
168
|
return () => {
|
|
148
169
|
alive = false;
|
|
149
170
|
};
|
|
150
|
-
|
|
171
|
+
// localeTick:切语言后档位文案要重算(它缓存的是已本地化的字符串,不是档位名)
|
|
172
|
+
}, [modelId, props.agentId, localeTick]);
|
|
151
173
|
// 创建会话
|
|
152
174
|
useEffect(() => {
|
|
153
175
|
let disposed = false;
|
|
@@ -168,7 +190,7 @@ export function Chat(props) {
|
|
|
168
190
|
conversationId: props.conversationId,
|
|
169
191
|
conversationName: props.conversationName,
|
|
170
192
|
// 自动迁移:与手动 /migrate、/handoff 共用同一套「交接方式三选一」选择器
|
|
171
|
-
requestMode: (info) => chooseHandoffMode(setPickerState,
|
|
193
|
+
requestMode: (info) => chooseHandoffMode(setPickerState, t("handoff.pickTitle", { used: info.promptTokens, limit: info.handoffTokens })),
|
|
172
194
|
}, notifier)
|
|
173
195
|
.then((session) => {
|
|
174
196
|
if (disposed) {
|
|
@@ -183,6 +205,14 @@ export function Chat(props) {
|
|
|
183
205
|
noticeChannel.push(initialNoticeRef.current);
|
|
184
206
|
initialNoticeRef.current = undefined;
|
|
185
207
|
}
|
|
208
|
+
// shell 钩子:会话就绪后立即把原命令当**用户消息**发出去(不经命令解析)
|
|
209
|
+
if (initialPromptRef.current) {
|
|
210
|
+
const prompt = initialPromptRef.current;
|
|
211
|
+
initialPromptRef.current = undefined;
|
|
212
|
+
setTick((t) => t + 1);
|
|
213
|
+
void runSubmit(prompt, { asMessage: true });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
186
216
|
setTick((t) => t + 1);
|
|
187
217
|
})
|
|
188
218
|
.catch((error) => setBootError(error?.message ?? String(error)));
|
|
@@ -197,231 +227,51 @@ export function Chat(props) {
|
|
|
197
227
|
props.conversationId,
|
|
198
228
|
props.conversationName,
|
|
199
229
|
]);
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const resolvePicker = (value) => {
|
|
207
|
-
setPickerState((ps) => {
|
|
208
|
-
ps?.resolve(value);
|
|
209
|
-
return null;
|
|
210
|
-
});
|
|
211
|
-
};
|
|
212
|
-
// ---- 配置向导(凭据 / 配置中心)----
|
|
213
|
-
const openWizard = async (step) => {
|
|
214
|
-
try {
|
|
215
|
-
const config = await readConfig();
|
|
216
|
-
currentEndpointRef.current = resolveCredential(config, modelId)?.endpointId ?? null;
|
|
217
|
-
const agentStore = await readAgentStore();
|
|
218
|
-
setWizardConfig(config);
|
|
219
|
-
setWizardPresets(await readReasoningPresets());
|
|
220
|
-
setWizardAgents(agentStore.agents.map((agent) => ({ id: agent.id, name: agent.name || agent.id })));
|
|
221
|
-
setWizardAgentStore(agentStore);
|
|
222
|
-
setWizardMcp({ global: await readMcpConfig("global"), project: await readMcpConfig("project") });
|
|
223
|
-
setWizardStep(step);
|
|
224
|
-
}
|
|
225
|
-
catch (error) {
|
|
226
|
-
noticeChannel.push(`读取配置失败:${error?.message ?? error}`, "error");
|
|
227
|
-
}
|
|
228
|
-
};
|
|
229
|
-
/** `/key`:直达「当前模型所用端点」的 API Key 编辑步骤 */
|
|
230
|
-
const openKeyEditor = async () => {
|
|
231
|
-
try {
|
|
232
|
-
const config = await readConfig();
|
|
233
|
-
const credential = resolveCredential(config, modelId);
|
|
234
|
-
if (!credential) {
|
|
235
|
-
noticeChannel.push(`模型 ${modelId} 未绑定可用端点,请用 /config 检查配置`, "error");
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
currentEndpointRef.current = credential.endpointId;
|
|
239
|
-
setWizardConfig(config);
|
|
240
|
-
setWizardStep({ kind: "edit", field: "apiKey", endpointId: credential.endpointId });
|
|
241
|
-
}
|
|
242
|
-
catch (error) {
|
|
243
|
-
noticeChannel.push(`读取配置失败:${error?.message ?? error}`, "error");
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
/** 向导关闭:渲染变更摘要;若改到当前端点则重建会话(消息保留) */
|
|
247
|
-
const closeWizard = (result) => {
|
|
248
|
-
setWizardStep(null);
|
|
249
|
-
setWizardConfig(null);
|
|
250
|
-
setWizardMcp(null);
|
|
251
|
-
setWizardAgentStore(null);
|
|
252
|
-
for (const line of result.summary)
|
|
253
|
-
noticeChannel.push(`配置已更新:${line}`);
|
|
254
|
-
if (!result.dirty)
|
|
255
|
-
return;
|
|
256
|
-
if (!sessionRef.current) {
|
|
257
|
-
exit({ restart: true, messages: props.messages });
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
const endpointChanged = !!currentEndpointRef.current && result.changedEndpointIds.includes(currentEndpointRef.current);
|
|
261
|
-
if (endpointChanged || result.mcpChanged || result.agentsChanged) {
|
|
262
|
-
sessionRef.current
|
|
263
|
-
.reload()
|
|
264
|
-
.then(() => noticeChannel.push("已按新配置重建会话(消息与上下文保留)"))
|
|
265
|
-
.catch((error) => noticeChannel.push(`重建会话失败:${error?.message ?? error}`, "error"));
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
// ---- 滚动视口控制(应用内翻页,回看历史)----
|
|
269
|
-
const scrollTo = (nextTop) => {
|
|
270
|
-
const L = layoutRef.current;
|
|
271
|
-
if (!L)
|
|
272
|
-
return;
|
|
273
|
-
setViewTop(clampViewTop(L.totalLines, L.budget, nextTop));
|
|
274
|
-
};
|
|
275
|
-
const scrollByPage = (dir) => {
|
|
276
|
-
const L = layoutRef.current;
|
|
277
|
-
if (!L)
|
|
278
|
-
return;
|
|
279
|
-
scrollTo(L.viewTop + dir * Math.max(1, L.budget - 1));
|
|
280
|
-
};
|
|
281
|
-
const scrollByLine = (dir) => {
|
|
282
|
-
const L = layoutRef.current;
|
|
283
|
-
if (!L)
|
|
284
|
-
return;
|
|
285
|
-
scrollTo(L.viewTop + dir);
|
|
286
|
-
};
|
|
287
|
-
/** 鼠标滚轮:一格滚 `WHEEL_STEP` 行(与按行滚动共用一个钳制入口) */
|
|
288
|
-
const scrollByWheel = (dir) => {
|
|
289
|
-
const L = layoutRef.current;
|
|
290
|
-
if (!L)
|
|
230
|
+
// 速率采样:Spinner 的重渲染只影响它自己,不会带着状态栏一起刷,所以生成中要自己定时读快照。
|
|
231
|
+
// 生成结束(busy → false)时再读一次,拿本轮平均速率并留在状态栏上。
|
|
232
|
+
useEffect(() => {
|
|
233
|
+
const read = () => setRate(sessionRef.current?.rateSample ?? null);
|
|
234
|
+
read();
|
|
235
|
+
if (!busy)
|
|
291
236
|
return;
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
237
|
+
const timer = setInterval(read, 250);
|
|
238
|
+
return () => clearInterval(timer);
|
|
239
|
+
}, [busy]);
|
|
295
240
|
/** 清屏但保留上下文:把显示起点推到当前消息末尾 */
|
|
296
241
|
const clearTranscript = () => {
|
|
297
242
|
setTranscriptFrom(sessionRef.current?.messages.length ?? 0);
|
|
298
|
-
|
|
299
|
-
};
|
|
300
|
-
// ---- 输入编辑(光标感知)----
|
|
301
|
-
const setInputText = (text) => {
|
|
302
|
-
setInput(text);
|
|
303
|
-
setCursor(Array.from(text).length);
|
|
304
|
-
};
|
|
305
|
-
const insertAtCursor = (text) => {
|
|
306
|
-
const chars = Array.from(input);
|
|
307
|
-
const cp = Math.max(0, Math.min(cursor, chars.length));
|
|
308
|
-
setInput(chars.slice(0, cp).join("") + text + chars.slice(cp).join(""));
|
|
309
|
-
setCursor(cp + Array.from(text).length);
|
|
310
|
-
};
|
|
311
|
-
const deleteBeforeCursor = () => {
|
|
312
|
-
const chars = Array.from(input);
|
|
313
|
-
const cp = Math.max(0, Math.min(cursor, chars.length));
|
|
314
|
-
if (cp === 0)
|
|
315
|
-
return;
|
|
316
|
-
setInput(chars.slice(0, cp - 1).join("") + chars.slice(cp).join(""));
|
|
317
|
-
setCursor(cp - 1);
|
|
318
|
-
};
|
|
319
|
-
const deleteAfterCursor = () => {
|
|
320
|
-
const chars = Array.from(input);
|
|
321
|
-
const cp = Math.max(0, Math.min(cursor, chars.length));
|
|
322
|
-
if (cp >= chars.length)
|
|
323
|
-
return;
|
|
324
|
-
setInput(chars.slice(0, cp).join("") + chars.slice(cp + 1).join(""));
|
|
325
|
-
};
|
|
326
|
-
/** 套用纯函数算好的「文本 + 光标」结果(无变化则不触发重渲染) */
|
|
327
|
-
const applyEdit = (r) => {
|
|
328
|
-
if (r.text === input && r.cursor === cursor)
|
|
329
|
-
return;
|
|
330
|
-
undoStackRef.current = pushUndo(undoStackRef.current, { text: input, cursor });
|
|
331
|
-
setInput(r.text);
|
|
332
|
-
setCursor(r.cursor);
|
|
333
|
-
};
|
|
334
|
-
/** 删除类编辑:额外把删掉的文本放进 kill ring(Ctrl+Y 可取回) */
|
|
335
|
-
const applyKill = (r) => {
|
|
336
|
-
if (r.text === input && r.cursor === cursor)
|
|
337
|
-
return;
|
|
338
|
-
killRingRef.current = pushKill(killRingRef.current, killedText(input, r.text));
|
|
339
|
-
yankIndexRef.current = -1;
|
|
340
|
-
applyEdit(r);
|
|
341
|
-
};
|
|
342
|
-
const deleteWordBeforeCursor = () => applyKill(deleteWordLeft(input, cursor));
|
|
343
|
-
const deleteWordAfterCursor = () => applyKill(deleteWordRight(input, cursor));
|
|
344
|
-
const deleteToEndOfLine = () => applyKill(deleteToLineEnd(input, cursor));
|
|
345
|
-
const deleteToStartOfLine = () => applyKill(deleteToLineStart(input, cursor));
|
|
346
|
-
/** Ctrl+W:删到前一空白(标点不切分,整条路径一次删掉) */
|
|
347
|
-
const deletePrevWhitespace = () => applyKill(deleteToPrevWhitespace(input, cursor));
|
|
348
|
-
/** Ctrl+Y / Alt+Y:插入 kill ring 中的一段(index 循环) */
|
|
349
|
-
const yank = (advance) => {
|
|
350
|
-
const ring = killRingRef.current;
|
|
351
|
-
if (ring.length === 0)
|
|
352
|
-
return;
|
|
353
|
-
const next = advance ? yankIndexRef.current + 1 : 0;
|
|
354
|
-
if (next >= ring.length)
|
|
355
|
-
return;
|
|
356
|
-
yankIndexRef.current = next;
|
|
357
|
-
const text = killAt(ring, next) ?? "";
|
|
358
|
-
insertAtCursor(text);
|
|
359
|
-
};
|
|
360
|
-
/** Ctrl+_:撤销上一次编辑 */
|
|
361
|
-
const undoEdit = () => {
|
|
362
|
-
const popped = popUndo(undoStackRef.current);
|
|
363
|
-
if (!popped)
|
|
364
|
-
return;
|
|
365
|
-
undoStackRef.current = popped.rest;
|
|
366
|
-
setInput(popped.state.text);
|
|
367
|
-
setCursor(popped.state.cursor);
|
|
368
|
-
};
|
|
369
|
-
/** Ctrl+S:暂存 / 恢复输入 */
|
|
370
|
-
const toggleStash = () => {
|
|
371
|
-
if (input.length > 0) {
|
|
372
|
-
stashRef.current = { text: input, cursor };
|
|
373
|
-
setInputText("");
|
|
374
|
-
noticeChannel.push("已暂存输入(Ctrl+S 恢复)");
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
const stashed = stashRef.current;
|
|
378
|
-
if (!stashed)
|
|
379
|
-
return;
|
|
380
|
-
stashRef.current = null;
|
|
381
|
-
setInput(stashed.text);
|
|
382
|
-
setCursor(stashed.cursor);
|
|
383
|
-
noticeChannel.push("已恢复暂存的输入");
|
|
384
|
-
};
|
|
385
|
-
/** Ctrl+V:粘粘图片 —— 暂未支持(需先做「文件 → 内容块」能力探测,见 SKILL/后续迭代) */
|
|
386
|
-
const pasteImageUnsupported = () => {
|
|
387
|
-
noticeChannel.push("暂不支持粘贴图片(Ctrl+V 预留给图片粘贴,当前未实现)", "error");
|
|
388
|
-
};
|
|
389
|
-
const moveCursor = (delta) => {
|
|
390
|
-
const len = Array.from(input).length;
|
|
391
|
-
setCursor((c) => Math.max(0, Math.min(len, c + delta)));
|
|
392
|
-
};
|
|
393
|
-
const moveCursorToLineStart = () => {
|
|
394
|
-
const chars = Array.from(input);
|
|
395
|
-
const cp = Math.max(0, Math.min(cursor, chars.length));
|
|
396
|
-
let i = cp;
|
|
397
|
-
while (i > 0 && chars[i - 1] !== "\n")
|
|
398
|
-
i -= 1;
|
|
399
|
-
setCursor(i);
|
|
400
|
-
};
|
|
401
|
-
const moveCursorToLineEnd = () => {
|
|
402
|
-
const chars = Array.from(input);
|
|
403
|
-
const cp = Math.max(0, Math.min(cursor, chars.length));
|
|
404
|
-
let i = cp;
|
|
405
|
-
while (i < chars.length && chars[i] !== "\n")
|
|
406
|
-
i += 1;
|
|
407
|
-
setCursor(i);
|
|
243
|
+
noticeChannel.clear();
|
|
408
244
|
};
|
|
245
|
+
/**
|
|
246
|
+
* 输入编辑(文本 / 光标 / 历史 / kill ring / undo / stash):
|
|
247
|
+
* 全部语义在 use-input-editing.ts,这里只把通知出口接上。
|
|
248
|
+
*/
|
|
249
|
+
const { input, cursor, setInputText, insertAtCursor, deleteBeforeCursor, deleteAfterCursor, deleteWordBeforeCursor, deleteWordAfterCursor, deleteToStartOfLine, deleteToEndOfLine, deletePrevWhitespace, yank, undoEdit, toggleStash, moveCursor, moveCursorToLineStart, moveCursorToLineEnd, moveCursorByWord, historyPrev, historyNext, clearDraft, rememberSubmitted, } = useInputEditing({
|
|
250
|
+
initialText: props.initialInput,
|
|
251
|
+
notify: (text, tone) => noticeChannel.push(text, tone),
|
|
252
|
+
});
|
|
253
|
+
/** 斜杠候选菜单的高亮索引;输入一变就回到第一个候选 */
|
|
254
|
+
const [menuIndex, setMenuIndex] = useState(0);
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
setMenuIndex(0);
|
|
257
|
+
}, [input]);
|
|
409
258
|
// 命令候选人
|
|
410
259
|
const slash = input.startsWith("/") && !input.includes("\n") && !input.includes(" ");
|
|
411
260
|
const hints = slash ? matchCommandHints(input.slice(1)) : [];
|
|
412
|
-
const runSubmit = async (text) => {
|
|
261
|
+
const runSubmit = async (text, options = {}) => {
|
|
413
262
|
const trimmed = text.trim();
|
|
414
263
|
if (!trimmed || !sessionRef.current || busy)
|
|
415
264
|
return;
|
|
416
|
-
|
|
417
|
-
historyIndex.current = -1;
|
|
265
|
+
clearDraft();
|
|
418
266
|
scrollToBottom();
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
267
|
+
// 发出去的消息永远属于主 Agent:停在子 Agent 视图上会让用户看不到自己的提问
|
|
268
|
+
setActiveAgentId("main");
|
|
269
|
+
noticeChannel.clear();
|
|
270
|
+
// 斜杠命令:命令名怎么解析、哪些命令会调用模型(要拦输入框),都来自命令元数据
|
|
271
|
+
const command = options.asMessage ? null : parseCommandInput(trimmed);
|
|
272
|
+
if (command) {
|
|
273
|
+
const name = command.name;
|
|
274
|
+
const blocksInput = commandBlocksInput(name);
|
|
425
275
|
if (blocksInput)
|
|
426
276
|
setBusy(true);
|
|
427
277
|
try {
|
|
@@ -438,7 +288,8 @@ export function Chat(props) {
|
|
|
438
288
|
setPickerState,
|
|
439
289
|
setConfirmState,
|
|
440
290
|
setInputText,
|
|
441
|
-
|
|
291
|
+
setEffortView,
|
|
292
|
+
refreshLocale: () => bumpLocale((n) => n + 1),
|
|
442
293
|
});
|
|
443
294
|
}
|
|
444
295
|
finally {
|
|
@@ -447,7 +298,7 @@ export function Chat(props) {
|
|
|
447
298
|
}
|
|
448
299
|
return;
|
|
449
300
|
}
|
|
450
|
-
|
|
301
|
+
rememberSubmitted(trimmed);
|
|
451
302
|
const base = sessionRef.current.messages.filter((m) => m.role === AgentNS.Role.User).length;
|
|
452
303
|
setPendingUser({ text: trimmed, base });
|
|
453
304
|
setBusy(true);
|
|
@@ -462,6 +313,32 @@ export function Chat(props) {
|
|
|
462
313
|
const doExit = () => {
|
|
463
314
|
exit("done");
|
|
464
315
|
};
|
|
316
|
+
/** 本次会话已出现的子 Agent(渲染期现取;`sessionRef` 在首次渲染时为 null) */
|
|
317
|
+
const subAgentViews = sessionRef.current?.subAgents ?? [];
|
|
318
|
+
/**
|
|
319
|
+
* Agent 栏目切换(← / →):在「主 Agent + 各子 Agent」之间循环。
|
|
320
|
+
*
|
|
321
|
+
* 键位分流见下方两个 `useInput`:输入框**为空**或**生成中**(输入框已被 Spinner 顶替)
|
|
322
|
+
* 时,左右键本来就无事可做,正好用来切换;输入框**有内容**时左右键仍然是移光标——
|
|
323
|
+
* 不能为了切栏目把文本编辑的左右移动废掉。
|
|
324
|
+
*/
|
|
325
|
+
const switchAgentView = (dir) => {
|
|
326
|
+
const ids = ["main", ...subAgentViews.map((item) => item.id)];
|
|
327
|
+
// 旧 id 已失效(会话重建)时 indexOf 为 -1,归一后从主 Agent 起步
|
|
328
|
+
const current = Math.max(0, ids.indexOf(activeAgentId));
|
|
329
|
+
setActiveAgentId(ids[(current + dir + ids.length) % ids.length]);
|
|
330
|
+
scrollToBottom();
|
|
331
|
+
};
|
|
332
|
+
/**
|
|
333
|
+
* 两个 `useInput`(空闲 / 忙碌)共用的键位上下文 —— 判定逻辑在 common-keys.ts。
|
|
334
|
+
* 放这里而不是各自内联,是为了让「什么时候能滚、Ctrl+D 怎么退」只有一份定义。
|
|
335
|
+
*/
|
|
336
|
+
const scrollKeys = { scrollByWheel, scrollByPage, scrollByLine };
|
|
337
|
+
const ctrlDExit = {
|
|
338
|
+
lastAt: ctrlDAtRef,
|
|
339
|
+
notify: () => noticeChannel.push(t("notice.pressCtrlDAgain")),
|
|
340
|
+
exit: doExit,
|
|
341
|
+
};
|
|
465
342
|
// 输入处理
|
|
466
343
|
useInput((char, key) => {
|
|
467
344
|
// 按键诊断(AIZ_DEBUG_KEYS=1):必须在任何 return 之前记录,否则被吞掉的键不会留痕
|
|
@@ -475,41 +352,9 @@ export function Chat(props) {
|
|
|
475
352
|
exit("done");
|
|
476
353
|
return;
|
|
477
354
|
}
|
|
478
|
-
//
|
|
479
|
-
|
|
480
|
-
// 交给 `isTerminalReply` 丢弃,绝不落下到输入框。
|
|
481
|
-
const wheel = parseWheel(char);
|
|
482
|
-
if (wheel) {
|
|
483
|
-
scrollByWheel(wheel.direction);
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
if (isTerminalReply(char))
|
|
355
|
+
// 回看历史类按键(滚轮 / PgUp/PgDn / ⌘[ ⌘] / ⌘; ⌘' / Alt+V)与忙碌态共用
|
|
356
|
+
if (handleScrollKey(char, key, scrollKeys))
|
|
487
357
|
return;
|
|
488
|
-
if (key.pageUp) {
|
|
489
|
-
scrollByPage(-1);
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
if (key.pageDown) {
|
|
493
|
-
scrollByPage(1);
|
|
494
|
-
return;
|
|
495
|
-
}
|
|
496
|
-
// 翻页的「兜底键」:部分终端把 PgUp/PgDn 留给自己滚 scrollback(应用收不到)。
|
|
497
|
-
// 注意 `Ctrl+V` 预留给图片粘贴(当前未实现),不再兼任 page-down。
|
|
498
|
-
if (char === "v" && key.meta) {
|
|
499
|
-
scrollByPage(-1);
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
|
-
// macOS 习惯键:⌘[ / ⌘] 翻页、⌘; / ⌘' 按行滚动。
|
|
503
|
-
// Cmd 只在 kitty 键盘协议下才会带 super 位(或终端把 ⌘ 组合映射成带 8 号
|
|
504
|
-
// 修饰位的转义序列,那种会落到上面的 meta 分支里,同样生效)。
|
|
505
|
-
if (key.super && (char === "[" || char === "]")) {
|
|
506
|
-
scrollByPage(char === "[" ? -1 : 1);
|
|
507
|
-
return;
|
|
508
|
-
}
|
|
509
|
-
if (key.super && (char === ";" || char === "'")) {
|
|
510
|
-
scrollByLine(char === ";" ? -1 : 1);
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
358
|
if (key.return && (key.ctrl || key.meta || key.shift)) {
|
|
514
359
|
insertAtCursor("\n");
|
|
515
360
|
return;
|
|
@@ -520,27 +365,19 @@ export function Chat(props) {
|
|
|
520
365
|
sessionRef.current?.abort();
|
|
521
366
|
else if (input.length > 0) {
|
|
522
367
|
setInputText("");
|
|
523
|
-
noticeChannel.push("
|
|
368
|
+
noticeChannel.push(t("notice.inputCleared"));
|
|
524
369
|
}
|
|
525
370
|
else
|
|
526
371
|
void doExit();
|
|
527
372
|
return;
|
|
528
373
|
}
|
|
529
|
-
if (char === "d" && key.ctrl) {
|
|
530
|
-
// Claude Code
|
|
531
|
-
|
|
532
|
-
deleteAfterCursor();
|
|
533
|
-
return;
|
|
534
|
-
}
|
|
535
|
-
const now = Date.now();
|
|
536
|
-
if (now - ctrlDAtRef.current < 800) {
|
|
537
|
-
void doExit();
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
540
|
-
ctrlDAtRef.current = now;
|
|
541
|
-
noticeChannel.push("再按一次 Ctrl+D 退出");
|
|
374
|
+
if (char === "d" && key.ctrl && input.length > 0) {
|
|
375
|
+
// Claude Code:有草稿时删光标后一个字符(空闲态独有)
|
|
376
|
+
deleteAfterCursor();
|
|
542
377
|
return;
|
|
543
378
|
}
|
|
379
|
+
if (handleCtrlDExit(char, key, ctrlDExit))
|
|
380
|
+
return;
|
|
544
381
|
if (char === "l" && key.ctrl) {
|
|
545
382
|
clearTranscript();
|
|
546
383
|
return;
|
|
@@ -578,7 +415,8 @@ export function Chat(props) {
|
|
|
578
415
|
return;
|
|
579
416
|
}
|
|
580
417
|
if ((char === "v" && key.ctrl) || (key.super && (char === "v" || char === "V"))) {
|
|
581
|
-
|
|
418
|
+
// 图片粘贴暂未支持:需要先做「文件 → 内容块」的能力探测(见后续迭代)
|
|
419
|
+
noticeChannel.push(t("notice.pasteImageUnsupported"), "error");
|
|
582
420
|
return;
|
|
583
421
|
}
|
|
584
422
|
// readline 的行首 / 行尾(macOS 上部分终端把 ⌘← / ⌘→ 发成 ^A / ^E)
|
|
@@ -601,22 +439,10 @@ export function Chat(props) {
|
|
|
601
439
|
setMenuIndex((i) => (up ? (i - 1 + hints.length) % hints.length : (i + 1) % hints.length));
|
|
602
440
|
return;
|
|
603
441
|
}
|
|
604
|
-
if (up
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
}
|
|
609
|
-
else if (!up && historyIndex.current >= 0) {
|
|
610
|
-
const idx = historyIndex.current + 1;
|
|
611
|
-
if (idx >= history.length) {
|
|
612
|
-
historyIndex.current = -1;
|
|
613
|
-
setInputText("");
|
|
614
|
-
}
|
|
615
|
-
else {
|
|
616
|
-
historyIndex.current = idx;
|
|
617
|
-
setInputText(history[idx]);
|
|
618
|
-
}
|
|
619
|
-
}
|
|
442
|
+
if (up)
|
|
443
|
+
historyPrev();
|
|
444
|
+
else
|
|
445
|
+
historyNext();
|
|
620
446
|
return;
|
|
621
447
|
}
|
|
622
448
|
// 按词删除:Alt+Backspace / Ctrl+Backspace 删前一个词;Alt+D / Ctrl+Delete 删后一个词。
|
|
@@ -650,34 +476,27 @@ export function Chat(props) {
|
|
|
650
476
|
setMenuIndex((i) => key.upArrow ? (i - 1 + hints.length) % hints.length : (i + 1) % hints.length);
|
|
651
477
|
return;
|
|
652
478
|
}
|
|
653
|
-
if (key.upArrow
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
setInputText("");
|
|
663
|
-
}
|
|
664
|
-
else {
|
|
665
|
-
historyIndex.current = idx;
|
|
666
|
-
setInputText(history[idx]);
|
|
667
|
-
}
|
|
668
|
-
}
|
|
479
|
+
if (key.upArrow)
|
|
480
|
+
historyPrev();
|
|
481
|
+
else
|
|
482
|
+
historyNext();
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
// Agent 栏目:输入框为空时光标停在 0,←/→ 本无作用 → 拿来切换主 / 子 Agent
|
|
486
|
+
if ((key.leftArrow || key.rightArrow) && input.length === 0 && subAgentViews.length > 0) {
|
|
487
|
+
switchAgentView(key.leftArrow ? -1 : 1);
|
|
669
488
|
return;
|
|
670
489
|
}
|
|
671
490
|
if (key.leftArrow) {
|
|
672
491
|
if (key.ctrl || key.meta || key.super)
|
|
673
|
-
|
|
492
|
+
moveCursorByWord(-1);
|
|
674
493
|
else
|
|
675
494
|
moveCursor(-1);
|
|
676
495
|
return;
|
|
677
496
|
}
|
|
678
497
|
if (key.rightArrow) {
|
|
679
498
|
if (key.ctrl || key.meta || key.super)
|
|
680
|
-
|
|
499
|
+
moveCursorByWord(1);
|
|
681
500
|
else
|
|
682
501
|
moveCursor(1);
|
|
683
502
|
return;
|
|
@@ -686,11 +505,11 @@ export function Chat(props) {
|
|
|
686
505
|
// (readline 的 backward-word / forward-word),并不带 leftArrow 标记;
|
|
687
506
|
// 而 Ctrl+←/→ 常被系统「Mission Control → 左右切换空间」截走,应用根本收不到。
|
|
688
507
|
if (char === "b" && key.meta) {
|
|
689
|
-
|
|
508
|
+
moveCursorByWord(-1);
|
|
690
509
|
return;
|
|
691
510
|
}
|
|
692
511
|
if (char === "f" && key.meta) {
|
|
693
|
-
|
|
512
|
+
moveCursorByWord(1);
|
|
694
513
|
return;
|
|
695
514
|
}
|
|
696
515
|
if (key.home) {
|
|
@@ -731,12 +550,7 @@ export function Chat(props) {
|
|
|
731
550
|
useInput((char, key) => {
|
|
732
551
|
keyDebug(char, key);
|
|
733
552
|
// 忙碌期只剩「中止 / 退出 / 回看」,滚轮同样生效(生成时正是最想往回看的时机)
|
|
734
|
-
|
|
735
|
-
if (wheel) {
|
|
736
|
-
scrollByWheel(wheel.direction);
|
|
737
|
-
return;
|
|
738
|
-
}
|
|
739
|
-
if (isTerminalReply(char))
|
|
553
|
+
if (handleScrollKey(char, key, scrollKeys))
|
|
740
554
|
return;
|
|
741
555
|
if (char === "c" && key.ctrl) {
|
|
742
556
|
sessionRef.current?.abort();
|
|
@@ -747,38 +561,19 @@ export function Chat(props) {
|
|
|
747
561
|
sessionRef.current?.abort();
|
|
748
562
|
return;
|
|
749
563
|
}
|
|
750
|
-
if (char
|
|
751
|
-
const now = Date.now();
|
|
752
|
-
if (now - ctrlDAtRef.current < 800) {
|
|
753
|
-
void doExit();
|
|
754
|
-
return;
|
|
755
|
-
}
|
|
756
|
-
ctrlDAtRef.current = now;
|
|
757
|
-
noticeChannel.push("再按一次 Ctrl+D 退出");
|
|
758
|
-
return;
|
|
759
|
-
}
|
|
760
|
-
if (key.pageUp) {
|
|
761
|
-
scrollByPage(-1);
|
|
564
|
+
if (handleCtrlDExit(char, key, ctrlDExit))
|
|
762
565
|
return;
|
|
763
|
-
}
|
|
764
|
-
if (key.pageDown) {
|
|
765
|
-
scrollByPage(1);
|
|
766
|
-
return;
|
|
767
|
-
}
|
|
768
566
|
if ((key.upArrow || key.downArrow) && (key.shift || key.meta || key.super)) {
|
|
769
567
|
scrollByLine(key.upArrow ? -1 : 1);
|
|
770
568
|
return;
|
|
771
569
|
}
|
|
772
|
-
|
|
773
|
-
|
|
570
|
+
// Agent 栏目:生成中输入框已失活,←/→ 空闲 → 直接切换(此时最想看子 Agent 在干什么)
|
|
571
|
+
if ((key.leftArrow || key.rightArrow) && subAgentViews.length > 0) {
|
|
572
|
+
switchAgentView(key.leftArrow ? -1 : 1);
|
|
774
573
|
return;
|
|
775
574
|
}
|
|
776
|
-
if (
|
|
777
|
-
scrollByPage(
|
|
778
|
-
return;
|
|
779
|
-
}
|
|
780
|
-
if (key.super && (char === ";" || char === "'")) {
|
|
781
|
-
scrollByLine(char === ";" ? -1 : 1);
|
|
575
|
+
if (char === "v" && key.super) {
|
|
576
|
+
scrollByPage(-1);
|
|
782
577
|
return;
|
|
783
578
|
}
|
|
784
579
|
}, { isActive: busy && !confirmState && !pickerState && !wizardStep });
|
|
@@ -790,20 +585,58 @@ export function Chat(props) {
|
|
|
790
585
|
}
|
|
791
586
|
if (bootError) {
|
|
792
587
|
setCursorPosition(undefined);
|
|
793
|
-
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
|
|
588
|
+
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { color: theme.error, wrap: "truncate", children: t("chat.bootFailed", { error: bootError }) }), _jsx(Text, { color: theme.dim, wrap: "truncate", children: t("chat.bootHint") })] }));
|
|
794
589
|
}
|
|
795
590
|
const session = sessionRef.current;
|
|
796
591
|
const lastTokens = session?.lastUsage?.total_tokens;
|
|
797
592
|
const totalTokens = session?.totalUsage.total_tokens;
|
|
798
593
|
const cols = columns ?? 80;
|
|
799
594
|
const messages = session?.messages ?? [];
|
|
595
|
+
const subAgents = session?.subAgents ?? [];
|
|
596
|
+
/** 当前查看的子 Agent;不在列表里(会话重建后 id 失效)时回落到主 Agent */
|
|
597
|
+
const activeSub = subAgents.find((item) => item.id === activeAgentId) ?? null;
|
|
800
598
|
const userCount = messages.filter((m) => m.role === AgentNS.Role.User).length;
|
|
801
|
-
|
|
802
|
-
const
|
|
599
|
+
// 乐观回显只属于主 Agent 的视图(子 Agent 不会收到用户直接发的消息)
|
|
600
|
+
const showPending = !activeSub && pendingUser != null && userCount <= pendingUser.base;
|
|
601
|
+
// 子 Agent 视图读它**自己**的 messages(从第 0 条起,不受主会话 /clear 的显示起点影响)
|
|
602
|
+
const transcript = transcriptLines(activeSub ? activeSub.agent.messages : messages.slice(transcriptFrom), cols);
|
|
803
603
|
if (showPending) {
|
|
804
604
|
transcript.push(...messageToLines({ id: "__pending_user", role: AgentNS.Role.User, content: pendingUser.text }, cols));
|
|
805
605
|
}
|
|
806
|
-
const noticeTextLines =
|
|
606
|
+
const noticeTextLines = noticeLines(notices, cols);
|
|
607
|
+
/** Agent 栏目条目:无子 Agent 时整条栏目不存在(不占高度、也不接管左右键) */
|
|
608
|
+
const agentTabs = subAgents.length > 0
|
|
609
|
+
? [
|
|
610
|
+
{ id: "main", label: t("agentTabs.main") },
|
|
611
|
+
...subAgents.map((item) => ({
|
|
612
|
+
id: item.id,
|
|
613
|
+
label: t("agentTabs.sub", { n: item.index }),
|
|
614
|
+
status: item.status,
|
|
615
|
+
})),
|
|
616
|
+
]
|
|
617
|
+
: [];
|
|
618
|
+
const activeTabId = activeSub ? activeSub.id : "main";
|
|
619
|
+
const agentTabLines = agentTabs.length > 0 ? AGENT_TABS_LINES : 0;
|
|
620
|
+
// 状态栏:先按当前状态算出「折行后实际几行」,交给布局预留高度(否则窄屏下一旦提示折行,
|
|
621
|
+
// 整帧就会高出一行,把输入框顶出屏幕)。这里用 `viewTop` 状态判断滚动,而不是
|
|
622
|
+
// `layout.scrolled` —— 后者要等布局算完才有,会与行数测量形成循环。
|
|
623
|
+
const statusProps = {
|
|
624
|
+
model: modelId,
|
|
625
|
+
agent: props.agentId ?? "default",
|
|
626
|
+
effort: effortView?.text ?? null,
|
|
627
|
+
effortDimmed: effortView?.dimmed ?? false,
|
|
628
|
+
lastTokens,
|
|
629
|
+
totalTokens,
|
|
630
|
+
busy,
|
|
631
|
+
hint: !ready
|
|
632
|
+
? t("input.hint.initializing")
|
|
633
|
+
: viewTop != null
|
|
634
|
+
? t("input.hint.scrolling")
|
|
635
|
+
: t("input.hint.ready"),
|
|
636
|
+
rate,
|
|
637
|
+
columns: cols,
|
|
638
|
+
};
|
|
639
|
+
const statusRows = statusBarRows(statusProps, cols);
|
|
807
640
|
const layout = computeChatLayout({
|
|
808
641
|
columns: cols,
|
|
809
642
|
rows: usableFrameRows(rows ?? 24),
|
|
@@ -812,45 +645,69 @@ export function Chat(props) {
|
|
|
812
645
|
busy,
|
|
813
646
|
hintCount: hints.length,
|
|
814
647
|
noticeCount: noticeTextLines.length,
|
|
648
|
+
agentTabLines,
|
|
815
649
|
overlay: confirmState ? "confirm" : pickerState ? "picker" : "none",
|
|
816
650
|
pickerItems: pickerState?.items.length ?? 0,
|
|
817
651
|
viewTop,
|
|
652
|
+
statusBarLines: statusRows.length,
|
|
818
653
|
});
|
|
819
654
|
layoutRef.current = layout;
|
|
820
655
|
const isPlaceholder = input.length === 0;
|
|
821
|
-
const inputLayout = layoutInput(isPlaceholder ? "
|
|
656
|
+
const inputLayout = layoutInput(isPlaceholder ? t("input.placeholder") : input, isPlaceholder ? 0 : cursor, Math.max(4, cols - 4));
|
|
822
657
|
const bodyLines = inputLayout.lines;
|
|
823
|
-
//
|
|
658
|
+
// 光标所在行的三段拆分(与 `CursorInputRow` 的渲染一一对应)
|
|
659
|
+
const cursorRowIndex = inputLayout.cursorRow;
|
|
660
|
+
const cursorRowChars = graphemes(bodyLines[cursorRowIndex] ?? "");
|
|
661
|
+
const cursorColInRow = Math.min(inputLayout.cursorCol, cursorRowChars.length);
|
|
662
|
+
const cursorBefore = cursorRowChars.slice(0, cursorColInRow).join("");
|
|
663
|
+
const cursorAt = cursorRowChars[cursorColInRow] ?? " ";
|
|
664
|
+
const cursorAfter = cursorRowChars.slice(cursorColInRow + 1).join("");
|
|
665
|
+
/** 实测结果的身份标记:输入段落一改,上一帧的实测值就作废 */
|
|
666
|
+
const cursorKey = `${cursorRowIndex}\u0000${cursorBefore}\u0000${cursorAt}\u0000${cursorAfter}`;
|
|
667
|
+
/** 渲染后实测(值不变则不会重渲染);闭包捕获当帧的 key,保证标的是「这一帧」的结果 */
|
|
668
|
+
const onCursorMeasure = (position) => {
|
|
669
|
+
setImePosition((prev) => prev && prev.position.x === position.x && prev.position.y === position.y
|
|
670
|
+
? prev
|
|
671
|
+
: { key: cursorKey, position });
|
|
672
|
+
};
|
|
673
|
+
// 把终端硬件光标移到编辑光标处,让 IME(输入法)候选框正确定位:
|
|
674
|
+
// 首选「渲染器实测」(宽字符 / emoji 的宽度由 Ink 自己算,不会偏);
|
|
675
|
+
// 首次渲染还没量到时,退回手算值顶一帧(下一帧即被实测值纠正)。
|
|
824
676
|
if (!busy && !confirmState && !pickerState) {
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
677
|
+
const measured = imePosition && imePosition.key === cursorKey ? imePosition.position : null;
|
|
678
|
+
setCursorPosition(measured ??
|
|
679
|
+
computeInputCursorPosition({
|
|
680
|
+
transcriptLines: layout.lines.length,
|
|
681
|
+
// Agent 栏目与通知栏都夹在对话区与输入行之间:漏算会让硬件光标整体偏上
|
|
682
|
+
agentTabCount: agentTabLines,
|
|
683
|
+
noticeCount: noticeTextLines.length,
|
|
684
|
+
hintCount: hints.length,
|
|
685
|
+
cursorRow: inputLayout.cursorRow,
|
|
686
|
+
cursorCol: inputLayout.cursorCol,
|
|
687
|
+
lineText: bodyLines[inputLayout.cursorRow] ?? "",
|
|
688
|
+
placeholder: isPlaceholder,
|
|
689
|
+
}));
|
|
833
690
|
}
|
|
834
691
|
else {
|
|
835
692
|
setCursorPosition(undefined);
|
|
836
693
|
}
|
|
837
|
-
return (_jsxs(Box, { flexDirection: "column", width: cols, children: [layout.lines.map((line) => (_jsx(LineView, { line: line }, line.key))), confirmState ? (_jsx(Confirm, { question: confirmState.question, onAnswer: resolveConfirm })) : pickerState ? (_jsx(PickerOverlay, { title: pickerState.title, items: pickerState.items, columns: cols, onPick: resolvePicker })) : (_jsxs(Box, { flexDirection: "column", children: [
|
|
694
|
+
return (_jsxs(Box, { flexDirection: "column", width: cols, children: [layout.lines.map((line) => (_jsx(LineView, { line: line }, line.key))), confirmState ? (_jsx(Confirm, { question: confirmState.question, onAnswer: resolveConfirm })) : pickerState ? (_jsx(PickerOverlay, { title: pickerState.title, items: pickerState.items, columns: cols, onPick: resolvePicker })) : (_jsxs(Box, { flexDirection: "column", children: [agentTabs.length > 0 ? _jsx(AgentTabs, { tabs: agentTabs, activeId: activeTabId }) : null, _jsx(NoticeBar, { lines: noticeTextLines }), hints.length > 0 ? _jsx(SlashMenu, { hints: hints, selected: menuIndex }) : null, _jsx(Box, { paddingX: 1, children: _jsx(Text, { color: theme.border, wrap: "truncate", children: "─".repeat(Math.max(1, cols - 2)) }) }), _jsx(Box, { flexDirection: "column", paddingX: 1, children: busy ? (
|
|
695
|
+
// 阶段随 SDK 事件推进:连接中 → 排队中 → 思考 / 输出 / 调用工具 → 等工具返回。
|
|
696
|
+
// 渲染期现取(不另存 state):chunk 与轮次边界都会触发重渲染,
|
|
697
|
+
// 而 `busy` 置 true 本身也会重渲一次,捕得到首轮的「连接中」。
|
|
698
|
+
_jsx(Spinner, { label: phaseLabel(sessionRef.current?.phase ?? null) })) : (bodyLines.map((line, i) => {
|
|
838
699
|
const prompt = i === 0 ? (_jsx(Text, { color: theme.brand[0], bold: true, children: "❯ " })) : (_jsx(Text, { children: " " }));
|
|
839
700
|
if (isPlaceholder) {
|
|
840
|
-
|
|
701
|
+
// 占位态不画「假光标」(那个行尾的 ▏):硬件光标这时就停在提示语开头
|
|
702
|
+
// (x = 前缀宽),终端自己会把它显示出来;再画一个方块等于屏幕上两个光标,
|
|
703
|
+
// 而且位置还在提示语末尾,反而误导。
|
|
704
|
+
return (_jsxs(Text, { wrap: "truncate", children: [prompt, _jsx(Text, { color: theme.faint, children: line })] }, i));
|
|
841
705
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
const at = rowChars[inputLayout.cursorCol];
|
|
846
|
-
const after = rowChars.slice(inputLayout.cursorCol + 1).join("");
|
|
847
|
-
return (_jsxs(Text, { wrap: "truncate", children: [prompt, _jsx(Text, { children: before }), _jsx(Text, { inverse: true, color: theme.brand[0], children: at ?? " " }), _jsx(Text, { children: after })] }, i));
|
|
706
|
+
// 光标行:把光标字符包成独立节点,渲染后由渲染器给出真实坐标(IME 定位)
|
|
707
|
+
if (i === cursorRowIndex) {
|
|
708
|
+
return (_jsx(CursorInputRow, { firstLine: i === 0, before: cursorBefore, at: cursorAt, after: cursorAfter, onMeasure: onCursorMeasure }, i));
|
|
848
709
|
}
|
|
849
710
|
return (_jsxs(Text, { wrap: "truncate", children: [prompt, _jsx(Text, { children: line })] }, i));
|
|
850
|
-
})) })] })), _jsx(StatusBar, {
|
|
851
|
-
? "初始化中…"
|
|
852
|
-
: layout.scrolled
|
|
853
|
-
? "↕ 滚动中 · PgUp/PgDn 翻页 · 发送回到底部"
|
|
854
|
-
: "Enter 发送 · Ctrl+J 换行 · / 命令 · PgUp 看历史" })] }));
|
|
711
|
+
})) })] })), _jsx(StatusBar, { ...statusProps })] }));
|
|
855
712
|
}
|
|
856
713
|
//# sourceMappingURL=chat.js.map
|