@code4bug/jarvis-agent 1.0.2
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/LICENSE +21 -0
- package/README.md +227 -0
- package/dist/agents/code-reviewer.md +69 -0
- package/dist/agents/dba.md +68 -0
- package/dist/agents/finance-advisor.md +81 -0
- package/dist/agents/index.d.ts +31 -0
- package/dist/agents/index.js +86 -0
- package/dist/agents/jarvis.md +95 -0
- package/dist/agents/stock-trader.md +81 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +9 -0
- package/dist/commands/index.d.ts +19 -0
- package/dist/commands/index.js +79 -0
- package/dist/commands/init.d.ts +15 -0
- package/dist/commands/init.js +283 -0
- package/dist/components/DangerConfirm.d.ts +17 -0
- package/dist/components/DangerConfirm.js +50 -0
- package/dist/components/MarkdownText.d.ts +12 -0
- package/dist/components/MarkdownText.js +166 -0
- package/dist/components/MessageItem.d.ts +8 -0
- package/dist/components/MessageItem.js +78 -0
- package/dist/components/MultilineInput.d.ts +34 -0
- package/dist/components/MultilineInput.js +437 -0
- package/dist/components/SlashCommandMenu.d.ts +16 -0
- package/dist/components/SlashCommandMenu.js +43 -0
- package/dist/components/StatusBar.d.ts +8 -0
- package/dist/components/StatusBar.js +26 -0
- package/dist/components/StreamingText.d.ts +6 -0
- package/dist/components/StreamingText.js +10 -0
- package/dist/components/WelcomeHeader.d.ts +6 -0
- package/dist/components/WelcomeHeader.js +25 -0
- package/dist/config/agentState.d.ts +16 -0
- package/dist/config/agentState.js +65 -0
- package/dist/config/constants.d.ts +25 -0
- package/dist/config/constants.js +67 -0
- package/dist/config/loader.d.ts +30 -0
- package/dist/config/loader.js +64 -0
- package/dist/config/systemInfo.d.ts +12 -0
- package/dist/config/systemInfo.js +95 -0
- package/dist/core/QueryEngine.d.ts +52 -0
- package/dist/core/QueryEngine.js +246 -0
- package/dist/core/hint.d.ts +14 -0
- package/dist/core/hint.js +279 -0
- package/dist/core/query.d.ts +24 -0
- package/dist/core/query.js +245 -0
- package/dist/core/safeguard.d.ts +96 -0
- package/dist/core/safeguard.js +236 -0
- package/dist/hooks/useFocus.d.ts +12 -0
- package/dist/hooks/useFocus.js +35 -0
- package/dist/hooks/useInputHistory.d.ts +14 -0
- package/dist/hooks/useInputHistory.js +102 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6 -0
- package/dist/screens/repl.d.ts +1 -0
- package/dist/screens/repl.js +842 -0
- package/dist/services/api/llm.d.ts +27 -0
- package/dist/services/api/llm.js +314 -0
- package/dist/services/api/mock.d.ts +9 -0
- package/dist/services/api/mock.js +102 -0
- package/dist/skills/index.d.ts +23 -0
- package/dist/skills/index.js +232 -0
- package/dist/skills/loader.d.ts +45 -0
- package/dist/skills/loader.js +108 -0
- package/dist/tools/createSkill.d.ts +8 -0
- package/dist/tools/createSkill.js +255 -0
- package/dist/tools/index.d.ts +16 -0
- package/dist/tools/index.js +23 -0
- package/dist/tools/listDirectory.d.ts +2 -0
- package/dist/tools/listDirectory.js +20 -0
- package/dist/tools/readFile.d.ts +2 -0
- package/dist/tools/readFile.js +17 -0
- package/dist/tools/runCommand.d.ts +2 -0
- package/dist/tools/runCommand.js +69 -0
- package/dist/tools/searchFiles.d.ts +2 -0
- package/dist/tools/searchFiles.js +45 -0
- package/dist/tools/writeFile.d.ts +2 -0
- package/dist/tools/writeFile.js +42 -0
- package/dist/types/index.d.ts +86 -0
- package/dist/types/index.js +2 -0
- package/package.json +55 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React, { useMemo } from 'react';
|
|
3
|
+
import { Text } from 'ink';
|
|
4
|
+
import { Marked } from 'marked';
|
|
5
|
+
// @ts-ignore — marked-terminal 无内置类型声明
|
|
6
|
+
import { markedTerminal } from 'marked-terminal';
|
|
7
|
+
// @ts-ignore
|
|
8
|
+
import Table from 'cli-table3';
|
|
9
|
+
// ===== ANSI 颜色常量 =====
|
|
10
|
+
const RESET = '\x1b[0m';
|
|
11
|
+
const BG_CODE = '\x1b[48;5;236m'; // 深灰背景
|
|
12
|
+
const FG_LANG = '\x1b[38;5;243m'; // 语言标签颜色
|
|
13
|
+
const FG_BORDER = '\x1b[38;5;240m'; // 边框颜色
|
|
14
|
+
const FG_CODE = '\x1b[38;5;252m'; // 代码文本颜色
|
|
15
|
+
/** 获取终端宽度,用于代码块边框 */
|
|
16
|
+
function getTermWidth() {
|
|
17
|
+
return Math.min(process.stdout.columns || 80, 100);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 去除字符串中的 ANSI 转义序列,返回可见字符长度
|
|
21
|
+
*/
|
|
22
|
+
function stripAnsi(str) {
|
|
23
|
+
// eslint-disable-next-line no-control-regex
|
|
24
|
+
return str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 自定义代码块渲染:深色背景 + Unicode 边框 + 语言标签
|
|
28
|
+
*/
|
|
29
|
+
function renderCodeBlock(code, lang) {
|
|
30
|
+
const width = getTermWidth() - 4;
|
|
31
|
+
const innerWidth = width - 2;
|
|
32
|
+
const lines = code.replace(/\n$/, '').split('\n');
|
|
33
|
+
const langTag = lang ? ` ${lang} ` : '';
|
|
34
|
+
const topBarFill = innerWidth - langTag.length;
|
|
35
|
+
const parts = [];
|
|
36
|
+
parts.push(`${FG_BORDER}┌${'─'.repeat(Math.max(topBarFill - langTag.length, 1))}${FG_LANG}${langTag}${FG_BORDER}${'─'.repeat(Math.min(langTag.length, topBarFill))}┐${RESET}`);
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
const visible = stripAnsi(line);
|
|
39
|
+
const pad = Math.max(innerWidth - visible.length, 0);
|
|
40
|
+
parts.push(`${FG_BORDER}│${RESET}${BG_CODE}${FG_CODE}${line}${' '.repeat(pad)}${RESET}${FG_BORDER}│${RESET}`);
|
|
41
|
+
}
|
|
42
|
+
parts.push(`${FG_BORDER}└${'─'.repeat(innerWidth)}┘${RESET}`);
|
|
43
|
+
return '\n' + parts.join('\n') + '\n';
|
|
44
|
+
}
|
|
45
|
+
// ===== 表格边框字符 =====
|
|
46
|
+
const TABLE_CHARS = {
|
|
47
|
+
top: '─', 'top-mid': '┬', 'top-left': '┌', 'top-right': '┐',
|
|
48
|
+
bottom: '─', 'bottom-mid': '┴', 'bottom-left': '└', 'bottom-right': '┘',
|
|
49
|
+
left: '│', 'left-mid': '├',
|
|
50
|
+
mid: '─', 'mid-mid': '┼',
|
|
51
|
+
right: '│', 'right-mid': '┤',
|
|
52
|
+
middle: '│',
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* 根据终端宽度和实际列数,计算每列宽度
|
|
56
|
+
* cli-table3 的 colWidths 包含 padding(左右各1),所以每列最小宽度 = 内容宽度 + 2
|
|
57
|
+
*/
|
|
58
|
+
function calcColWidths(colCount) {
|
|
59
|
+
const termWidth = getTermWidth();
|
|
60
|
+
// 边框占用:左边框1 + 列间分隔符(colCount-1) + 右边框1 = colCount + 1
|
|
61
|
+
const borderOverhead = colCount + 1;
|
|
62
|
+
// 每列 padding 左右各1
|
|
63
|
+
const paddingOverhead = colCount * 2;
|
|
64
|
+
const available = Math.max(termWidth - borderOverhead - paddingOverhead, colCount * 6);
|
|
65
|
+
const baseWidth = Math.floor(available / colCount);
|
|
66
|
+
const remainder = available - baseWidth * colCount;
|
|
67
|
+
// 均分宽度,余数分给前几列;+2 是 cli-table3 的 padding
|
|
68
|
+
return Array.from({ length: colCount }, (_, i) => baseWidth + (i < remainder ? 1 : 0) + 2);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 递归提取 marked token 中的纯文本内容
|
|
72
|
+
*/
|
|
73
|
+
function extractTokenText(tokens) {
|
|
74
|
+
if (!tokens || !Array.isArray(tokens))
|
|
75
|
+
return '';
|
|
76
|
+
return tokens.map((t) => {
|
|
77
|
+
if (t.type === 'text' || t.type === 'codespan')
|
|
78
|
+
return t.text || t.raw || '';
|
|
79
|
+
if (t.type === 'strong' || t.type === 'em' || t.type === 'del')
|
|
80
|
+
return extractTokenText(t.tokens);
|
|
81
|
+
if (t.type === 'link')
|
|
82
|
+
return extractTokenText(t.tokens);
|
|
83
|
+
return t.raw || t.text || '';
|
|
84
|
+
}).join('');
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 自定义表格渲染:动态列宽 + 自动换行
|
|
88
|
+
* 作为 marked extension 注入,完全接管表格 token 的渲染
|
|
89
|
+
*/
|
|
90
|
+
function renderTable(token) {
|
|
91
|
+
// 解析表头
|
|
92
|
+
const headerCells = token.header.map((cell) => extractTokenText(cell.tokens));
|
|
93
|
+
// 解析表体
|
|
94
|
+
const bodyRows = token.rows.map((row) => row.map((cell) => extractTokenText(cell.tokens)));
|
|
95
|
+
const colCount = headerCells.length;
|
|
96
|
+
if (colCount === 0)
|
|
97
|
+
return '';
|
|
98
|
+
const colWidths = calcColWidths(colCount);
|
|
99
|
+
const table = new Table({
|
|
100
|
+
head: headerCells,
|
|
101
|
+
chars: TABLE_CHARS,
|
|
102
|
+
colWidths,
|
|
103
|
+
wordWrap: true,
|
|
104
|
+
wrapOnWordBoundary: false,
|
|
105
|
+
style: {
|
|
106
|
+
'padding-left': 1,
|
|
107
|
+
'padding-right': 1,
|
|
108
|
+
head: ['cyan', 'bold'],
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
for (const row of bodyRows) {
|
|
112
|
+
table.push(row);
|
|
113
|
+
}
|
|
114
|
+
return '\n' + table.toString() + '\n\n';
|
|
115
|
+
}
|
|
116
|
+
// 创建 marked 实例
|
|
117
|
+
const marked = new Marked(markedTerminal({
|
|
118
|
+
reflowText: false,
|
|
119
|
+
code: renderCodeBlock,
|
|
120
|
+
// 保留 tableOptions 作为 fallback,但主要靠 extension 接管
|
|
121
|
+
tableOptions: {
|
|
122
|
+
chars: TABLE_CHARS,
|
|
123
|
+
},
|
|
124
|
+
}),
|
|
125
|
+
// 自定义 table renderer extension,优先级高于 markedTerminal 的 table 方法
|
|
126
|
+
{
|
|
127
|
+
renderer: {
|
|
128
|
+
table: renderTable,
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
/**
|
|
132
|
+
* 补全流式文本中未闭合的 markdown 结构
|
|
133
|
+
*/
|
|
134
|
+
function patchIncompleteMarkdown(text) {
|
|
135
|
+
let patched = text;
|
|
136
|
+
const fenceMatches = patched.match(/^```/gm);
|
|
137
|
+
if (fenceMatches && fenceMatches.length % 2 !== 0) {
|
|
138
|
+
patched += '\n```';
|
|
139
|
+
}
|
|
140
|
+
const backtickCount = (patched.match(/(?<!\\)`/g) || []).length;
|
|
141
|
+
if (backtickCount % 2 !== 0) {
|
|
142
|
+
patched += '`';
|
|
143
|
+
}
|
|
144
|
+
return patched;
|
|
145
|
+
}
|
|
146
|
+
/** 将 markdown 文本渲染为终端 ANSI 字符串 */
|
|
147
|
+
function renderMarkdown(text) {
|
|
148
|
+
try {
|
|
149
|
+
const patched = patchIncompleteMarkdown(text);
|
|
150
|
+
const rendered = marked.parse(patched);
|
|
151
|
+
return rendered.replace(/^\n+/, '').replace(/\n+$/, '');
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return text;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Markdown 终端渲染组件
|
|
159
|
+
* 支持表格(动态列宽+自动换行)、代码块(深色背景+边框)、加粗、列表等
|
|
160
|
+
* 对流式不完整文本做容错补全
|
|
161
|
+
*/
|
|
162
|
+
function MarkdownText({ text, color }) {
|
|
163
|
+
const rendered = useMemo(() => renderMarkdown(text), [text]);
|
|
164
|
+
return (_jsx(Text, { wrap: "wrap", color: color, children: rendered }));
|
|
165
|
+
}
|
|
166
|
+
export default React.memo(MarkdownText);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Message } from '../types/index.js';
|
|
3
|
+
declare function MessageItem({ msg, showDetails }: {
|
|
4
|
+
msg: Message;
|
|
5
|
+
showDetails?: boolean;
|
|
6
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
7
|
+
declare const _default: React.MemoExoticComponent<typeof MessageItem>;
|
|
8
|
+
export default _default;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import Spinner from 'ink-spinner';
|
|
5
|
+
import MarkdownText from './MarkdownText.js';
|
|
6
|
+
// 状态圆点 icon,根据消息类型 + 状态决定颜色
|
|
7
|
+
// reasoning 完成 → 白色 / tool_exec 成功 → 绿色 / error → 红色 / aborted → 黄色 / pending → 黄色
|
|
8
|
+
function statusDot(status, type) {
|
|
9
|
+
if (status === 'pending')
|
|
10
|
+
return { dot: '●', color: 'yellow' };
|
|
11
|
+
if (status === 'aborted')
|
|
12
|
+
return { dot: '●', color: 'yellow' };
|
|
13
|
+
if (status === 'error')
|
|
14
|
+
return { dot: '●', color: 'red' };
|
|
15
|
+
// success 按消息类型区分
|
|
16
|
+
if (type === 'reasoning')
|
|
17
|
+
return { dot: '●', color: 'white' };
|
|
18
|
+
if (type === 'tool_exec')
|
|
19
|
+
return { dot: '●', color: 'green' };
|
|
20
|
+
return { dot: '●', color: 'green' };
|
|
21
|
+
}
|
|
22
|
+
/** 消息统计信息行:耗时 / token 数 / 首 token 延时 / 平均 token/s */
|
|
23
|
+
function MessageStats({ msg, show }) {
|
|
24
|
+
if (msg.type === 'user' || msg.status === 'pending')
|
|
25
|
+
return null;
|
|
26
|
+
// aborted 提示始终显示,不受 showDetails 控制
|
|
27
|
+
const showAbortHint = msg.status === 'aborted' && msg.abortHint;
|
|
28
|
+
if (!show && !showAbortHint)
|
|
29
|
+
return null;
|
|
30
|
+
const parts = [];
|
|
31
|
+
if (show) {
|
|
32
|
+
if (msg.duration != null)
|
|
33
|
+
parts.push(`耗时 ${(msg.duration / 1000).toFixed(1)}s`);
|
|
34
|
+
if (msg.tokenCount != null)
|
|
35
|
+
parts.push(`${msg.tokenCount} tokens`);
|
|
36
|
+
if (msg.firstTokenLatency != null)
|
|
37
|
+
parts.push(`首token ${msg.firstTokenLatency}ms`);
|
|
38
|
+
if (msg.tokensPerSecond != null)
|
|
39
|
+
parts.push(`${msg.tokensPerSecond.toFixed(1)} tok/s`);
|
|
40
|
+
}
|
|
41
|
+
return (_jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [parts.length > 0 && (_jsx(Text, { color: "gray", dimColor: true, children: parts.join(' · ') })), showAbortHint && (_jsxs(Text, { color: "red", children: ["\u23F9 ", msg.abortHint] }))] }));
|
|
42
|
+
}
|
|
43
|
+
function MessageItem({ msg, showDetails = false }) {
|
|
44
|
+
const { dot, color: dotColor } = statusDot(msg.status, msg.type);
|
|
45
|
+
if (msg.type === 'user') {
|
|
46
|
+
return (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: "gray", children: "\u276F " }), _jsx(Text, { bold: true, children: msg.content })] }));
|
|
47
|
+
}
|
|
48
|
+
if (msg.type === 'thinking' && msg.status === 'pending') {
|
|
49
|
+
// pending 阶段 thinking 内容存储在 content 字段(由 onThinking 回调实时更新)
|
|
50
|
+
const thinkContent = msg.content && msg.content !== '思考中...' ? msg.content : '';
|
|
51
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: _jsx(Spinner, { type: "dots" }) }), _jsxs(Text, { color: "gray", children: [" ", _jsx(Text, { color: dotColor, children: dot }), " Thinking..."] })] }), thinkContent ? (_jsx(Box, { marginLeft: 2, flexDirection: "column", children: _jsx(Text, { color: "gray", dimColor: true, wrap: "wrap", children: thinkContent.length > 200 ? thinkContent.slice(0, 200) + '...' : thinkContent }) })) : null] }));
|
|
52
|
+
}
|
|
53
|
+
// thinking 完成后持久化渲染,Ctrl+O 展开/折叠
|
|
54
|
+
if (msg.type === 'thinking' && msg.status === 'success' && msg.think) {
|
|
55
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 0, children: [_jsxs(Box, { children: [_jsxs(Text, { color: "cyan", children: ['○', " "] }), _jsx(Text, { color: "gray", children: "Thinking" }), _jsxs(Text, { color: "gray", dimColor: true, children: [" (", msg.think.length, " chars)"] }), !showDetails && _jsx(Text, { color: "gray", dimColor: true, children: " [Ctrl+O \u5C55\u5F00]" })] }), showDetails && (_jsx(Box, { marginLeft: 2, flexDirection: "column", children: _jsx(Text, { color: "gray", dimColor: true, wrap: "wrap", children: msg.think }) }))] }));
|
|
56
|
+
}
|
|
57
|
+
if (msg.type === 'tool_exec') {
|
|
58
|
+
// Bash 工具直接显示命令内容
|
|
59
|
+
const isBash = msg.toolName === 'Bash';
|
|
60
|
+
const bashCmd = isBash && msg.toolArgs?.command ? String(msg.toolArgs.command) : '';
|
|
61
|
+
const toolLabel = isBash && bashCmd ? `Bash(${bashCmd})` : (msg.toolName || 'tool');
|
|
62
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsxs(Text, { color: dotColor, children: [dot, " "] }), isBash && bashCmd ? (_jsxs(Text, { children: [_jsx(Text, { color: "white", bold: true, children: "Bash" }), _jsxs(Text, { color: "gray", children: ["(", bashCmd, ")"] })] })) : (_jsx(Text, { color: "magenta", bold: true, children: toolLabel }))] }), showDetails && msg.toolArgs && !isBash && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "gray", dimColor: true, wrap: "wrap", children: JSON.stringify(msg.toolArgs) }) })), showDetails && msg.toolResult && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "gray", wrap: "wrap", children: msg.toolResult.length > 300 ? msg.toolResult.slice(0, 300) + '…' : msg.toolResult }) })), _jsx(MessageStats, { msg: msg, show: showDetails })] }));
|
|
63
|
+
}
|
|
64
|
+
if (msg.type === 'error') {
|
|
65
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { color: "red", children: [_jsx(Text, { color: dotColor, children: dot }), " ", msg.content] }), _jsx(MessageStats, { msg: msg, show: showDetails })] }));
|
|
66
|
+
}
|
|
67
|
+
if (msg.type === 'reasoning') {
|
|
68
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: dotColor, children: dot }), _jsx(Box, { marginLeft: 1, children: _jsx(MarkdownText, { text: msg.content }) })] }), _jsx(MessageStats, { msg: msg, show: showDetails })] }));
|
|
69
|
+
}
|
|
70
|
+
if (msg.status !== 'pending' && msg.content) {
|
|
71
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: dotColor, children: dot }), _jsx(Box, { marginLeft: 1, children: _jsx(MarkdownText, { text: msg.content, color: "gray" }) })] }), _jsx(MessageStats, { msg: msg, show: showDetails })] }));
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
export default React.memo(MessageItem, (prev, next) => {
|
|
76
|
+
// 仅当消息内容或显示详情变化时才重新渲染
|
|
77
|
+
return prev.msg === next.msg && prev.showDetails === next.showDetails;
|
|
78
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
interface MultilineInputProps {
|
|
2
|
+
value: string;
|
|
3
|
+
onChange: (value: string) => void;
|
|
4
|
+
onSubmit: (value: string) => void;
|
|
5
|
+
onUpArrow?: () => void;
|
|
6
|
+
onDownArrow?: () => void;
|
|
7
|
+
placeholder?: string;
|
|
8
|
+
isActive?: boolean;
|
|
9
|
+
showCursor?: boolean;
|
|
10
|
+
/** 斜杠命令菜单:是否处于激活状态 */
|
|
11
|
+
slashMenuActive?: boolean;
|
|
12
|
+
/** 斜杠命令菜单:选中项上移 */
|
|
13
|
+
onSlashMenuUp?: () => void;
|
|
14
|
+
/** 斜杠命令菜单:选中项下移 */
|
|
15
|
+
onSlashMenuDown?: () => void;
|
|
16
|
+
/** 斜杠命令菜单:确认选中 */
|
|
17
|
+
onSlashMenuSelect?: () => void;
|
|
18
|
+
/** 斜杠命令菜单:关闭菜单 */
|
|
19
|
+
onSlashMenuClose?: () => void;
|
|
20
|
+
/** 输入为空时按 Tab 的回调(用于填入 placeholder) */
|
|
21
|
+
onTabFillPlaceholder?: () => void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 多行文本输入组件,支持光标移动和粘贴折叠。
|
|
25
|
+
*
|
|
26
|
+
* - Enter: 提交(占位符会被展开为真实内容)
|
|
27
|
+
* - Alt/Option+Enter: 换行
|
|
28
|
+
* - ←→: 左右移动光标
|
|
29
|
+
* - ↑↓: 上下行移动光标(单行时触发 onUpArrow/onDownArrow)
|
|
30
|
+
* - Backspace: 删除光标前一个字符(占位符整体删除)
|
|
31
|
+
* - 粘贴多行内容: 折叠为 [Pasted text #N +X lines] 占位符
|
|
32
|
+
*/
|
|
33
|
+
export default function MultilineInput({ value, onChange, onSubmit, onUpArrow, onDownArrow, placeholder, isActive, showCursor, slashMenuActive, onSlashMenuUp, onSlashMenuDown, onSlashMenuSelect, onSlashMenuClose, onTabFillPlaceholder, }: MultilineInputProps): import("react/jsx-runtime").JSX.Element;
|
|
34
|
+
export {};
|