@aragon-agent/cli 0.6.12 → 0.6.14
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 +27 -0
- package/README.md +108 -31
- package/dist/agent/controller.d.ts +38 -2
- package/dist/agent/controller.js +185 -34
- package/dist/agent/model-profile-settings.d.ts +31 -0
- package/dist/agent/model-profile-settings.js +208 -0
- package/dist/agent/queued-messages.d.ts +12 -0
- package/dist/agent/queued-messages.js +18 -0
- package/dist/agent/reducer.d.ts +13 -1
- package/dist/agent/reducer.js +52 -12
- package/dist/boot/guard.d.ts +3 -3
- package/dist/boot/guard.js +3 -3
- package/dist/cli.js +48 -16
- package/dist/commands/builtins.js +66 -39
- package/dist/commands/queue-command.d.ts +3 -0
- package/dist/commands/queue-command.js +5 -0
- package/dist/commands/registry.d.ts +3 -0
- package/dist/compaction/child.d.ts +4 -2
- package/dist/compaction/child.js +8 -1
- package/dist/compaction/compactor.d.ts +7 -2
- package/dist/compaction/compactor.js +21 -16
- package/dist/compaction/context-command.js +10 -3
- package/dist/compaction/meter.js +2 -0
- package/dist/compaction/pressure.d.ts +1 -0
- package/dist/compaction/pressure.js +1 -0
- package/dist/compaction/types.d.ts +2 -0
- package/dist/compaction/wiring.d.ts +4 -3
- package/dist/compaction/wiring.js +10 -2
- package/dist/config/cli-commands.d.ts +2 -0
- package/dist/config/cli-commands.js +16 -4
- package/dist/config/load.d.ts +5 -2
- package/dist/config/load.js +23 -12
- package/dist/config/model-profile-resolution.d.ts +20 -0
- package/dist/config/model-profile-resolution.js +81 -0
- package/dist/config/model-profile-store.d.ts +67 -0
- package/dist/config/model-profile-store.js +153 -0
- package/dist/config/model-profiles.d.ts +44 -0
- package/dist/config/model-profiles.js +121 -0
- package/dist/config/schema.d.ts +11 -2
- package/dist/config/store.d.ts +11 -1
- package/dist/config/store.js +43 -27
- package/dist/fast/resolve.d.ts +2 -1
- package/dist/fast/resolve.js +6 -4
- package/dist/fast/wiring.d.ts +5 -3
- package/dist/fast/wiring.js +8 -4
- package/dist/input/stdin-filter.js +3 -2
- package/dist/logging/install.js +4 -2
- package/dist/logging/secret-registry.d.ts +2 -0
- package/dist/logging/secret-registry.js +18 -0
- package/dist/team/runtime.d.ts +2 -1
- package/dist/team/subagent.d.ts +3 -1
- package/dist/team/subagent.js +3 -1
- package/dist/ui/ActivityLine.d.ts +6 -70
- package/dist/ui/ActivityLine.js +12 -34
- package/dist/ui/App.d.ts +1 -0
- package/dist/ui/App.js +116 -215
- package/dist/ui/BottomStatusRow.d.ts +6 -86
- package/dist/ui/BottomStatusRow.js +11 -26
- package/dist/ui/Composer.d.ts +16 -83
- package/dist/ui/Composer.js +9 -100
- package/dist/ui/Logo.d.ts +3 -5
- package/dist/ui/Logo.js +4 -8
- package/dist/ui/PromptInput.d.ts +4 -1
- package/dist/ui/PromptInput.js +44 -35
- package/dist/ui/QueuePanel.d.ts +8 -0
- package/dist/ui/QueuePanel.js +9 -0
- package/dist/ui/QueueStatusRow.d.ts +2 -0
- package/dist/ui/QueueStatusRow.js +1 -1
- package/dist/ui/StatusBar.d.ts +13 -149
- package/dist/ui/StatusBar.js +27 -142
- package/dist/ui/capabilities.d.ts +2 -0
- package/dist/ui/capabilities.js +6 -0
- package/dist/ui/clipboard-task.d.ts +29 -0
- package/dist/ui/clipboard-task.js +66 -0
- package/dist/ui/clipboard.d.ts +29 -50
- package/dist/ui/clipboard.js +141 -75
- package/dist/ui/color-runtime.d.ts +5 -0
- package/dist/ui/color-runtime.js +29 -0
- package/dist/ui/composer-limits.d.ts +3 -4
- package/dist/ui/composer-limits.js +4 -9
- package/dist/ui/composer-rows.d.ts +1 -1
- package/dist/ui/composer-rows.js +24 -137
- package/dist/ui/editor-navigation.d.ts +45 -0
- package/dist/ui/editor-navigation.js +85 -0
- package/dist/ui/editor-reducer.d.ts +4 -0
- package/dist/ui/editor-reducer.js +29 -15
- package/dist/ui/glyphs.js +10 -8
- package/dist/ui/ink-runtime.d.ts +3 -0
- package/dist/ui/ink-runtime.js +30 -0
- package/dist/ui/interaction-copy.d.ts +77 -0
- package/dist/ui/interaction-copy.js +40 -0
- package/dist/ui/interaction-hints.d.ts +18 -0
- package/dist/ui/interaction-hints.js +92 -0
- package/dist/ui/layout/budget.js +1 -1
- package/dist/ui/layout/queue-layout.d.ts +19 -0
- package/dist/ui/layout/queue-layout.js +26 -0
- package/dist/ui/layout/status-layout.d.ts +55 -0
- package/dist/ui/layout/status-layout.js +110 -0
- package/dist/ui/layout/todo-layout.d.ts +1 -0
- package/dist/ui/layout/todo-layout.js +1 -1
- package/dist/ui/overlays/HelpOverlay.js +8 -8
- package/dist/ui/overlays/ModelProfileEditor.d.ts +12 -0
- package/dist/ui/overlays/ModelProfileEditor.js +103 -0
- package/dist/ui/overlays/ModelProfilePicker.d.ts +35 -0
- package/dist/ui/overlays/ModelProfilePicker.js +167 -0
- package/dist/ui/overlays/QueueOverlay.d.ts +22 -0
- package/dist/ui/overlays/QueueOverlay.js +79 -0
- package/dist/ui/overlays/SettingsScreen.d.ts +9 -2
- package/dist/ui/overlays/SettingsScreen.js +347 -3
- package/dist/ui/overlays/model-profile-state.d.ts +74 -0
- package/dist/ui/overlays/model-profile-state.js +119 -0
- package/dist/ui/render-cache.d.ts +2 -2
- package/dist/ui/render-cache.js +4 -3
- package/dist/ui/run-status-row.d.ts +7 -19
- package/dist/ui/run-status-row.js +2 -18
- package/dist/ui/selection/selection-controller.d.ts +2 -2
- package/dist/ui/selection/selection-controller.js +1 -0
- package/dist/ui/use-wheel-routing.js +2 -1
- package/package.json +2 -2
package/dist/ui/composer-rows.js
CHANGED
|
@@ -18,65 +18,11 @@
|
|
|
18
18
|
* spaces, so it destroys indentation and makes the character-index-to-screen-cell
|
|
19
19
|
* mapping the caret depends on impossible to state, let alone compute.
|
|
20
20
|
*/
|
|
21
|
-
import
|
|
21
|
+
import { buildEditorVisualMap, graphemes, graphemeWidth } from './editor-navigation.js';
|
|
22
22
|
import { tokenSpans } from './paste-tokens.js';
|
|
23
|
-
/**
|
|
24
|
-
* Columns a TAB advances to.
|
|
25
|
-
*
|
|
26
|
-
* TABS ARE EXPANDED TO SPACES FOR DISPLAY ONLY -- the buffer keeps the tab, so
|
|
27
|
-
* the model receives exactly what was pasted. The alternative is to emit the raw
|
|
28
|
-
* tab and let the terminal expand it, which puts the terminal's tab stop (8) and
|
|
29
|
-
* this module's column arithmetic in disagreement: the row would overflow its box
|
|
30
|
-
* and the terminal would wrap it a second time, which is the corruption the whole
|
|
31
|
-
* feature exists to remove.
|
|
32
|
-
*/
|
|
33
|
-
const TAB_WIDTH = 4;
|
|
34
|
-
/**
|
|
35
|
-
* Display width of one code point.
|
|
36
|
-
*
|
|
37
|
-
* The ASCII fast path matters: `layoutComposer` runs on every keystroke and a
|
|
38
|
-
* recalled prompt-history entry can be thousands of characters, so one
|
|
39
|
-
* `string-width` call per character would be the cost this package spent
|
|
40
|
-
* `tui-render-performance` removing.
|
|
41
|
-
*/
|
|
42
|
-
function charWidth(ch) {
|
|
43
|
-
const code = ch.codePointAt(0) ?? 0;
|
|
44
|
-
if (code >= 0x20 && code < 0x7f)
|
|
45
|
-
return 1;
|
|
46
|
-
return stringWidth(ch);
|
|
47
|
-
}
|
|
48
|
-
/** Total display width of a run, using the same table the layout used. */
|
|
23
|
+
/** Width of a displayed run, measured by complete graphemes. */
|
|
49
24
|
export function displayWidth(text) {
|
|
50
|
-
|
|
51
|
-
for (const ch of text)
|
|
52
|
-
w += charWidth(ch);
|
|
53
|
-
return w;
|
|
54
|
-
}
|
|
55
|
-
/** Normalize only the visual projection; the editor keeps its UTF-16 index. */
|
|
56
|
-
function visualCursor(buffer, cursor) {
|
|
57
|
-
let index = Math.max(0, Math.min(buffer.length, cursor));
|
|
58
|
-
const code = buffer.charCodeAt(index);
|
|
59
|
-
const previous = buffer.charCodeAt(index - 1);
|
|
60
|
-
if (code >= 0xdc00 && code <= 0xdfff && previous >= 0xd800 && previous <= 0xdbff)
|
|
61
|
-
index -= 1;
|
|
62
|
-
if (index === buffer.length || buffer[index] === '\n')
|
|
63
|
-
return index;
|
|
64
|
-
const ch = String.fromCodePoint(buffer.codePointAt(index));
|
|
65
|
-
if (ch === '\t' || charWidth(ch) > 0)
|
|
66
|
-
return index;
|
|
67
|
-
const start = buffer.lastIndexOf('\n', index - 1) + 1;
|
|
68
|
-
let nearest = -1;
|
|
69
|
-
let i = start;
|
|
70
|
-
while (i < buffer.length && buffer[i] !== '\n') {
|
|
71
|
-
const point = String.fromCodePoint(buffer.codePointAt(i));
|
|
72
|
-
if (point === '\t' || charWidth(point) > 0) {
|
|
73
|
-
if (i > index)
|
|
74
|
-
return nearest >= 0 ? nearest : i;
|
|
75
|
-
nearest = i;
|
|
76
|
-
}
|
|
77
|
-
i += point.length;
|
|
78
|
-
}
|
|
79
|
-
return nearest >= 0 ? nearest : i;
|
|
25
|
+
return graphemes(text).reduce((width, part) => width + graphemeWidth(part.text), 0);
|
|
80
26
|
}
|
|
81
27
|
/**
|
|
82
28
|
* Wrap `buffer` into rows, locate the caret, and select a window of at most
|
|
@@ -89,88 +35,29 @@ function visualCursor(buffer, cursor) {
|
|
|
89
35
|
*/
|
|
90
36
|
export function layoutComposer(input) {
|
|
91
37
|
const { buffer, active } = input;
|
|
92
|
-
const cursor = visualCursor(buffer, input.cursor);
|
|
93
|
-
const cols = Math.max(1, Math.floor(input.cols));
|
|
94
38
|
const maxRows = input.maxRows >= 1 ? Math.floor(input.maxRows) : 1;
|
|
39
|
+
const map = buildEditorVisualMap(input);
|
|
40
|
+
const cursorRowAbs = active ? map.cursor.row : -1;
|
|
41
|
+
const cursorCol = map.cursor.column;
|
|
95
42
|
const spans = tokenSpans(buffer);
|
|
96
43
|
let spanIndex = 0;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
spanIndex
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
segments = [];
|
|
112
|
-
col = 0;
|
|
113
|
-
};
|
|
114
|
-
const append = (text, width, kind) => {
|
|
115
|
-
const last = segments[segments.length - 1];
|
|
116
|
-
if (last && last.kind === kind)
|
|
117
|
-
segments[segments.length - 1] = { text: last.text + text, kind };
|
|
118
|
-
else
|
|
119
|
-
segments.push({ text, kind });
|
|
120
|
-
col += width;
|
|
121
|
-
};
|
|
122
|
-
const ensureRoom = (width) => {
|
|
123
|
-
if (col > 0 && col + width > cols)
|
|
124
|
-
breakRow();
|
|
125
|
-
};
|
|
126
|
-
const length = buffer.length;
|
|
127
|
-
let i = 0;
|
|
128
|
-
while (i <= length) {
|
|
129
|
-
const atCursor = active && i === cursor && cursorRowAbs === -1;
|
|
130
|
-
if (i === length) {
|
|
131
|
-
if (atCursor) {
|
|
132
|
-
ensureRoom(1);
|
|
133
|
-
cursorRowAbs = rows.length;
|
|
134
|
-
cursorCol = col;
|
|
135
|
-
}
|
|
136
|
-
break;
|
|
137
|
-
}
|
|
138
|
-
const ch = String.fromCodePoint(buffer.codePointAt(i));
|
|
139
|
-
if (ch === '\n') {
|
|
140
|
-
if (atCursor) {
|
|
141
|
-
ensureRoom(1);
|
|
142
|
-
cursorRowAbs = rows.length;
|
|
143
|
-
cursorCol = col;
|
|
144
|
-
}
|
|
145
|
-
breakRow();
|
|
146
|
-
i += 1;
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
const kind = kindAt(i);
|
|
150
|
-
if (ch === '\t') {
|
|
151
|
-
let advance = TAB_WIDTH - (col % TAB_WIDTH);
|
|
152
|
-
if (col > 0 && col + advance > cols) {
|
|
153
|
-
breakRow();
|
|
154
|
-
advance = TAB_WIDTH;
|
|
155
|
-
}
|
|
156
|
-
if (atCursor) {
|
|
157
|
-
cursorRowAbs = rows.length;
|
|
158
|
-
cursorCol = col;
|
|
159
|
-
}
|
|
160
|
-
append(' '.repeat(advance), advance, kind);
|
|
161
|
-
i += 1;
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
const width = charWidth(ch);
|
|
165
|
-
ensureRoom(width);
|
|
166
|
-
if (atCursor) {
|
|
167
|
-
cursorRowAbs = rows.length;
|
|
168
|
-
cursorCol = col;
|
|
44
|
+
const rows = map.rows.map((cells) => {
|
|
45
|
+
const segments = [];
|
|
46
|
+
for (const cell of cells) {
|
|
47
|
+
while (spanIndex < spans.length && spans[spanIndex].end <= cell.index)
|
|
48
|
+
spanIndex += 1;
|
|
49
|
+
const span = spans[spanIndex];
|
|
50
|
+
const kind = span && cell.index >= span.start && cell.index < span.end ? 'token' : 'text';
|
|
51
|
+
const last = segments[segments.length - 1];
|
|
52
|
+
if (last && last.kind === kind)
|
|
53
|
+
segments[segments.length - 1] = {
|
|
54
|
+
text: last.text + cell.text, kind,
|
|
55
|
+
};
|
|
56
|
+
else
|
|
57
|
+
segments.push({ text: cell.text, kind });
|
|
169
58
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
rows.push({ segments });
|
|
59
|
+
return { segments };
|
|
60
|
+
});
|
|
174
61
|
const totalRows = rows.length;
|
|
175
62
|
const cap = Math.max(1, Math.min(maxRows, totalRows));
|
|
176
63
|
let start = Math.max(0, totalRows - cap);
|
|
@@ -213,8 +100,8 @@ export function splitRowAtColumn(row, column) {
|
|
|
213
100
|
const cells = [];
|
|
214
101
|
let leading = '';
|
|
215
102
|
for (const segment of row.segments) {
|
|
216
|
-
for (const ch of segment.text) {
|
|
217
|
-
if (
|
|
103
|
+
for (const { text: ch } of graphemes(segment.text)) {
|
|
104
|
+
if (graphemeWidth(ch) === 0) {
|
|
218
105
|
const last = cells[cells.length - 1];
|
|
219
106
|
if (last)
|
|
220
107
|
cells[cells.length - 1] = { ...last, text: last.text + ch };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface EditorGrapheme {
|
|
2
|
+
readonly text: string;
|
|
3
|
+
readonly index: number;
|
|
4
|
+
readonly end: number;
|
|
5
|
+
}
|
|
6
|
+
/** Segment text without changing its UTF-16 offsets or stored whitespace. */
|
|
7
|
+
export declare function graphemes(text: string): EditorGrapheme[];
|
|
8
|
+
/** Snap an arbitrary UTF-16 offset to a complete grapheme boundary. */
|
|
9
|
+
export declare function snapGrapheme(text: string, cursor: number, right?: boolean): number;
|
|
10
|
+
/** Move one complete grapheme, leaving the two buffer edges unchanged. */
|
|
11
|
+
export declare function stepGrapheme(text: string, cursor: number, direction: 'left' | 'right'): number;
|
|
12
|
+
/** Width of one grapheme (ASCII avoids the Unicode width-table lookup). */
|
|
13
|
+
export declare function graphemeWidth(text: string): number;
|
|
14
|
+
export interface VisualCell extends EditorGrapheme {
|
|
15
|
+
readonly column: number;
|
|
16
|
+
readonly width: number;
|
|
17
|
+
}
|
|
18
|
+
export interface VisualPosition {
|
|
19
|
+
readonly index: number;
|
|
20
|
+
readonly row: number;
|
|
21
|
+
readonly column: number;
|
|
22
|
+
}
|
|
23
|
+
export interface EditorVisualMap {
|
|
24
|
+
readonly rows: VisualCell[][];
|
|
25
|
+
readonly positions: VisualPosition[];
|
|
26
|
+
readonly cursor: VisualPosition;
|
|
27
|
+
}
|
|
28
|
+
/** Map graphemes to hard-wrapped rows, sharing tab stops and caret reservation. */
|
|
29
|
+
export declare function buildEditorVisualMap(input: {
|
|
30
|
+
buffer: string;
|
|
31
|
+
cols: number;
|
|
32
|
+
cursor: number;
|
|
33
|
+
active?: boolean;
|
|
34
|
+
}): EditorVisualMap;
|
|
35
|
+
/** Move to the closest boundary at or left of the preferred display column. */
|
|
36
|
+
export declare function moveVisualCursor(input: {
|
|
37
|
+
buffer: string;
|
|
38
|
+
cursor: number;
|
|
39
|
+
cols: number;
|
|
40
|
+
direction: 'up' | 'down';
|
|
41
|
+
preferredVisualColumn?: number;
|
|
42
|
+
}): {
|
|
43
|
+
cursor: number;
|
|
44
|
+
preferredVisualColumn: number;
|
|
45
|
+
} | null;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** Shared UTF-16 grapheme boundaries and terminal-cell layout for the editor. */
|
|
2
|
+
import stringWidth from 'string-width';
|
|
3
|
+
const segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
|
4
|
+
/** Segment text without changing its UTF-16 offsets or stored whitespace. */
|
|
5
|
+
export function graphemes(text) {
|
|
6
|
+
return Array.from(segmenter.segment(text), ({ segment, index }) => ({
|
|
7
|
+
text: segment, index, end: index + segment.length,
|
|
8
|
+
}));
|
|
9
|
+
}
|
|
10
|
+
/** Snap an arbitrary UTF-16 offset to a complete grapheme boundary. */
|
|
11
|
+
export function snapGrapheme(text, cursor, right = false) {
|
|
12
|
+
const clamped = Math.max(0, Math.min(text.length, cursor));
|
|
13
|
+
for (const part of segmenter.segment(text)) {
|
|
14
|
+
const end = part.index + part.segment.length;
|
|
15
|
+
if (clamped > part.index && clamped < end)
|
|
16
|
+
return right ? end : part.index;
|
|
17
|
+
if (part.index >= clamped)
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
return clamped;
|
|
21
|
+
}
|
|
22
|
+
/** Move one complete grapheme, leaving the two buffer edges unchanged. */
|
|
23
|
+
export function stepGrapheme(text, cursor, direction) {
|
|
24
|
+
if (direction === 'left')
|
|
25
|
+
return snapGrapheme(text, Math.max(0, cursor - 1));
|
|
26
|
+
return snapGrapheme(text, Math.min(text.length, cursor + 1), true);
|
|
27
|
+
}
|
|
28
|
+
/** Width of one grapheme (ASCII avoids the Unicode width-table lookup). */
|
|
29
|
+
export function graphemeWidth(text) {
|
|
30
|
+
if (text.length === 1 && text >= ' ' && text <= '~')
|
|
31
|
+
return 1;
|
|
32
|
+
return stringWidth(text);
|
|
33
|
+
}
|
|
34
|
+
/** Map graphemes to hard-wrapped rows, sharing tab stops and caret reservation. */
|
|
35
|
+
export function buildEditorVisualMap(input) {
|
|
36
|
+
const cols = Math.max(1, Math.floor(input.cols));
|
|
37
|
+
const cursor = snapGrapheme(input.buffer, input.cursor);
|
|
38
|
+
const rows = [[]];
|
|
39
|
+
const positions = [];
|
|
40
|
+
let column = 0;
|
|
41
|
+
const breakRow = () => { rows.push([]); column = 0; };
|
|
42
|
+
const locate = (index) => {
|
|
43
|
+
positions.push({ index, row: rows.length - 1, column });
|
|
44
|
+
};
|
|
45
|
+
for (const part of graphemes(input.buffer)) {
|
|
46
|
+
if (part.text === '\n') {
|
|
47
|
+
if (input.active !== false && part.index === cursor && column >= cols)
|
|
48
|
+
breakRow();
|
|
49
|
+
locate(part.index);
|
|
50
|
+
breakRow();
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
let width = part.text === '\t' ? Math.min(cols, 4 - column % 4)
|
|
54
|
+
: graphemeWidth(part.text);
|
|
55
|
+
if (column > 0 && column + width > cols) {
|
|
56
|
+
breakRow();
|
|
57
|
+
if (part.text === '\t')
|
|
58
|
+
width = Math.min(cols, 4);
|
|
59
|
+
}
|
|
60
|
+
locate(part.index);
|
|
61
|
+
rows[rows.length - 1].push({ ...part,
|
|
62
|
+
text: part.text === '\t' ? ' '.repeat(width) : part.text, column, width });
|
|
63
|
+
column += width;
|
|
64
|
+
}
|
|
65
|
+
if (input.active !== false && cursor === input.buffer.length && column >= cols)
|
|
66
|
+
breakRow();
|
|
67
|
+
locate(input.buffer.length);
|
|
68
|
+
const caret = positions.find((position) => position.index === cursor);
|
|
69
|
+
return { rows, positions, cursor: caret };
|
|
70
|
+
}
|
|
71
|
+
/** Move to the closest boundary at or left of the preferred display column. */
|
|
72
|
+
export function moveVisualCursor(input) {
|
|
73
|
+
const map = buildEditorVisualMap(input);
|
|
74
|
+
const row = map.cursor.row + (input.direction === 'up' ? -1 : 1);
|
|
75
|
+
if (row < 0 || row >= map.rows.length)
|
|
76
|
+
return null;
|
|
77
|
+
const preferredVisualColumn = input.preferredVisualColumn ?? map.cursor.column;
|
|
78
|
+
const candidates = map.positions.filter((position) => position.row === row);
|
|
79
|
+
let target = candidates[0];
|
|
80
|
+
for (const position of candidates) {
|
|
81
|
+
if (position.column <= preferredVisualColumn)
|
|
82
|
+
target = position;
|
|
83
|
+
}
|
|
84
|
+
return target ? { cursor: target.index, preferredVisualColumn } : null;
|
|
85
|
+
}
|
|
@@ -26,6 +26,7 @@ import { type PasteRecord } from './paste-tokens.js';
|
|
|
26
26
|
export interface EditorState {
|
|
27
27
|
buffer: string;
|
|
28
28
|
cursor: number;
|
|
29
|
+
preferredVisualColumn?: number;
|
|
29
30
|
historyIndex: number | null;
|
|
30
31
|
dismissed: boolean;
|
|
31
32
|
sel: number;
|
|
@@ -70,9 +71,12 @@ export type EditorAction = {
|
|
|
70
71
|
cursor: number;
|
|
71
72
|
} | {
|
|
72
73
|
type: 'backspace';
|
|
74
|
+
} | {
|
|
75
|
+
type: 'delete';
|
|
73
76
|
} | {
|
|
74
77
|
type: 'moveCursor';
|
|
75
78
|
cursor: number;
|
|
79
|
+
preferredVisualColumn?: number;
|
|
76
80
|
} | {
|
|
77
81
|
type: 'recall';
|
|
78
82
|
buffer: string;
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* Extracted from the component so it is unit-testable without mounting Ink.
|
|
24
24
|
*/
|
|
25
25
|
import { expandRangeOverTokens, formatPasteToken, makePasteRecord, referencedIds, shouldCollapse, snapOutOfToken, } from './paste-tokens.js';
|
|
26
|
+
import { snapGrapheme, stepGrapheme } from './editor-navigation.js';
|
|
26
27
|
const NO_PASTES = new Map();
|
|
27
28
|
export const INITIAL_EDITOR_STATE = {
|
|
28
29
|
buffer: '',
|
|
@@ -42,7 +43,9 @@ export const INITIAL_EDITOR_STATE = {
|
|
|
42
43
|
* typing again, so suggestions should come back), and re-homes the selection
|
|
43
44
|
* (the candidate list is about to be recomputed).
|
|
44
45
|
*/
|
|
45
|
-
const DRAFT_FLAGS = {
|
|
46
|
+
const DRAFT_FLAGS = {
|
|
47
|
+
historyIndex: null, dismissed: false, sel: 0, preferredVisualColumn: undefined,
|
|
48
|
+
};
|
|
46
49
|
/**
|
|
47
50
|
* Drop payloads the buffer no longer references (I-7).
|
|
48
51
|
*
|
|
@@ -87,12 +90,13 @@ export function editorReducer(state, action) {
|
|
|
87
90
|
case 'insert': {
|
|
88
91
|
// D-8: typing can never land INSIDE a token. A no-op on a buffer with no
|
|
89
92
|
// tokens, which is every buffer until the first collapse.
|
|
90
|
-
const cursor = snapOutOfToken(state.buffer, state.cursor);
|
|
93
|
+
const cursor = snapOutOfToken(state.buffer, snapGrapheme(state.buffer, state.cursor));
|
|
94
|
+
const buffer = state.buffer.slice(0, cursor) + action.text + state.buffer.slice(cursor);
|
|
91
95
|
return withPrune({
|
|
92
96
|
...state,
|
|
93
97
|
...DRAFT_FLAGS,
|
|
94
|
-
buffer
|
|
95
|
-
cursor: cursor + action.text.length,
|
|
98
|
+
buffer,
|
|
99
|
+
cursor: snapGrapheme(buffer, cursor + action.text.length, true),
|
|
96
100
|
});
|
|
97
101
|
}
|
|
98
102
|
case 'replace':
|
|
@@ -100,24 +104,29 @@ export function editorReducer(state, action) {
|
|
|
100
104
|
...state,
|
|
101
105
|
...DRAFT_FLAGS,
|
|
102
106
|
buffer: action.buffer,
|
|
103
|
-
cursor: action.cursor,
|
|
107
|
+
cursor: snapGrapheme(action.buffer, action.cursor),
|
|
104
108
|
});
|
|
105
|
-
case 'backspace':
|
|
106
|
-
|
|
109
|
+
case 'backspace':
|
|
110
|
+
case 'delete': {
|
|
111
|
+
const cursor = snapGrapheme(state.buffer, state.cursor);
|
|
112
|
+
const backwards = action.type === 'backspace';
|
|
113
|
+
if (backwards ? cursor <= 0 : cursor >= state.buffer.length)
|
|
107
114
|
return state;
|
|
108
115
|
// G5: one `Backspace` immediately after `...lines]` removes the whole
|
|
109
116
|
// token and releases its payload, in ONE dispatch.
|
|
110
|
-
const
|
|
117
|
+
const other = stepGrapheme(state.buffer, cursor, backwards ? 'left' : 'right');
|
|
118
|
+
const range = expandRangeOverTokens(state.buffer, backwards ? other : cursor, backwards ? cursor : other);
|
|
119
|
+
const buffer = state.buffer.slice(0, range.from) + state.buffer.slice(range.to);
|
|
111
120
|
return withPrune({
|
|
112
121
|
...state,
|
|
113
122
|
...DRAFT_FLAGS,
|
|
114
|
-
buffer
|
|
115
|
-
cursor: range.from,
|
|
123
|
+
buffer,
|
|
124
|
+
cursor: snapGrapheme(buffer, range.from),
|
|
116
125
|
});
|
|
117
126
|
}
|
|
118
127
|
case 'input': {
|
|
119
128
|
let buffer = state.buffer;
|
|
120
|
-
let cursor = snapOutOfToken(buffer, state.cursor);
|
|
129
|
+
let cursor = snapOutOfToken(buffer, snapGrapheme(buffer, state.cursor));
|
|
121
130
|
let pastes = null;
|
|
122
131
|
for (const segment of action.segments) {
|
|
123
132
|
const collapse = segment.kind === 'paste' && shouldCollapse(segment.text);
|
|
@@ -129,7 +138,7 @@ export function editorReducer(state, action) {
|
|
|
129
138
|
pastes.set(record.id, record);
|
|
130
139
|
}
|
|
131
140
|
buffer = buffer.slice(0, cursor) + text + buffer.slice(cursor);
|
|
132
|
-
cursor
|
|
141
|
+
cursor = snapGrapheme(buffer, cursor + text.length, true);
|
|
133
142
|
}
|
|
134
143
|
return withPrune({
|
|
135
144
|
...state,
|
|
@@ -139,11 +148,15 @@ export function editorReducer(state, action) {
|
|
|
139
148
|
...(pastes ? { pastes } : {}),
|
|
140
149
|
});
|
|
141
150
|
}
|
|
142
|
-
case 'moveCursor':
|
|
151
|
+
case 'moveCursor': {
|
|
143
152
|
// NO DRAFT-FLAG RESET. Moving the caret is not an edit: clearing
|
|
144
153
|
// `historyIndex` here would end a history walk the moment the user pressed
|
|
145
154
|
// ← to look at what they recalled.
|
|
146
|
-
|
|
155
|
+
const cursor = snapGrapheme(state.buffer, action.cursor);
|
|
156
|
+
const preferredVisualColumn = action.preferredVisualColumn;
|
|
157
|
+
return state.cursor === cursor && state.preferredVisualColumn === preferredVisualColumn
|
|
158
|
+
? state : { ...state, cursor, preferredVisualColumn };
|
|
159
|
+
}
|
|
147
160
|
case 'recall':
|
|
148
161
|
// `historyIndex` is SET, not reset, which is why recall cannot reuse
|
|
149
162
|
// `replace` — the two differ in exactly the field that keeps the walk
|
|
@@ -155,7 +168,8 @@ export function editorReducer(state, action) {
|
|
|
155
168
|
return withPrune({
|
|
156
169
|
...state,
|
|
157
170
|
buffer: action.buffer,
|
|
158
|
-
cursor: action.cursor,
|
|
171
|
+
cursor: snapGrapheme(action.buffer, action.cursor),
|
|
172
|
+
preferredVisualColumn: undefined,
|
|
159
173
|
historyIndex: action.historyIndex,
|
|
160
174
|
});
|
|
161
175
|
case 'select':
|
package/dist/ui/glyphs.js
CHANGED
|
@@ -24,18 +24,20 @@ const UNICODE_GLYPHS = {
|
|
|
24
24
|
thinking: '✱',
|
|
25
25
|
toolPending: '◦',
|
|
26
26
|
toolRunning: '◍',
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
// U+2714 has conflicting emoji widths in Ink and string-width. U+2713 is
|
|
28
|
+
// consistently one cell, including beside the right-edge scrollbar.
|
|
29
|
+
toolDone: '✓',
|
|
30
|
+
toolError: '×',
|
|
31
|
+
info: 'ⓘ',
|
|
30
32
|
warn: '▲',
|
|
31
|
-
error: '
|
|
33
|
+
error: '×',
|
|
32
34
|
bullet: '•',
|
|
33
35
|
spinnerStill: '·',
|
|
34
36
|
gaugeFull: '█',
|
|
35
37
|
gaugeEmpty: '░',
|
|
36
38
|
keyOn: '●',
|
|
37
39
|
keyOff: '○',
|
|
38
|
-
boxChecked: '
|
|
40
|
+
boxChecked: '☒',
|
|
39
41
|
boxEmpty: '☐',
|
|
40
42
|
wordmark: '◇',
|
|
41
43
|
ellipsis: '…',
|
|
@@ -60,10 +62,10 @@ const UNICODE_GLYPHS = {
|
|
|
60
62
|
maskDot: '•',
|
|
61
63
|
boxStyle: 'round',
|
|
62
64
|
teamAgent: '◆',
|
|
63
|
-
teamMail: '
|
|
65
|
+
teamMail: '⇄',
|
|
64
66
|
todoPending: '○',
|
|
65
67
|
todoActive: '▸',
|
|
66
|
-
todoDone: '
|
|
68
|
+
todoDone: '✓',
|
|
67
69
|
retry: '↻',
|
|
68
70
|
compaction: '⤓',
|
|
69
71
|
queued: '◷',
|
|
@@ -78,7 +80,7 @@ const UNICODE_GLYPHS = {
|
|
|
78
80
|
ask_user: '?',
|
|
79
81
|
submit_plan: '◈',
|
|
80
82
|
task: '◆',
|
|
81
|
-
todo_write: '
|
|
83
|
+
todo_write: '≡',
|
|
82
84
|
},
|
|
83
85
|
toolDefault: '•',
|
|
84
86
|
};
|
package/dist/ui/ink-runtime.d.ts
CHANGED
|
@@ -1 +1,4 @@
|
|
|
1
|
+
import type { TermCapabilities } from './capabilities.js';
|
|
1
2
|
export declare const render: (node: import("react").ReactNode, options?: NodeJS.WriteStream | import("ink").RenderOptions) => import("ink").Instance;
|
|
3
|
+
/** Mount the TUI with its resolved colors, restoring all renderers on exit. */
|
|
4
|
+
export declare function renderTui(node: Parameters<typeof render>[0], options: Parameters<typeof render>[1], caps: TermCapabilities): ReturnType<typeof render>;
|
package/dist/ui/ink-runtime.js
CHANGED
|
@@ -19,3 +19,33 @@ async function loadInk() {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
export const { render } = await loadInk();
|
|
22
|
+
// Keep asynchronous dependency resolution after Ink's CI initialization. A
|
|
23
|
+
// static import here would let sibling UI modules load Ink before CI is reset.
|
|
24
|
+
const { useTuiColors } = await import('./color-runtime.js');
|
|
25
|
+
/** Mount the TUI with its resolved colors, restoring all renderers on exit. */
|
|
26
|
+
export function renderTui(node, options, caps) {
|
|
27
|
+
const restore = useTuiColors(caps.colorLevel);
|
|
28
|
+
try {
|
|
29
|
+
// Ink creates its exit promise lazily and does not replay earlier exits.
|
|
30
|
+
// Mount the host first, then subscribe before application render or layout
|
|
31
|
+
// effects can fail or call useApp().exit().
|
|
32
|
+
const instance = render(null, options);
|
|
33
|
+
void instance.waitUntilExit().then(restore, restore);
|
|
34
|
+
instance.rerender(node);
|
|
35
|
+
return {
|
|
36
|
+
...instance,
|
|
37
|
+
unmount() {
|
|
38
|
+
try {
|
|
39
|
+
instance.unmount();
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
restore();
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
restore();
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/** Product copy is centralized here; Unicode escapes keep UI source ASCII-only. */
|
|
2
|
+
export declare const interactionCopy: {
|
|
3
|
+
readonly copied: "已复制";
|
|
4
|
+
readonly lines: "行";
|
|
5
|
+
readonly copySent: "复制请求已发送,无法确认剪贴板";
|
|
6
|
+
readonly copyReasons: {
|
|
7
|
+
readonly empty: "没有可复制的内容";
|
|
8
|
+
readonly unavailable: "剪贴板工具不可用";
|
|
9
|
+
readonly write: "写入剪贴板失败";
|
|
10
|
+
readonly timeout: "剪贴板工具超时";
|
|
11
|
+
readonly 'too-large': "内容超过剪贴板限制";
|
|
12
|
+
readonly cancelled: "复制已取消";
|
|
13
|
+
};
|
|
14
|
+
readonly stopServicesShort: "^C 停服务";
|
|
15
|
+
readonly approveTool: "y 批准";
|
|
16
|
+
readonly rejectTool: "n/Enter 拒绝";
|
|
17
|
+
readonly saveSettings: "Enter 保存";
|
|
18
|
+
readonly modeLabel: "切换模式";
|
|
19
|
+
readonly idle: readonly ["空闲", "空闲"];
|
|
20
|
+
readonly starting: readonly ["启动", "启动中"];
|
|
21
|
+
readonly waiting: readonly ["等待", "等待模型"];
|
|
22
|
+
readonly thinking: readonly ["思考", "正在思考"];
|
|
23
|
+
readonly generating: readonly ["生成", "正在生成内容"];
|
|
24
|
+
readonly preparingTool: readonly ["工具", "正在准备工具"];
|
|
25
|
+
readonly tool: readonly ["工具", "正在运行工具"];
|
|
26
|
+
readonly confirming: readonly ["确认", "等待用户确认"];
|
|
27
|
+
readonly stopping: readonly ["停止中", "停止中"];
|
|
28
|
+
readonly compacting: readonly ["压缩", "正在压缩上下文"];
|
|
29
|
+
readonly retrying: readonly ["重试", "等待重试"];
|
|
30
|
+
readonly ended: readonly ["结束", "已结束"];
|
|
31
|
+
readonly interrupted: readonly ["中断", "已中断"];
|
|
32
|
+
readonly failed: readonly ["失败", "运行失败"];
|
|
33
|
+
readonly readFile: readonly ["读文件", "正在读取文件"];
|
|
34
|
+
readonly writeFile: readonly ["改文件", "正在修改文件"];
|
|
35
|
+
readonly command: readonly ["命令", "正在执行命令"];
|
|
36
|
+
readonly queue: "队";
|
|
37
|
+
readonly pending: "待处理";
|
|
38
|
+
readonly context: "上下文";
|
|
39
|
+
readonly contextUnknown: "上下文未知";
|
|
40
|
+
readonly sessionInput: "会话 输入";
|
|
41
|
+
readonly output: "输出";
|
|
42
|
+
readonly estimatedCost: "会话估算 USD";
|
|
43
|
+
readonly speed: "本轮均速";
|
|
44
|
+
readonly elapsed: "耗时";
|
|
45
|
+
readonly services: "服务";
|
|
46
|
+
readonly send: "Enter 发送";
|
|
47
|
+
readonly enqueue: "Enter 入队";
|
|
48
|
+
readonly newline: "^J 换行";
|
|
49
|
+
readonly sendLong: "Enter:发送";
|
|
50
|
+
readonly enqueueLong: "Enter:加入队列";
|
|
51
|
+
readonly newlineLong: "Ctrl+J:换行";
|
|
52
|
+
readonly interrupt: "Esc×2 中断";
|
|
53
|
+
readonly interruptAgain: "再按 Esc 中断";
|
|
54
|
+
readonly forceStop: "再按 Esc 强制停止";
|
|
55
|
+
readonly startingHint: "启动中,请稍候";
|
|
56
|
+
readonly cancelStart: "Esc×2 取消启动";
|
|
57
|
+
readonly cancelStartAgain: "再按 Esc 取消启动";
|
|
58
|
+
readonly copy: "Ctrl+C 复制";
|
|
59
|
+
readonly copying: "正在复制";
|
|
60
|
+
readonly copyFailed: "复制失败";
|
|
61
|
+
readonly copyCleanup: "复制失败,正在清理";
|
|
62
|
+
readonly close: "Esc 关闭";
|
|
63
|
+
readonly closeCompletion: "Esc 关闭补全";
|
|
64
|
+
readonly executeCommand: "Enter 执行命令";
|
|
65
|
+
readonly complete: "Tab 补全";
|
|
66
|
+
readonly select: "Up/Down 选择";
|
|
67
|
+
readonly scroll: "PgUp/PgDn 滚动";
|
|
68
|
+
readonly confirm: "Enter 确认";
|
|
69
|
+
readonly stopServices: "Ctrl+C 停止服务";
|
|
70
|
+
readonly exit: "Ctrl+C×2 退出";
|
|
71
|
+
readonly help: "/help 帮助";
|
|
72
|
+
readonly shiftNewline: "Shift+Enter 换行需终端支持";
|
|
73
|
+
readonly mode: "Shift+Tab 切换模式";
|
|
74
|
+
readonly update: "/update 更新";
|
|
75
|
+
readonly idlePlaceholder: "输入任务或问题…";
|
|
76
|
+
readonly runningPlaceholder: "输入补充说明…";
|
|
77
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** Product copy is centralized here; Unicode escapes keep UI source ASCII-only. */
|
|
2
|
+
export const interactionCopy = {
|
|
3
|
+
copied: '\u5df2\u590d\u5236', lines: '\u884c',
|
|
4
|
+
copySent: '\u590d\u5236\u8bf7\u6c42\u5df2\u53d1\u9001\uff0c\u65e0\u6cd5\u786e\u8ba4\u526a\u8d34\u677f',
|
|
5
|
+
copyReasons: {
|
|
6
|
+
empty: '\u6ca1\u6709\u53ef\u590d\u5236\u7684\u5185\u5bb9',
|
|
7
|
+
unavailable: '\u526a\u8d34\u677f\u5de5\u5177\u4e0d\u53ef\u7528',
|
|
8
|
+
write: '\u5199\u5165\u526a\u8d34\u677f\u5931\u8d25',
|
|
9
|
+
timeout: '\u526a\u8d34\u677f\u5de5\u5177\u8d85\u65f6',
|
|
10
|
+
'too-large': '\u5185\u5bb9\u8d85\u8fc7\u526a\u8d34\u677f\u9650\u5236',
|
|
11
|
+
cancelled: '\u590d\u5236\u5df2\u53d6\u6d88',
|
|
12
|
+
},
|
|
13
|
+
stopServicesShort: '^C \u505c\u670d\u52a1',
|
|
14
|
+
approveTool: 'y \u6279\u51c6', rejectTool: 'n/Enter \u62d2\u7edd',
|
|
15
|
+
saveSettings: 'Enter \u4fdd\u5b58',
|
|
16
|
+
modeLabel: '\u5207\u6362\u6a21\u5f0f',
|
|
17
|
+
idle: ['\u7a7a\u95f2', '\u7a7a\u95f2'], starting: ['\u542f\u52a8', '\u542f\u52a8\u4e2d'], waiting: ['\u7b49\u5f85', '\u7b49\u5f85\u6a21\u578b'],
|
|
18
|
+
thinking: ['\u601d\u8003', '\u6b63\u5728\u601d\u8003'], generating: ['\u751f\u6210', '\u6b63\u5728\u751f\u6210\u5185\u5bb9'],
|
|
19
|
+
preparingTool: ['\u5de5\u5177', '\u6b63\u5728\u51c6\u5907\u5de5\u5177'], tool: ['\u5de5\u5177', '\u6b63\u5728\u8fd0\u884c\u5de5\u5177'],
|
|
20
|
+
confirming: ['\u786e\u8ba4', '\u7b49\u5f85\u7528\u6237\u786e\u8ba4'], stopping: ['\u505c\u6b62\u4e2d', '\u505c\u6b62\u4e2d'],
|
|
21
|
+
compacting: ['\u538b\u7f29', '\u6b63\u5728\u538b\u7f29\u4e0a\u4e0b\u6587'], retrying: ['\u91cd\u8bd5', '\u7b49\u5f85\u91cd\u8bd5'],
|
|
22
|
+
ended: ['\u7ed3\u675f', '\u5df2\u7ed3\u675f'], interrupted: ['\u4e2d\u65ad', '\u5df2\u4e2d\u65ad'], failed: ['\u5931\u8d25', '\u8fd0\u884c\u5931\u8d25'],
|
|
23
|
+
readFile: ['\u8bfb\u6587\u4ef6', '\u6b63\u5728\u8bfb\u53d6\u6587\u4ef6'], writeFile: ['\u6539\u6587\u4ef6', '\u6b63\u5728\u4fee\u6539\u6587\u4ef6'],
|
|
24
|
+
command: ['\u547d\u4ee4', '\u6b63\u5728\u6267\u884c\u547d\u4ee4'],
|
|
25
|
+
queue: '\u961f', pending: '\u5f85\u5904\u7406', context: '\u4e0a\u4e0b\u6587', contextUnknown: '\u4e0a\u4e0b\u6587\u672a\u77e5',
|
|
26
|
+
sessionInput: '\u4f1a\u8bdd \u8f93\u5165', output: '\u8f93\u51fa', estimatedCost: '\u4f1a\u8bdd\u4f30\u7b97 USD',
|
|
27
|
+
speed: '\u672c\u8f6e\u5747\u901f', elapsed: '\u8017\u65f6', services: '\u670d\u52a1',
|
|
28
|
+
send: 'Enter \u53d1\u9001', enqueue: 'Enter \u5165\u961f', newline: '^J \u6362\u884c',
|
|
29
|
+
sendLong: 'Enter\uff1a\u53d1\u9001', enqueueLong: 'Enter\uff1a\u52a0\u5165\u961f\u5217', newlineLong: 'Ctrl+J\uff1a\u6362\u884c',
|
|
30
|
+
interrupt: 'Esc\u00d72 \u4e2d\u65ad', interruptAgain: '\u518d\u6309 Esc \u4e2d\u65ad', forceStop: '\u518d\u6309 Esc \u5f3a\u5236\u505c\u6b62',
|
|
31
|
+
startingHint: '\u542f\u52a8\u4e2d\uff0c\u8bf7\u7a0d\u5019', cancelStart: 'Esc\u00d72 \u53d6\u6d88\u542f\u52a8',
|
|
32
|
+
cancelStartAgain: '\u518d\u6309 Esc \u53d6\u6d88\u542f\u52a8',
|
|
33
|
+
copy: 'Ctrl+C \u590d\u5236', copying: '\u6b63\u5728\u590d\u5236', copyFailed: '\u590d\u5236\u5931\u8d25', copyCleanup: '\u590d\u5236\u5931\u8d25\uff0c\u6b63\u5728\u6e05\u7406',
|
|
34
|
+
close: 'Esc \u5173\u95ed', closeCompletion: 'Esc \u5173\u95ed\u8865\u5168', executeCommand: 'Enter \u6267\u884c\u547d\u4ee4',
|
|
35
|
+
complete: 'Tab \u8865\u5168', select: 'Up/Down \u9009\u62e9', scroll: 'PgUp/PgDn \u6eda\u52a8',
|
|
36
|
+
confirm: 'Enter \u786e\u8ba4', stopServices: 'Ctrl+C \u505c\u6b62\u670d\u52a1', exit: 'Ctrl+C\u00d72 \u9000\u51fa',
|
|
37
|
+
help: '/help \u5e2e\u52a9', shiftNewline: 'Shift+Enter \u6362\u884c\u9700\u7ec8\u7aef\u652f\u6301',
|
|
38
|
+
mode: 'Shift+Tab \u5207\u6362\u6a21\u5f0f', update: '/update \u66f4\u65b0',
|
|
39
|
+
idlePlaceholder: '\u8f93\u5165\u4efb\u52a1\u6216\u95ee\u9898\u2026', runningPlaceholder: '\u8f93\u5165\u8865\u5145\u8bf4\u660e\u2026',
|
|
40
|
+
};
|