xgen-dex-cli 1.4.1 → 1.6.0
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.
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DexError,
|
|
3
|
+
dataDirectory,
|
|
3
4
|
publicError
|
|
4
5
|
} from "./chunk-ULMEXJOR.js";
|
|
5
6
|
|
|
@@ -8,11 +9,11 @@ import { render } from "ink";
|
|
|
8
9
|
import { stdout } from "node:process";
|
|
9
10
|
|
|
10
11
|
// src/tui/app.tsx
|
|
11
|
-
import { useCallback, useEffect as
|
|
12
|
+
import { useCallback, useEffect as useEffect8, useState as useState11 } from "react";
|
|
12
13
|
import { Box as Box10, Text as Text10, useApp as useApp2, useInput as useInput9 } from "ink";
|
|
13
14
|
|
|
14
15
|
// src/tui/dashboard.tsx
|
|
15
|
-
import { useEffect as
|
|
16
|
+
import { useEffect as useEffect6, useReducer, useRef as useRef3, useState as useState7 } from "react";
|
|
16
17
|
import { Box as Box6, Text as Text6, useApp, useInput as useInput5 } from "ink";
|
|
17
18
|
|
|
18
19
|
// src/tui/chat-state.ts
|
|
@@ -134,17 +135,161 @@ function chatReducer(state, action) {
|
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
|
|
138
|
+
// src/tui/measure.ts
|
|
139
|
+
import { useEffect, useRef, useState } from "react";
|
|
140
|
+
function placementOf(node) {
|
|
141
|
+
const yogaSelf = node?.yogaNode;
|
|
142
|
+
if (!node || !yogaSelf) return void 0;
|
|
143
|
+
let x = 0;
|
|
144
|
+
let y = 0;
|
|
145
|
+
for (let current = node; current; current = current.parentNode) {
|
|
146
|
+
const yoga = current.yogaNode;
|
|
147
|
+
if (!yoga) continue;
|
|
148
|
+
x += yoga.getComputedLeft();
|
|
149
|
+
y += yoga.getComputedTop();
|
|
150
|
+
}
|
|
151
|
+
return { x, y, width: yogaSelf.getComputedWidth(), height: yogaSelf.getComputedHeight() };
|
|
152
|
+
}
|
|
153
|
+
function useMeasured() {
|
|
154
|
+
const ref = useRef(null);
|
|
155
|
+
const [placement, setPlacement] = useState(void 0);
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
const measured = placementOf(ref.current);
|
|
158
|
+
if (!measured) return;
|
|
159
|
+
if (placement?.x !== measured.x || placement?.y !== measured.y || placement?.width !== measured.width || placement?.height !== measured.height) {
|
|
160
|
+
setPlacement(measured);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
return [ref, placement];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/tui/transcript.ts
|
|
167
|
+
import stringWidth from "string-width";
|
|
168
|
+
var INDENT = " ";
|
|
169
|
+
function colorOf(role) {
|
|
170
|
+
if (role === "user") return "cyan";
|
|
171
|
+
if (role === "assistant") return "green";
|
|
172
|
+
if (role === "activity") return "yellow";
|
|
173
|
+
if (role === "system") return "red";
|
|
174
|
+
return void 0;
|
|
175
|
+
}
|
|
176
|
+
function labelOf(role, agentName) {
|
|
177
|
+
if (role === "user") return "You";
|
|
178
|
+
if (role === "assistant") return agentName;
|
|
179
|
+
if (role === "activity") return "Tool";
|
|
180
|
+
return "System";
|
|
181
|
+
}
|
|
182
|
+
function wrapToWidth(text, width) {
|
|
183
|
+
if (width <= 0) return [text];
|
|
184
|
+
const lines = [];
|
|
185
|
+
for (const paragraph of text.split("\n")) {
|
|
186
|
+
if (paragraph === "") {
|
|
187
|
+
lines.push("");
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
let line = "";
|
|
191
|
+
let lineWidth = 0;
|
|
192
|
+
const flush2 = () => {
|
|
193
|
+
lines.push(line);
|
|
194
|
+
line = "";
|
|
195
|
+
lineWidth = 0;
|
|
196
|
+
};
|
|
197
|
+
for (const word of paragraph.match(/\s+|\S+/g) ?? []) {
|
|
198
|
+
const wordWidth = stringWidth(word);
|
|
199
|
+
if (lineWidth > 0 && lineWidth + wordWidth > width) {
|
|
200
|
+
flush2();
|
|
201
|
+
if (/^\s+$/.test(word)) continue;
|
|
202
|
+
}
|
|
203
|
+
if (wordWidth <= width) {
|
|
204
|
+
line += word;
|
|
205
|
+
lineWidth += wordWidth;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
for (const char of word) {
|
|
209
|
+
const charWidth = stringWidth(char);
|
|
210
|
+
if (lineWidth + charWidth > width) flush2();
|
|
211
|
+
line += char;
|
|
212
|
+
lineWidth += charWidth;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
flush2();
|
|
216
|
+
}
|
|
217
|
+
return lines;
|
|
218
|
+
}
|
|
219
|
+
function ruleFor(label, width) {
|
|
220
|
+
const room = Math.max(0, width - stringWidth("\u2500\u2500 "));
|
|
221
|
+
let trimmed = "";
|
|
222
|
+
let used = 0;
|
|
223
|
+
for (const char of label) {
|
|
224
|
+
const charWidth = stringWidth(char);
|
|
225
|
+
if (used + charWidth > room) {
|
|
226
|
+
trimmed = trimmed.slice(0, -1) + "\u2026";
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
trimmed += char;
|
|
230
|
+
used += charWidth;
|
|
231
|
+
}
|
|
232
|
+
const head = `\u2500\u2500 ${trimmed} `;
|
|
233
|
+
return head + "\u2500".repeat(Math.max(0, width - stringWidth(head)));
|
|
234
|
+
}
|
|
235
|
+
function renderTranscript(messages, agentName, width) {
|
|
236
|
+
const lines = [];
|
|
237
|
+
const bodyWidth = Math.max(1, width - INDENT.length);
|
|
238
|
+
for (const message of messages) {
|
|
239
|
+
const color = colorOf(message.role);
|
|
240
|
+
if (message.role === "activity") {
|
|
241
|
+
for (const [index, text] of wrapToWidth(message.text, bodyWidth).entries()) {
|
|
242
|
+
lines.push({
|
|
243
|
+
key: `${message.id}:${index}`,
|
|
244
|
+
text: `${index === 0 ? "\xB7 " : INDENT}${text}`,
|
|
245
|
+
role: "activity",
|
|
246
|
+
color
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (lines.length > 0) lines.push({ key: `${message.id}:gap`, text: "", role: "text" });
|
|
252
|
+
lines.push({
|
|
253
|
+
key: `${message.id}:label`,
|
|
254
|
+
text: ruleFor(labelOf(message.role, agentName), width),
|
|
255
|
+
role: "label",
|
|
256
|
+
color
|
|
257
|
+
});
|
|
258
|
+
const body = message.text || (message.role === "assistant" ? "\u2026" : "");
|
|
259
|
+
for (const [index, text] of wrapToWidth(body, bodyWidth).entries()) {
|
|
260
|
+
lines.push({
|
|
261
|
+
key: `${message.id}:${index}`,
|
|
262
|
+
text: INDENT + text,
|
|
263
|
+
role: message.role === "system" ? "system" : "text",
|
|
264
|
+
color: message.role === "system" ? color : void 0
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return lines;
|
|
269
|
+
}
|
|
270
|
+
function viewportOf(lines, height, scrollUp) {
|
|
271
|
+
if (height <= 0) return { lines: [], above: lines.length, below: 0 };
|
|
272
|
+
const maximumScroll2 = Math.max(0, lines.length - height);
|
|
273
|
+
const up = Math.min(Math.max(0, scrollUp), maximumScroll2);
|
|
274
|
+
const end = lines.length - up;
|
|
275
|
+
const start = Math.max(0, end - height);
|
|
276
|
+
return { lines: lines.slice(start, end), above: start, below: lines.length - end };
|
|
277
|
+
}
|
|
278
|
+
function maximumScroll(lineCount, height) {
|
|
279
|
+
return Math.max(0, lineCount - Math.max(0, height));
|
|
280
|
+
}
|
|
281
|
+
|
|
137
282
|
// src/tui/command-palette.tsx
|
|
138
|
-
import { useState as
|
|
283
|
+
import { useState as useState3 } from "react";
|
|
139
284
|
import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
|
|
140
285
|
|
|
141
286
|
// src/tui/components.tsx
|
|
142
287
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
143
288
|
|
|
144
289
|
// src/tui/ime-text-input.tsx
|
|
145
|
-
import { useEffect, useRef, useState } from "react";
|
|
290
|
+
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
146
291
|
import { Box, Text, useCursor, useInput } from "ink";
|
|
147
|
-
import
|
|
292
|
+
import stringWidth2 from "string-width";
|
|
148
293
|
|
|
149
294
|
// src/tui/terminal-input.ts
|
|
150
295
|
var PASTE_START = "[200~";
|
|
@@ -160,6 +305,155 @@ function classifyInput(input, pasting = false) {
|
|
|
160
305
|
return text ? { kind: "text", text } : { kind: "ignore" };
|
|
161
306
|
}
|
|
162
307
|
|
|
308
|
+
// src/tui/hangul.ts
|
|
309
|
+
var CHO = "\u3131\u3132\u3134\u3137\u3138\u3139\u3141\u3142\u3143\u3145\u3146\u3147\u3148\u3149\u314A\u314B\u314C\u314D\u314E";
|
|
310
|
+
var JUNG = "\u314F\u3150\u3151\u3152\u3153\u3154\u3155\u3156\u3157\u3158\u3159\u315A\u315B\u315C\u315D\u315E\u315F\u3160\u3161\u3162\u3163";
|
|
311
|
+
var JONG = " \u3131\u3132\u3133\u3134\u3135\u3136\u3137\u3139\u313A\u313B\u313C\u313D\u313E\u313F\u3140\u3141\u3142\u3144\u3145\u3146\u3147\u3148\u314A\u314B\u314C\u314D\u314E";
|
|
312
|
+
var SYLLABLE_BASE = 44032;
|
|
313
|
+
var KEYS = {
|
|
314
|
+
r: "\u3131",
|
|
315
|
+
R: "\u3132",
|
|
316
|
+
s: "\u3134",
|
|
317
|
+
e: "\u3137",
|
|
318
|
+
E: "\u3138",
|
|
319
|
+
f: "\u3139",
|
|
320
|
+
a: "\u3141",
|
|
321
|
+
q: "\u3142",
|
|
322
|
+
Q: "\u3143",
|
|
323
|
+
t: "\u3145",
|
|
324
|
+
T: "\u3146",
|
|
325
|
+
d: "\u3147",
|
|
326
|
+
w: "\u3148",
|
|
327
|
+
W: "\u3149",
|
|
328
|
+
c: "\u314A",
|
|
329
|
+
z: "\u314B",
|
|
330
|
+
x: "\u314C",
|
|
331
|
+
v: "\u314D",
|
|
332
|
+
g: "\u314E",
|
|
333
|
+
k: "\u314F",
|
|
334
|
+
o: "\u3150",
|
|
335
|
+
i: "\u3151",
|
|
336
|
+
O: "\u3152",
|
|
337
|
+
j: "\u3153",
|
|
338
|
+
p: "\u3154",
|
|
339
|
+
u: "\u3155",
|
|
340
|
+
P: "\u3156",
|
|
341
|
+
h: "\u3157",
|
|
342
|
+
y: "\u315B",
|
|
343
|
+
n: "\u315C",
|
|
344
|
+
b: "\u3160",
|
|
345
|
+
m: "\u3161",
|
|
346
|
+
l: "\u3163"
|
|
347
|
+
};
|
|
348
|
+
var VOWEL_PAIRS = {
|
|
349
|
+
"\u3157\u314F": "\u3158",
|
|
350
|
+
"\u3157\u3150": "\u3159",
|
|
351
|
+
"\u3157\u3163": "\u315A",
|
|
352
|
+
"\u315C\u3153": "\u315D",
|
|
353
|
+
"\u315C\u3154": "\u315E",
|
|
354
|
+
"\u315C\u3163": "\u315F",
|
|
355
|
+
"\u3161\u3163": "\u3162"
|
|
356
|
+
};
|
|
357
|
+
var FINAL_PAIRS = {
|
|
358
|
+
"\u3131\u3145": "\u3133",
|
|
359
|
+
"\u3134\u3148": "\u3135",
|
|
360
|
+
"\u3134\u314E": "\u3136",
|
|
361
|
+
"\u3139\u3131": "\u313A",
|
|
362
|
+
"\u3139\u3141": "\u313B",
|
|
363
|
+
"\u3139\u3142": "\u313C",
|
|
364
|
+
"\u3139\u3145": "\u313D",
|
|
365
|
+
"\u3139\u314C": "\u313E",
|
|
366
|
+
"\u3139\u314D": "\u313F",
|
|
367
|
+
"\u3139\u314E": "\u3140",
|
|
368
|
+
"\u3142\u3145": "\u3144"
|
|
369
|
+
};
|
|
370
|
+
var SPLIT = Object.fromEntries(
|
|
371
|
+
[...Object.entries(VOWEL_PAIRS), ...Object.entries(FINAL_PAIRS)].map(([pair, joined]) => [
|
|
372
|
+
joined,
|
|
373
|
+
[pair[0], pair[1]]
|
|
374
|
+
])
|
|
375
|
+
);
|
|
376
|
+
var EMPTY = {};
|
|
377
|
+
function jamoOf(key) {
|
|
378
|
+
if (KEYS[key]) return KEYS[key];
|
|
379
|
+
const lower = key.toLowerCase();
|
|
380
|
+
return lower === key ? void 0 : KEYS[lower];
|
|
381
|
+
}
|
|
382
|
+
function isVowel(jamo) {
|
|
383
|
+
return JUNG.includes(jamo) || jamo === "\u3157" || jamo === "\u315C" || jamo === "\u3161";
|
|
384
|
+
}
|
|
385
|
+
function canBeFinal(jamo) {
|
|
386
|
+
return JONG.indexOf(jamo) > 0;
|
|
387
|
+
}
|
|
388
|
+
function display(state) {
|
|
389
|
+
const { cho, jung, jong } = state;
|
|
390
|
+
if (cho && jung) {
|
|
391
|
+
const l = CHO.indexOf(cho);
|
|
392
|
+
const v = JUNG.indexOf(jung);
|
|
393
|
+
const t = jong ? JONG.indexOf(jong) : 0;
|
|
394
|
+
if (l >= 0 && v >= 0 && t >= 0) {
|
|
395
|
+
return String.fromCharCode(SYLLABLE_BASE + (l * 21 + v) * 28 + t);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return cho ?? jung ?? "";
|
|
399
|
+
}
|
|
400
|
+
function feed(state, jamo) {
|
|
401
|
+
const { cho, jung, jong } = state;
|
|
402
|
+
if (isVowel(jamo)) {
|
|
403
|
+
if (cho && jung && jong) {
|
|
404
|
+
const parts = SPLIT[jong];
|
|
405
|
+
const moved = parts ? parts[1] : jong;
|
|
406
|
+
const kept = parts ? parts[0] : void 0;
|
|
407
|
+
return {
|
|
408
|
+
commit: display({ cho, jung, jong: kept }),
|
|
409
|
+
state: { cho: moved, jung: jamo }
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
if (cho && jung) {
|
|
413
|
+
const merged = VOWEL_PAIRS[jung + jamo];
|
|
414
|
+
if (merged) return { commit: "", state: { cho, jung: merged } };
|
|
415
|
+
return { commit: display(state), state: { jung: jamo } };
|
|
416
|
+
}
|
|
417
|
+
if (cho) return { commit: "", state: { cho, jung: jamo } };
|
|
418
|
+
if (jung) {
|
|
419
|
+
const merged = VOWEL_PAIRS[jung + jamo];
|
|
420
|
+
if (merged) return { commit: "", state: { jung: merged } };
|
|
421
|
+
return { commit: jung, state: { jung: jamo } };
|
|
422
|
+
}
|
|
423
|
+
return { commit: "", state: { jung: jamo } };
|
|
424
|
+
}
|
|
425
|
+
if (cho && jung) {
|
|
426
|
+
if (!jong) {
|
|
427
|
+
if (canBeFinal(jamo)) return { commit: "", state: { cho, jung, jong: jamo } };
|
|
428
|
+
return { commit: display(state), state: { cho: jamo } };
|
|
429
|
+
}
|
|
430
|
+
const merged = FINAL_PAIRS[jong + jamo];
|
|
431
|
+
if (merged) return { commit: "", state: { cho, jung, jong: merged } };
|
|
432
|
+
return { commit: display(state), state: { cho: jamo } };
|
|
433
|
+
}
|
|
434
|
+
if (cho) {
|
|
435
|
+
return { commit: cho, state: { cho: jamo } };
|
|
436
|
+
}
|
|
437
|
+
if (jung) return { commit: jung, state: { cho: jamo } };
|
|
438
|
+
return { commit: "", state: { cho: jamo } };
|
|
439
|
+
}
|
|
440
|
+
function back(state) {
|
|
441
|
+
const { cho, jung, jong } = state;
|
|
442
|
+
if (jong) {
|
|
443
|
+
const parts = SPLIT[jong];
|
|
444
|
+
return { state: { cho, jung, jong: parts ? parts[0] : void 0 }, handled: true };
|
|
445
|
+
}
|
|
446
|
+
if (jung) {
|
|
447
|
+
const parts = SPLIT[jung];
|
|
448
|
+
return { state: { cho, jung: parts ? parts[0] : void 0 }, handled: true };
|
|
449
|
+
}
|
|
450
|
+
if (cho) return { state: EMPTY, handled: true };
|
|
451
|
+
return { state: EMPTY, handled: false };
|
|
452
|
+
}
|
|
453
|
+
function flush(state) {
|
|
454
|
+
return { commit: display(state), state: EMPTY };
|
|
455
|
+
}
|
|
456
|
+
|
|
163
457
|
// src/tui/ime-text-input.tsx
|
|
164
458
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
165
459
|
var segmenter = new Intl.Segmenter("ko", { granularity: "grapheme" });
|
|
@@ -169,26 +463,13 @@ function graphemes(value) {
|
|
|
169
463
|
function clamp(value, minimum, maximum) {
|
|
170
464
|
return Math.min(Math.max(value, minimum), maximum);
|
|
171
465
|
}
|
|
172
|
-
function placementOf(node) {
|
|
173
|
-
const width = node?.yogaNode?.getComputedWidth();
|
|
174
|
-
if (!node || width === void 0) return void 0;
|
|
175
|
-
let x = 0;
|
|
176
|
-
let y = 0;
|
|
177
|
-
for (let current = node; current; current = current.parentNode) {
|
|
178
|
-
const yoga = current.yogaNode;
|
|
179
|
-
if (!yoga) continue;
|
|
180
|
-
x += yoga.getComputedLeft();
|
|
181
|
-
y += yoga.getComputedTop();
|
|
182
|
-
}
|
|
183
|
-
return { x, y, width };
|
|
184
|
-
}
|
|
185
466
|
function visibleInput(segments, cursor, maximumWidth) {
|
|
186
467
|
let start = cursor;
|
|
187
468
|
let widthBeforeCursor = 0;
|
|
188
|
-
const followingWidth = cursor < segments.length ?
|
|
469
|
+
const followingWidth = cursor < segments.length ? stringWidth2(segments[cursor] ?? "") : 0;
|
|
189
470
|
const beforeLimit = Math.max(0, maximumWidth - Math.min(followingWidth, maximumWidth));
|
|
190
471
|
while (start > 0) {
|
|
191
|
-
const width =
|
|
472
|
+
const width = stringWidth2(segments[start - 1] ?? "");
|
|
192
473
|
if (widthBeforeCursor + width > beforeLimit) break;
|
|
193
474
|
widthBeforeCursor += width;
|
|
194
475
|
start -= 1;
|
|
@@ -196,7 +477,7 @@ function visibleInput(segments, cursor, maximumWidth) {
|
|
|
196
477
|
let end = cursor;
|
|
197
478
|
let totalWidth = widthBeforeCursor;
|
|
198
479
|
while (end < segments.length) {
|
|
199
|
-
const width =
|
|
480
|
+
const width = stringWidth2(segments[end] ?? "");
|
|
200
481
|
if (totalWidth + width > maximumWidth) break;
|
|
201
482
|
totalWidth += width;
|
|
202
483
|
end += 1;
|
|
@@ -212,20 +493,13 @@ function TerminalCursor({ x, y }) {
|
|
|
212
493
|
return null;
|
|
213
494
|
}
|
|
214
495
|
function ImeTextInput(props) {
|
|
215
|
-
const ref =
|
|
216
|
-
const [placement, setPlacement] = useState(void 0);
|
|
496
|
+
const [ref, placement] = useMeasured();
|
|
217
497
|
const initialSegments = graphemes(props.value);
|
|
218
|
-
const [cursor, setCursor] =
|
|
219
|
-
const valueRef =
|
|
220
|
-
const cursorRef =
|
|
221
|
-
const pastingRef =
|
|
222
|
-
|
|
223
|
-
const measured = placementOf(ref.current);
|
|
224
|
-
if (!measured) return;
|
|
225
|
-
if (placement?.x !== measured.x || placement?.y !== measured.y || placement?.width !== measured.width) {
|
|
226
|
-
setPlacement(measured);
|
|
227
|
-
}
|
|
228
|
-
});
|
|
498
|
+
const [cursor, setCursor] = useState2(initialSegments.length);
|
|
499
|
+
const valueRef = useRef2(props.value);
|
|
500
|
+
const cursorRef = useRef2(initialSegments.length);
|
|
501
|
+
const pastingRef = useRef2(false);
|
|
502
|
+
const composingRef = useRef2(EMPTY);
|
|
229
503
|
const moveCursor = (next, length) => {
|
|
230
504
|
const resolved = clamp(next, 0, length);
|
|
231
505
|
cursorRef.current = resolved;
|
|
@@ -237,7 +511,10 @@ function ImeTextInput(props) {
|
|
|
237
511
|
moveCursor(nextCursor, segments.length);
|
|
238
512
|
props.onChange(nextValue);
|
|
239
513
|
};
|
|
240
|
-
|
|
514
|
+
useEffect2(() => {
|
|
515
|
+
if (!props.focus || !props.hangulMode) composingRef.current = EMPTY;
|
|
516
|
+
}, [props.focus, props.hangulMode]);
|
|
517
|
+
useEffect2(() => {
|
|
241
518
|
if (props.value === valueRef.current) return;
|
|
242
519
|
const previousLength = graphemes(valueRef.current).length;
|
|
243
520
|
const nextLength = graphemes(props.value).length;
|
|
@@ -245,12 +522,24 @@ function ImeTextInput(props) {
|
|
|
245
522
|
valueRef.current = props.value;
|
|
246
523
|
moveCursor(wasAtEnd ? nextLength : cursorRef.current, nextLength);
|
|
247
524
|
}, [props.value]);
|
|
525
|
+
const applyComposition = (segments, cursor2, previous, commit, next) => {
|
|
526
|
+
const removed = previous ? 1 : 0;
|
|
527
|
+
const inserted = graphemes(commit + next);
|
|
528
|
+
segments.splice(cursor2 - removed, removed, ...inserted);
|
|
529
|
+
updateValue(segments, cursor2 - removed + inserted.length);
|
|
530
|
+
};
|
|
248
531
|
useInput(
|
|
249
532
|
(input, key) => {
|
|
250
533
|
const current = graphemes(valueRef.current);
|
|
251
534
|
const currentCursor = clamp(cursorRef.current, 0, current.length);
|
|
252
535
|
const event = classifyInput(input, pastingRef.current);
|
|
536
|
+
const composing = composingRef.current;
|
|
537
|
+
const shown = display(composing);
|
|
538
|
+
const settle = () => {
|
|
539
|
+
composingRef.current = EMPTY;
|
|
540
|
+
};
|
|
253
541
|
if (event.kind === "paste-start") {
|
|
542
|
+
settle();
|
|
254
543
|
pastingRef.current = true;
|
|
255
544
|
return;
|
|
256
545
|
}
|
|
@@ -266,36 +555,81 @@ function ImeTextInput(props) {
|
|
|
266
555
|
}
|
|
267
556
|
return;
|
|
268
557
|
}
|
|
558
|
+
if (key.ctrl && (input === "`" || input === "l")) {
|
|
559
|
+
settle();
|
|
560
|
+
props.onHangulModeChange?.(!props.hangulMode);
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
269
563
|
if (key.return) {
|
|
564
|
+
settle();
|
|
270
565
|
props.onSubmit?.(valueRef.current);
|
|
271
566
|
return;
|
|
272
567
|
}
|
|
273
568
|
if (key.leftArrow) {
|
|
569
|
+
settle();
|
|
274
570
|
moveCursor(currentCursor - 1, current.length);
|
|
275
571
|
return;
|
|
276
572
|
}
|
|
277
573
|
if (key.rightArrow) {
|
|
574
|
+
settle();
|
|
278
575
|
moveCursor(currentCursor + 1, current.length);
|
|
279
576
|
return;
|
|
280
577
|
}
|
|
281
578
|
if (key.home) {
|
|
579
|
+
settle();
|
|
282
580
|
moveCursor(0, current.length);
|
|
283
581
|
return;
|
|
284
582
|
}
|
|
285
583
|
if (key.end) {
|
|
584
|
+
settle();
|
|
286
585
|
moveCursor(current.length, current.length);
|
|
287
586
|
return;
|
|
288
587
|
}
|
|
289
588
|
if (key.backspace || key.delete) {
|
|
589
|
+
const stepped = back(composing);
|
|
590
|
+
if (stepped.handled) {
|
|
591
|
+
composingRef.current = stepped.state;
|
|
592
|
+
applyComposition(current, currentCursor, shown, "", display(stepped.state));
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
290
595
|
if (currentCursor === 0) return;
|
|
291
596
|
current.splice(currentCursor - 1, 1);
|
|
292
597
|
updateValue(current, currentCursor - 1);
|
|
293
598
|
return;
|
|
294
599
|
}
|
|
295
600
|
if (key.ctrl || key.meta || key.tab || key.escape || key.upArrow || key.downArrow || key.pageUp || key.pageDown) {
|
|
601
|
+
settle();
|
|
296
602
|
return;
|
|
297
603
|
}
|
|
298
604
|
if (event.kind !== "text") return;
|
|
605
|
+
if (props.hangulMode) {
|
|
606
|
+
let state = composing;
|
|
607
|
+
let previous = shown;
|
|
608
|
+
let segments = current;
|
|
609
|
+
let cursor2 = currentCursor;
|
|
610
|
+
for (const character of event.text) {
|
|
611
|
+
const jamo = jamoOf(character);
|
|
612
|
+
if (!jamo) {
|
|
613
|
+
const flushed = flush(state);
|
|
614
|
+
state = flushed.state;
|
|
615
|
+
applyComposition(segments, cursor2, previous, flushed.commit + character, "");
|
|
616
|
+
segments = graphemes(valueRef.current);
|
|
617
|
+
cursor2 = cursorRef.current;
|
|
618
|
+
previous = "";
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
const result = feed(state, jamo);
|
|
622
|
+
state = result.state;
|
|
623
|
+
const next = display(state);
|
|
624
|
+
applyComposition(segments, cursor2, previous, result.commit, next);
|
|
625
|
+
segments = graphemes(valueRef.current);
|
|
626
|
+
cursor2 = cursorRef.current;
|
|
627
|
+
previous = next;
|
|
628
|
+
}
|
|
629
|
+
composingRef.current = state;
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
settle();
|
|
299
633
|
const inserted = graphemes(event.text);
|
|
300
634
|
current.splice(currentCursor, 0, ...inserted);
|
|
301
635
|
updateValue(current, currentCursor + inserted.length);
|
|
@@ -368,7 +702,7 @@ function FormField(props) {
|
|
|
368
702
|
// src/tui/command-palette.tsx
|
|
369
703
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
370
704
|
function CommandPalette(props) {
|
|
371
|
-
const [cursor, setCursor] =
|
|
705
|
+
const [cursor, setCursor] = useState3(0);
|
|
372
706
|
useInput2((_input, key) => {
|
|
373
707
|
if (key.escape) props.onCancel();
|
|
374
708
|
if (key.upArrow) setCursor((current) => Math.max(0, current - 1));
|
|
@@ -387,15 +721,15 @@ function CommandPalette(props) {
|
|
|
387
721
|
}
|
|
388
722
|
|
|
389
723
|
// src/tui/history-screen.tsx
|
|
390
|
-
import { useEffect as
|
|
724
|
+
import { useEffect as useEffect3, useState as useState4 } from "react";
|
|
391
725
|
import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
|
|
392
726
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
393
727
|
function HistoryScreen(props) {
|
|
394
|
-
const [items, setItems] =
|
|
395
|
-
const [cursor, setCursor] =
|
|
396
|
-
const [loading, setLoading] =
|
|
397
|
-
const [error, setError] =
|
|
398
|
-
|
|
728
|
+
const [items, setItems] = useState4([]);
|
|
729
|
+
const [cursor, setCursor] = useState4(0);
|
|
730
|
+
const [loading, setLoading] = useState4(true);
|
|
731
|
+
const [error, setError] = useState4();
|
|
732
|
+
useEffect3(() => {
|
|
399
733
|
let alive = true;
|
|
400
734
|
props.engine.listConversations(props.profile).then((result) => alive && setItems(result)).catch((reason) => alive && setError(publicError(reason).message)).finally(() => alive && setLoading(false));
|
|
401
735
|
return () => {
|
|
@@ -444,7 +778,7 @@ function HistoryScreen(props) {
|
|
|
444
778
|
}
|
|
445
779
|
|
|
446
780
|
// src/tui/start-panel.tsx
|
|
447
|
-
import { useEffect as
|
|
781
|
+
import { useEffect as useEffect4, useState as useState5 } from "react";
|
|
448
782
|
import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
|
|
449
783
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
450
784
|
function StartPanel(props) {
|
|
@@ -452,10 +786,10 @@ function StartPanel(props) {
|
|
|
452
786
|
{ kind: "new" },
|
|
453
787
|
...props.conversations.map((conversation) => ({ kind: "conversation", conversation }))
|
|
454
788
|
];
|
|
455
|
-
const [cursor, setCursor] =
|
|
456
|
-
const [opening, setOpening] =
|
|
457
|
-
const [error, setError] =
|
|
458
|
-
|
|
789
|
+
const [cursor, setCursor] = useState5(0);
|
|
790
|
+
const [opening, setOpening] = useState5(false);
|
|
791
|
+
const [error, setError] = useState5();
|
|
792
|
+
useEffect4(() => {
|
|
459
793
|
setCursor((current) => Math.min(current, Math.max(0, rows.length - 1)));
|
|
460
794
|
}, [rows.length]);
|
|
461
795
|
useInput4(
|
|
@@ -530,7 +864,7 @@ function when(conversation) {
|
|
|
530
864
|
}
|
|
531
865
|
|
|
532
866
|
// src/tui/use-terminal-size.ts
|
|
533
|
-
import { useEffect as
|
|
867
|
+
import { useEffect as useEffect5, useState as useState6 } from "react";
|
|
534
868
|
import { useStdout } from "ink";
|
|
535
869
|
function useTerminalSize() {
|
|
536
870
|
const { stdout: stdout2 } = useStdout();
|
|
@@ -539,8 +873,8 @@ function useTerminalSize() {
|
|
|
539
873
|
const rows = stdout2.rows || 30;
|
|
540
874
|
return { columns, rows, wide: columns >= 88 };
|
|
541
875
|
};
|
|
542
|
-
const [size, setSize] =
|
|
543
|
-
|
|
876
|
+
const [size, setSize] = useState6(read);
|
|
877
|
+
useEffect5(() => {
|
|
544
878
|
const resize = () => setSize(read());
|
|
545
879
|
stdout2.on("resize", resize);
|
|
546
880
|
return () => {
|
|
@@ -573,32 +907,39 @@ function AgentSidebar(props) {
|
|
|
573
907
|
props.agents.length === 0 ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\uC0AC\uC6A9 \uAC00\uB2A5\uD55C Agent\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }) : null
|
|
574
908
|
] });
|
|
575
909
|
}
|
|
576
|
-
function messageColor(role) {
|
|
577
|
-
if (role === "user") return "cyan";
|
|
578
|
-
if (role === "assistant") return "green";
|
|
579
|
-
if (role === "activity") return "yellow";
|
|
580
|
-
if (role === "system") return "red";
|
|
581
|
-
return void 0;
|
|
582
|
-
}
|
|
583
|
-
function labelOf(role, agentName) {
|
|
584
|
-
if (role === "user") return "You";
|
|
585
|
-
if (role === "assistant") return agentName;
|
|
586
|
-
if (role === "activity") return "Tool";
|
|
587
|
-
return "System";
|
|
588
|
-
}
|
|
589
910
|
function ChatPane(props) {
|
|
590
|
-
const
|
|
591
|
-
const
|
|
911
|
+
const [ref, box] = useMeasured();
|
|
912
|
+
const width = box?.width ?? 20;
|
|
913
|
+
const height = box?.height ?? 1;
|
|
914
|
+
const lines = renderTranscript(props.messages, props.agent?.workflowName ?? "Agent", width);
|
|
915
|
+
const view = viewportOf(lines, height, props.scrollUp);
|
|
916
|
+
useEffect6(() => {
|
|
917
|
+
props.onViewport(lines.length, height);
|
|
918
|
+
}, [lines.length, height]);
|
|
592
919
|
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "blue", paddingX: 1, children: [
|
|
593
|
-
/* @__PURE__ */
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
] }
|
|
920
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
921
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, wrap: "truncate-end", children: props.agent?.workflowName ?? "Agent\uB97C \uC120\uD0DD\uD558\uC138\uC694" }),
|
|
922
|
+
view.below > 0 ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
923
|
+
" \xB7 \u2193",
|
|
924
|
+
view.below,
|
|
925
|
+
"\uC904"
|
|
926
|
+
] }) : null
|
|
927
|
+
] }),
|
|
928
|
+
/* @__PURE__ */ jsxs6(Box6, { ref, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [
|
|
929
|
+
view.lines.length === 0 ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, wrap: "truncate-end", children: props.agent ? "\uBA54\uC2DC\uC9C0\uB97C \uC785\uB825\uD574 \uB300\uD654\uB97C \uC2DC\uC791\uD558\uC138\uC694." : "\uC67C\uCABD\uC5D0\uC11C Agent\uB97C \uC120\uD0DD\uD558\uC138\uC694." }) : null,
|
|
930
|
+
view.lines.map((line) => /* @__PURE__ */ jsx6(
|
|
931
|
+
Text6,
|
|
932
|
+
{
|
|
933
|
+
wrap: "truncate-end",
|
|
934
|
+
bold: line.role === "label",
|
|
935
|
+
dimColor: line.role === "activity",
|
|
936
|
+
color: line.color,
|
|
937
|
+
children: line.text || " "
|
|
938
|
+
},
|
|
939
|
+
line.key
|
|
940
|
+
))
|
|
600
941
|
] }),
|
|
601
|
-
props.status ? /* @__PURE__ */ jsxs6(Text6, { color: "yellow", children: [
|
|
942
|
+
props.status ? /* @__PURE__ */ jsxs6(Text6, { color: "yellow", wrap: "truncate-end", children: [
|
|
602
943
|
"\u25C6 ",
|
|
603
944
|
props.status
|
|
604
945
|
] }) : null
|
|
@@ -606,7 +947,8 @@ function ChatPane(props) {
|
|
|
606
947
|
}
|
|
607
948
|
function Composer(props) {
|
|
608
949
|
return /* @__PURE__ */ jsxs6(Box6, { borderStyle: "round", borderColor: props.focused ? "cyan" : "gray", paddingX: 1, children: [
|
|
609
|
-
/* @__PURE__ */ jsx6(Text6, { color: "
|
|
950
|
+
/* @__PURE__ */ jsx6(Text6, { color: props.hangulMode ? "yellow" : void 0, dimColor: !props.hangulMode, children: props.hangulMode ? "\uD55C" : "EN" }),
|
|
951
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyan", children: " \u203A " }),
|
|
610
952
|
props.disabled ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\uC751\uB2F5\uC744 \uAE30\uB2E4\uB9AC\uB294 \uC911..." }) : /* @__PURE__ */ jsx6(
|
|
611
953
|
ImeTextInput,
|
|
612
954
|
{
|
|
@@ -614,7 +956,9 @@ function Composer(props) {
|
|
|
614
956
|
onChange: props.onChange,
|
|
615
957
|
onSubmit: props.onSubmit,
|
|
616
958
|
focus: props.focused,
|
|
617
|
-
placeholder: "\uBA54\uC2DC\uC9C0\uB97C \uC785\uB825\uD558\uC138\uC694"
|
|
959
|
+
placeholder: "\uBA54\uC2DC\uC9C0\uB97C \uC785\uB825\uD558\uC138\uC694",
|
|
960
|
+
hangulMode: props.hangulMode,
|
|
961
|
+
onHangulModeChange: props.onHangulModeChange
|
|
618
962
|
}
|
|
619
963
|
)
|
|
620
964
|
] });
|
|
@@ -623,20 +967,27 @@ function Dashboard(props) {
|
|
|
623
967
|
const { exit } = useApp();
|
|
624
968
|
const size = useTerminalSize();
|
|
625
969
|
const bodyHeight = Math.max(12, size.rows - 5);
|
|
626
|
-
const [focus, setFocus] =
|
|
627
|
-
const [cursor, setCursor] =
|
|
628
|
-
const [selected, setSelected] =
|
|
970
|
+
const [focus, setFocus] = useState7("agents");
|
|
971
|
+
const [cursor, setCursor] = useState7(0);
|
|
972
|
+
const [selected, setSelected] = useState7(() => {
|
|
629
973
|
const first = props.session.agents[0];
|
|
630
974
|
return first ? { workflowId: first.workflowId, workflowName: first.workflowName } : void 0;
|
|
631
975
|
});
|
|
632
|
-
const [input, setInput] =
|
|
976
|
+
const [input, setInput] = useState7("");
|
|
633
977
|
const [chat, dispatch] = useReducer(chatReducer, initialChatState);
|
|
634
|
-
const [palette, setPalette] =
|
|
635
|
-
const [history, setHistory] =
|
|
636
|
-
const [start, setStart] =
|
|
637
|
-
const [
|
|
638
|
-
const
|
|
639
|
-
|
|
978
|
+
const [palette, setPalette] = useState7(false);
|
|
979
|
+
const [history, setHistory] = useState7(false);
|
|
980
|
+
const [start, setStart] = useState7();
|
|
981
|
+
const [scrollUp, setScrollUp] = useState7(0);
|
|
982
|
+
const [hangulMode, setHangulMode] = useState7(props.preferences?.hangulMode ?? false);
|
|
983
|
+
const changeHangulMode = (enabled) => {
|
|
984
|
+
setHangulMode(enabled);
|
|
985
|
+
props.preferences?.onHangulModeChange?.(enabled);
|
|
986
|
+
};
|
|
987
|
+
const viewport = useRef3({ lineCount: 0, height: 0 });
|
|
988
|
+
const [starting, setStarting] = useState7(false);
|
|
989
|
+
const controller = useRef3(null);
|
|
990
|
+
useEffect6(() => () => controller.current?.abort(), []);
|
|
640
991
|
const selectAgent = async () => {
|
|
641
992
|
const agent = props.session.agents[cursor];
|
|
642
993
|
if (!agent || chat.running || starting) return;
|
|
@@ -662,12 +1013,14 @@ function Dashboard(props) {
|
|
|
662
1013
|
setSelected(ref);
|
|
663
1014
|
dispatch({ type: "reset" });
|
|
664
1015
|
setInput("");
|
|
1016
|
+
setScrollUp(0);
|
|
665
1017
|
setStart(void 0);
|
|
666
1018
|
setFocus("composer");
|
|
667
1019
|
};
|
|
668
1020
|
const openHistory = (conversation, turns) => {
|
|
669
1021
|
setSelected({ workflowId: conversation.workflowId, workflowName: conversation.workflowName });
|
|
670
1022
|
dispatch({ type: "history_loaded", interactionId: conversation.interactionId, turns });
|
|
1023
|
+
setScrollUp(0);
|
|
671
1024
|
const index = props.session.agents.findIndex((agent) => agent.workflowId === conversation.workflowId);
|
|
672
1025
|
if (index >= 0) setCursor(index);
|
|
673
1026
|
setHistory(false);
|
|
@@ -682,6 +1035,7 @@ function Dashboard(props) {
|
|
|
682
1035
|
if (chat.running) return;
|
|
683
1036
|
dispatch({ type: "reset" });
|
|
684
1037
|
setInput("");
|
|
1038
|
+
setScrollUp(0);
|
|
685
1039
|
setPalette(false);
|
|
686
1040
|
setFocus("composer");
|
|
687
1041
|
};
|
|
@@ -697,6 +1051,7 @@ function Dashboard(props) {
|
|
|
697
1051
|
input: text
|
|
698
1052
|
});
|
|
699
1053
|
setInput("");
|
|
1054
|
+
setScrollUp(0);
|
|
700
1055
|
dispatch({ type: "turn_started", interactionId: resolved.interactionId, input: text });
|
|
701
1056
|
const active = new AbortController();
|
|
702
1057
|
controller.current = active;
|
|
@@ -712,6 +1067,12 @@ function Dashboard(props) {
|
|
|
712
1067
|
controller.current = null;
|
|
713
1068
|
}
|
|
714
1069
|
};
|
|
1070
|
+
const scrollBy = (direction) => {
|
|
1071
|
+
const { lineCount, height } = viewport.current;
|
|
1072
|
+
const step = Math.max(1, Math.floor(height / 2));
|
|
1073
|
+
const limit = maximumScroll(lineCount, height);
|
|
1074
|
+
setScrollUp((current) => Math.min(limit, Math.max(0, current - direction * step)));
|
|
1075
|
+
};
|
|
715
1076
|
useInput5(
|
|
716
1077
|
(keyInput, key) => {
|
|
717
1078
|
if (key.ctrl && keyInput === "k") setPalette(true);
|
|
@@ -722,6 +1083,8 @@ function Dashboard(props) {
|
|
|
722
1083
|
if (chat.running) cancelTurn();
|
|
723
1084
|
setHistory(true);
|
|
724
1085
|
} else if (key.ctrl && keyInput === "n") newConversation();
|
|
1086
|
+
else if (key.pageUp) scrollBy(-1);
|
|
1087
|
+
else if (key.pageDown) scrollBy(1);
|
|
725
1088
|
else if (key.escape && chat.running) cancelTurn();
|
|
726
1089
|
else if (key.escape) setFocus("agents");
|
|
727
1090
|
else if (key.tab) setFocus((current) => current === "agents" ? "composer" : "agents");
|
|
@@ -803,7 +1166,18 @@ function Dashboard(props) {
|
|
|
803
1166
|
}
|
|
804
1167
|
}
|
|
805
1168
|
) : /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", flexGrow: 1, children: [
|
|
806
|
-
/* @__PURE__ */ jsx6(
|
|
1169
|
+
/* @__PURE__ */ jsx6(
|
|
1170
|
+
ChatPane,
|
|
1171
|
+
{
|
|
1172
|
+
agent: selected,
|
|
1173
|
+
messages: chat.messages,
|
|
1174
|
+
status: chat.status,
|
|
1175
|
+
scrollUp,
|
|
1176
|
+
onViewport: (lineCount, height) => {
|
|
1177
|
+
viewport.current = { lineCount, height };
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
),
|
|
807
1181
|
/* @__PURE__ */ jsx6(
|
|
808
1182
|
Composer,
|
|
809
1183
|
{
|
|
@@ -811,7 +1185,9 @@ function Dashboard(props) {
|
|
|
811
1185
|
onChange: setInput,
|
|
812
1186
|
onSubmit: (value) => void send(value),
|
|
813
1187
|
focused: focus === "composer",
|
|
814
|
-
disabled: chat.running || !selected
|
|
1188
|
+
disabled: chat.running || !selected,
|
|
1189
|
+
hangulMode,
|
|
1190
|
+
onHangulModeChange: changeHangulMode
|
|
815
1191
|
}
|
|
816
1192
|
)
|
|
817
1193
|
] });
|
|
@@ -830,18 +1206,18 @@ function Dashboard(props) {
|
|
|
830
1206
|
}
|
|
831
1207
|
),
|
|
832
1208
|
body,
|
|
833
|
-
/* @__PURE__ */ jsx6(Footer, { text: "Tab \uD328\uB110 \xB7 Ctrl+K \uBA85\uB839 \xB7 Ctrl+H \uAE30\uB85D \xB7 Ctrl+P \uD504\uB85C\uD544 \xB7 Esc \uCDE8\uC18C \xB7 Ctrl+Q \uC885\uB8CC" })
|
|
1209
|
+
/* @__PURE__ */ jsx6(Footer, { text: "Ctrl+Space \uD55C/\uC601 \xB7 Tab \uD328\uB110 \xB7 PgUp/PgDn \uC2A4\uD06C\uB864 \xB7 Ctrl+K \uBA85\uB839 \xB7 Ctrl+H \uAE30\uB85D \xB7 Ctrl+P \uD504\uB85C\uD544 \xB7 Esc \uCDE8\uC18C \xB7 Ctrl+Q \uC885\uB8CC" })
|
|
834
1210
|
] });
|
|
835
1211
|
}
|
|
836
1212
|
|
|
837
1213
|
// src/tui/login-screen.tsx
|
|
838
|
-
import { useState as
|
|
1214
|
+
import { useState as useState8 } from "react";
|
|
839
1215
|
import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
|
|
840
1216
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
841
1217
|
function LoginScreen(props) {
|
|
842
|
-
const [email, setEmail] =
|
|
843
|
-
const [password, setPassword] =
|
|
844
|
-
const [focus, setFocus] =
|
|
1218
|
+
const [email, setEmail] = useState8("");
|
|
1219
|
+
const [password, setPassword] = useState8("");
|
|
1220
|
+
const [focus, setFocus] = useState8("email");
|
|
845
1221
|
useInput6(
|
|
846
1222
|
(input, key) => {
|
|
847
1223
|
if (key.tab) setFocus((current) => current === "email" ? "password" : "email");
|
|
@@ -902,16 +1278,16 @@ function LoginScreen(props) {
|
|
|
902
1278
|
}
|
|
903
1279
|
|
|
904
1280
|
// src/tui/profile-screen.tsx
|
|
905
|
-
import { useEffect as
|
|
1281
|
+
import { useEffect as useEffect7, useState as useState9 } from "react";
|
|
906
1282
|
import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
|
|
907
1283
|
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
908
1284
|
function ProfileScreen(props) {
|
|
909
|
-
const [cursor, setCursor] =
|
|
910
|
-
const [creating, setCreating] =
|
|
911
|
-
const [focus, setFocus] =
|
|
912
|
-
const [name, setName] =
|
|
913
|
-
const [serverUrl, setServerUrl] =
|
|
914
|
-
|
|
1285
|
+
const [cursor, setCursor] = useState9(Math.max(0, props.profiles.findIndex((profile) => profile.current)));
|
|
1286
|
+
const [creating, setCreating] = useState9(false);
|
|
1287
|
+
const [focus, setFocus] = useState9("name");
|
|
1288
|
+
const [name, setName] = useState9("");
|
|
1289
|
+
const [serverUrl, setServerUrl] = useState9("");
|
|
1290
|
+
useEffect7(() => setCursor((current) => Math.min(current, Math.max(0, props.profiles.length - 1))), [props.profiles]);
|
|
915
1291
|
useInput7(
|
|
916
1292
|
(input, key) => {
|
|
917
1293
|
if (key.escape) {
|
|
@@ -995,11 +1371,11 @@ function ProfileScreen(props) {
|
|
|
995
1371
|
}
|
|
996
1372
|
|
|
997
1373
|
// src/tui/server-screen.tsx
|
|
998
|
-
import { useState as
|
|
1374
|
+
import { useState as useState10 } from "react";
|
|
999
1375
|
import { Box as Box9, Text as Text9, useInput as useInput8 } from "ink";
|
|
1000
1376
|
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1001
1377
|
function ServerScreen(props) {
|
|
1002
|
-
const [serverUrl, setServerUrl] =
|
|
1378
|
+
const [serverUrl, setServerUrl] = useState10(props.initialValue ?? "");
|
|
1003
1379
|
const editing = props.initialValue !== void 0;
|
|
1004
1380
|
useInput8(
|
|
1005
1381
|
(_input, key) => {
|
|
@@ -1038,15 +1414,18 @@ function ServerScreen(props) {
|
|
|
1038
1414
|
|
|
1039
1415
|
// src/tui/app.tsx
|
|
1040
1416
|
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1041
|
-
function App({
|
|
1417
|
+
function App({
|
|
1418
|
+
engine,
|
|
1419
|
+
preferences
|
|
1420
|
+
}) {
|
|
1042
1421
|
const { exit } = useApp2();
|
|
1043
|
-
const [route, setRoute] =
|
|
1044
|
-
const [session, setSession] =
|
|
1045
|
-
const [profiles, setProfiles] =
|
|
1046
|
-
const [loginTarget, setLoginTarget] =
|
|
1047
|
-
const [editingServer, setEditingServer] =
|
|
1048
|
-
const [busy, setBusy] =
|
|
1049
|
-
const [error, setError] =
|
|
1422
|
+
const [route, setRoute] = useState11("boot");
|
|
1423
|
+
const [session, setSession] = useState11();
|
|
1424
|
+
const [profiles, setProfiles] = useState11([]);
|
|
1425
|
+
const [loginTarget, setLoginTarget] = useState11();
|
|
1426
|
+
const [editingServer, setEditingServer] = useState11();
|
|
1427
|
+
const [busy, setBusy] = useState11(false);
|
|
1428
|
+
const [error, setError] = useState11();
|
|
1050
1429
|
useInput9((input, key) => {
|
|
1051
1430
|
if (key.ctrl && input === "q") exit();
|
|
1052
1431
|
});
|
|
@@ -1097,7 +1476,7 @@ function App({ engine }) {
|
|
|
1097
1476
|
},
|
|
1098
1477
|
[engine]
|
|
1099
1478
|
);
|
|
1100
|
-
|
|
1479
|
+
useEffect8(() => {
|
|
1101
1480
|
void bootstrap();
|
|
1102
1481
|
}, [bootstrap]);
|
|
1103
1482
|
const configure = async (serverUrl) => {
|
|
@@ -1230,6 +1609,7 @@ function App({ engine }) {
|
|
|
1230
1609
|
return /* @__PURE__ */ jsx10(
|
|
1231
1610
|
Dashboard,
|
|
1232
1611
|
{
|
|
1612
|
+
preferences,
|
|
1233
1613
|
engine,
|
|
1234
1614
|
session,
|
|
1235
1615
|
onProfiles: () => void openProfiles(),
|
|
@@ -1290,6 +1670,35 @@ function createScreenGuard(stream) {
|
|
|
1290
1670
|
};
|
|
1291
1671
|
}
|
|
1292
1672
|
|
|
1673
|
+
// src/tui/preferences.ts
|
|
1674
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
1675
|
+
import { dirname, join } from "node:path";
|
|
1676
|
+
function preferencesPath(env = process.env) {
|
|
1677
|
+
return join(dataDirectory(env), "tui.json");
|
|
1678
|
+
}
|
|
1679
|
+
function localeDefaultHangul(env = process.env) {
|
|
1680
|
+
const locale = env.LC_ALL || env.LC_CTYPE || env.LANG || "";
|
|
1681
|
+
return /^ko(_|-|\.|$)/i.test(locale.trim());
|
|
1682
|
+
}
|
|
1683
|
+
async function readPreferences(env = process.env) {
|
|
1684
|
+
const fallback = { hangulMode: localeDefaultHangul(env) };
|
|
1685
|
+
try {
|
|
1686
|
+
const raw = JSON.parse(await readFile(preferencesPath(env), "utf8"));
|
|
1687
|
+
return typeof raw.hangulMode === "boolean" ? { hangulMode: raw.hangulMode } : fallback;
|
|
1688
|
+
} catch {
|
|
1689
|
+
return fallback;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
async function writePreferences(preferences, env = process.env) {
|
|
1693
|
+
try {
|
|
1694
|
+
const path = preferencesPath(env);
|
|
1695
|
+
await mkdir(dirname(path), { recursive: true });
|
|
1696
|
+
await writeFile(path, `${JSON.stringify(preferences, null, 2)}
|
|
1697
|
+
`, "utf8");
|
|
1698
|
+
} catch {
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1293
1702
|
// src/tui/index.tsx
|
|
1294
1703
|
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1295
1704
|
async function runTui(engine) {
|
|
@@ -1302,12 +1711,25 @@ async function runTui(engine) {
|
|
|
1302
1711
|
process.once("exit", restore);
|
|
1303
1712
|
process.once("SIGINT", onSignal);
|
|
1304
1713
|
process.once("SIGTERM", onSignal);
|
|
1714
|
+
const preferences = await readPreferences();
|
|
1305
1715
|
screen.enter();
|
|
1306
1716
|
try {
|
|
1307
|
-
const instance = render(
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1717
|
+
const instance = render(
|
|
1718
|
+
/* @__PURE__ */ jsx11(
|
|
1719
|
+
App,
|
|
1720
|
+
{
|
|
1721
|
+
engine,
|
|
1722
|
+
preferences: {
|
|
1723
|
+
hangulMode: preferences.hangulMode,
|
|
1724
|
+
onHangulModeChange: (enabled) => void writePreferences({ hangulMode: enabled })
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
),
|
|
1728
|
+
{
|
|
1729
|
+
exitOnCtrlC: true,
|
|
1730
|
+
patchConsole: true
|
|
1731
|
+
}
|
|
1732
|
+
);
|
|
1311
1733
|
await instance.waitUntilExit();
|
|
1312
1734
|
} finally {
|
|
1313
1735
|
restore();
|
|
@@ -1319,4 +1741,4 @@ async function runTui(engine) {
|
|
|
1319
1741
|
export {
|
|
1320
1742
|
runTui
|
|
1321
1743
|
};
|
|
1322
|
-
//# sourceMappingURL=tui-
|
|
1744
|
+
//# sourceMappingURL=tui-DTMX2JZ7.js.map
|