xgen-dex-cli 1.4.0 → 1.5.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.
|
@@ -8,12 +8,12 @@ import { render } from "ink";
|
|
|
8
8
|
import { stdout } from "node:process";
|
|
9
9
|
|
|
10
10
|
// src/tui/app.tsx
|
|
11
|
-
import { useCallback, useEffect as
|
|
12
|
-
import { Box as
|
|
11
|
+
import { useCallback, useEffect as useEffect8, useState as useState11 } from "react";
|
|
12
|
+
import { Box as Box10, Text as Text10, useApp as useApp2, useInput as useInput9 } from "ink";
|
|
13
13
|
|
|
14
14
|
// src/tui/dashboard.tsx
|
|
15
|
-
import { useEffect as
|
|
16
|
-
import { Box as
|
|
15
|
+
import { useEffect as useEffect6, useReducer, useRef as useRef3, useState as useState7 } from "react";
|
|
16
|
+
import { Box as Box6, Text as Text6, useApp, useInput as useInput5 } from "ink";
|
|
17
17
|
|
|
18
18
|
// src/tui/chat-state.ts
|
|
19
19
|
var initialChatState = { messages: [], running: false };
|
|
@@ -134,18 +134,178 @@ function chatReducer(state, action) {
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
// src/tui/measure.ts
|
|
138
|
+
import { useEffect, useRef, useState } from "react";
|
|
139
|
+
function placementOf(node) {
|
|
140
|
+
const yogaSelf = node?.yogaNode;
|
|
141
|
+
if (!node || !yogaSelf) return void 0;
|
|
142
|
+
let x = 0;
|
|
143
|
+
let y = 0;
|
|
144
|
+
for (let current = node; current; current = current.parentNode) {
|
|
145
|
+
const yoga = current.yogaNode;
|
|
146
|
+
if (!yoga) continue;
|
|
147
|
+
x += yoga.getComputedLeft();
|
|
148
|
+
y += yoga.getComputedTop();
|
|
149
|
+
}
|
|
150
|
+
return { x, y, width: yogaSelf.getComputedWidth(), height: yogaSelf.getComputedHeight() };
|
|
151
|
+
}
|
|
152
|
+
function useMeasured() {
|
|
153
|
+
const ref = useRef(null);
|
|
154
|
+
const [placement, setPlacement] = useState(void 0);
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
const measured = placementOf(ref.current);
|
|
157
|
+
if (!measured) return;
|
|
158
|
+
if (placement?.x !== measured.x || placement?.y !== measured.y || placement?.width !== measured.width || placement?.height !== measured.height) {
|
|
159
|
+
setPlacement(measured);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
return [ref, placement];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/tui/transcript.ts
|
|
166
|
+
import stringWidth from "string-width";
|
|
167
|
+
var INDENT = " ";
|
|
168
|
+
function colorOf(role) {
|
|
169
|
+
if (role === "user") return "cyan";
|
|
170
|
+
if (role === "assistant") return "green";
|
|
171
|
+
if (role === "activity") return "yellow";
|
|
172
|
+
if (role === "system") return "red";
|
|
173
|
+
return void 0;
|
|
174
|
+
}
|
|
175
|
+
function labelOf(role, agentName) {
|
|
176
|
+
if (role === "user") return "You";
|
|
177
|
+
if (role === "assistant") return agentName;
|
|
178
|
+
if (role === "activity") return "Tool";
|
|
179
|
+
return "System";
|
|
180
|
+
}
|
|
181
|
+
function wrapToWidth(text, width) {
|
|
182
|
+
if (width <= 0) return [text];
|
|
183
|
+
const lines = [];
|
|
184
|
+
for (const paragraph of text.split("\n")) {
|
|
185
|
+
if (paragraph === "") {
|
|
186
|
+
lines.push("");
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
let line = "";
|
|
190
|
+
let lineWidth = 0;
|
|
191
|
+
const flush = () => {
|
|
192
|
+
lines.push(line);
|
|
193
|
+
line = "";
|
|
194
|
+
lineWidth = 0;
|
|
195
|
+
};
|
|
196
|
+
for (const word of paragraph.match(/\s+|\S+/g) ?? []) {
|
|
197
|
+
const wordWidth = stringWidth(word);
|
|
198
|
+
if (lineWidth > 0 && lineWidth + wordWidth > width) {
|
|
199
|
+
flush();
|
|
200
|
+
if (/^\s+$/.test(word)) continue;
|
|
201
|
+
}
|
|
202
|
+
if (wordWidth <= width) {
|
|
203
|
+
line += word;
|
|
204
|
+
lineWidth += wordWidth;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
for (const char of word) {
|
|
208
|
+
const charWidth = stringWidth(char);
|
|
209
|
+
if (lineWidth + charWidth > width) flush();
|
|
210
|
+
line += char;
|
|
211
|
+
lineWidth += charWidth;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
flush();
|
|
215
|
+
}
|
|
216
|
+
return lines;
|
|
217
|
+
}
|
|
218
|
+
function ruleFor(label, width) {
|
|
219
|
+
const room = Math.max(0, width - stringWidth("\u2500\u2500 "));
|
|
220
|
+
let trimmed = "";
|
|
221
|
+
let used = 0;
|
|
222
|
+
for (const char of label) {
|
|
223
|
+
const charWidth = stringWidth(char);
|
|
224
|
+
if (used + charWidth > room) {
|
|
225
|
+
trimmed = trimmed.slice(0, -1) + "\u2026";
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
trimmed += char;
|
|
229
|
+
used += charWidth;
|
|
230
|
+
}
|
|
231
|
+
const head = `\u2500\u2500 ${trimmed} `;
|
|
232
|
+
return head + "\u2500".repeat(Math.max(0, width - stringWidth(head)));
|
|
233
|
+
}
|
|
234
|
+
function renderTranscript(messages, agentName, width) {
|
|
235
|
+
const lines = [];
|
|
236
|
+
const bodyWidth = Math.max(1, width - INDENT.length);
|
|
237
|
+
for (const message of messages) {
|
|
238
|
+
const color = colorOf(message.role);
|
|
239
|
+
if (message.role === "activity") {
|
|
240
|
+
for (const [index, text] of wrapToWidth(message.text, bodyWidth).entries()) {
|
|
241
|
+
lines.push({
|
|
242
|
+
key: `${message.id}:${index}`,
|
|
243
|
+
text: `${index === 0 ? "\xB7 " : INDENT}${text}`,
|
|
244
|
+
role: "activity",
|
|
245
|
+
color
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (lines.length > 0) lines.push({ key: `${message.id}:gap`, text: "", role: "text" });
|
|
251
|
+
lines.push({
|
|
252
|
+
key: `${message.id}:label`,
|
|
253
|
+
text: ruleFor(labelOf(message.role, agentName), width),
|
|
254
|
+
role: "label",
|
|
255
|
+
color
|
|
256
|
+
});
|
|
257
|
+
const body = message.text || (message.role === "assistant" ? "\u2026" : "");
|
|
258
|
+
for (const [index, text] of wrapToWidth(body, bodyWidth).entries()) {
|
|
259
|
+
lines.push({
|
|
260
|
+
key: `${message.id}:${index}`,
|
|
261
|
+
text: INDENT + text,
|
|
262
|
+
role: message.role === "system" ? "system" : "text",
|
|
263
|
+
color: message.role === "system" ? color : void 0
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return lines;
|
|
268
|
+
}
|
|
269
|
+
function viewportOf(lines, height, scrollUp) {
|
|
270
|
+
if (height <= 0) return { lines: [], above: lines.length, below: 0 };
|
|
271
|
+
const maximumScroll2 = Math.max(0, lines.length - height);
|
|
272
|
+
const up = Math.min(Math.max(0, scrollUp), maximumScroll2);
|
|
273
|
+
const end = lines.length - up;
|
|
274
|
+
const start = Math.max(0, end - height);
|
|
275
|
+
return { lines: lines.slice(start, end), above: start, below: lines.length - end };
|
|
276
|
+
}
|
|
277
|
+
function maximumScroll(lineCount, height) {
|
|
278
|
+
return Math.max(0, lineCount - Math.max(0, height));
|
|
279
|
+
}
|
|
280
|
+
|
|
137
281
|
// src/tui/command-palette.tsx
|
|
138
|
-
import { useState as
|
|
139
|
-
import { Box as
|
|
282
|
+
import { useState as useState3 } from "react";
|
|
283
|
+
import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
|
|
140
284
|
|
|
141
285
|
// src/tui/components.tsx
|
|
142
|
-
import { Box, Text as Text2 } from "ink";
|
|
286
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
143
287
|
|
|
144
288
|
// src/tui/ime-text-input.tsx
|
|
145
|
-
import { useEffect, useRef, useState } from "react";
|
|
146
|
-
import { Text, useCursor, useInput
|
|
147
|
-
import
|
|
148
|
-
|
|
289
|
+
import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
290
|
+
import { Box, Text, useCursor, useInput } from "ink";
|
|
291
|
+
import stringWidth2 from "string-width";
|
|
292
|
+
|
|
293
|
+
// src/tui/terminal-input.ts
|
|
294
|
+
var PASTE_START = "[200~";
|
|
295
|
+
var PASTE_END = "[201~";
|
|
296
|
+
var CONTROL_TAIL = /^(?:\[[\d;:<>?!"'$ ]*[@-~]|O[@-~])$/;
|
|
297
|
+
var CONTROL_CHARS = /[\u0000-\u001F\u007F]/g;
|
|
298
|
+
function classifyInput(input, pasting = false) {
|
|
299
|
+
if (input === PASTE_START) return { kind: "paste-start" };
|
|
300
|
+
if (input === PASTE_END) return { kind: "paste-end" };
|
|
301
|
+
if (!pasting && input.length > 1 && CONTROL_TAIL.test(input)) return { kind: "ignore" };
|
|
302
|
+
const flattened = input.replace(/\r\n|[\r\n]/g, " ");
|
|
303
|
+
const text = flattened.replace(CONTROL_CHARS, "");
|
|
304
|
+
return text ? { kind: "text", text } : { kind: "ignore" };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// src/tui/ime-text-input.tsx
|
|
308
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
149
309
|
var segmenter = new Intl.Segmenter("ko", { granularity: "grapheme" });
|
|
150
310
|
function graphemes(value) {
|
|
151
311
|
return [...segmenter.segment(value)].map(({ segment }) => segment);
|
|
@@ -156,10 +316,10 @@ function clamp(value, minimum, maximum) {
|
|
|
156
316
|
function visibleInput(segments, cursor, maximumWidth) {
|
|
157
317
|
let start = cursor;
|
|
158
318
|
let widthBeforeCursor = 0;
|
|
159
|
-
const followingWidth = cursor < segments.length ?
|
|
319
|
+
const followingWidth = cursor < segments.length ? stringWidth2(segments[cursor] ?? "") : 0;
|
|
160
320
|
const beforeLimit = Math.max(0, maximumWidth - Math.min(followingWidth, maximumWidth));
|
|
161
321
|
while (start > 0) {
|
|
162
|
-
const width =
|
|
322
|
+
const width = stringWidth2(segments[start - 1] ?? "");
|
|
163
323
|
if (widthBeforeCursor + width > beforeLimit) break;
|
|
164
324
|
widthBeforeCursor += width;
|
|
165
325
|
start -= 1;
|
|
@@ -167,7 +327,7 @@ function visibleInput(segments, cursor, maximumWidth) {
|
|
|
167
327
|
let end = cursor;
|
|
168
328
|
let totalWidth = widthBeforeCursor;
|
|
169
329
|
while (end < segments.length) {
|
|
170
|
-
const width =
|
|
330
|
+
const width = stringWidth2(segments[end] ?? "");
|
|
171
331
|
if (totalWidth + width > maximumWidth) break;
|
|
172
332
|
totalWidth += width;
|
|
173
333
|
end += 1;
|
|
@@ -177,17 +337,18 @@ function visibleInput(segments, cursor, maximumWidth) {
|
|
|
177
337
|
cursorWidth: widthBeforeCursor
|
|
178
338
|
};
|
|
179
339
|
}
|
|
180
|
-
function TerminalCursor({
|
|
340
|
+
function TerminalCursor({ x, y }) {
|
|
181
341
|
const { setCursorPosition } = useCursor();
|
|
182
|
-
setCursorPosition({ x
|
|
342
|
+
setCursorPosition({ x, y });
|
|
183
343
|
return null;
|
|
184
344
|
}
|
|
185
345
|
function ImeTextInput(props) {
|
|
186
|
-
const
|
|
346
|
+
const [ref, placement] = useMeasured();
|
|
187
347
|
const initialSegments = graphemes(props.value);
|
|
188
|
-
const [cursor, setCursor] =
|
|
189
|
-
const valueRef =
|
|
190
|
-
const cursorRef =
|
|
348
|
+
const [cursor, setCursor] = useState2(initialSegments.length);
|
|
349
|
+
const valueRef = useRef2(props.value);
|
|
350
|
+
const cursorRef = useRef2(initialSegments.length);
|
|
351
|
+
const pastingRef = useRef2(false);
|
|
191
352
|
const moveCursor = (next, length) => {
|
|
192
353
|
const resolved = clamp(next, 0, length);
|
|
193
354
|
cursorRef.current = resolved;
|
|
@@ -199,7 +360,7 @@ function ImeTextInput(props) {
|
|
|
199
360
|
moveCursor(nextCursor, segments.length);
|
|
200
361
|
props.onChange(nextValue);
|
|
201
362
|
};
|
|
202
|
-
|
|
363
|
+
useEffect2(() => {
|
|
203
364
|
if (props.value === valueRef.current) return;
|
|
204
365
|
const previousLength = graphemes(valueRef.current).length;
|
|
205
366
|
const nextLength = graphemes(props.value).length;
|
|
@@ -211,6 +372,23 @@ function ImeTextInput(props) {
|
|
|
211
372
|
(input, key) => {
|
|
212
373
|
const current = graphemes(valueRef.current);
|
|
213
374
|
const currentCursor = clamp(cursorRef.current, 0, current.length);
|
|
375
|
+
const event = classifyInput(input, pastingRef.current);
|
|
376
|
+
if (event.kind === "paste-start") {
|
|
377
|
+
pastingRef.current = true;
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (event.kind === "paste-end") {
|
|
381
|
+
pastingRef.current = false;
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
if (pastingRef.current) {
|
|
385
|
+
if (event.kind === "text") {
|
|
386
|
+
const inserted2 = graphemes(event.text);
|
|
387
|
+
current.splice(currentCursor, 0, ...inserted2);
|
|
388
|
+
updateValue(current, currentCursor + inserted2.length);
|
|
389
|
+
}
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
214
392
|
if (key.return) {
|
|
215
393
|
props.onSubmit?.(valueRef.current);
|
|
216
394
|
return;
|
|
@@ -237,10 +415,11 @@ function ImeTextInput(props) {
|
|
|
237
415
|
updateValue(current, currentCursor - 1);
|
|
238
416
|
return;
|
|
239
417
|
}
|
|
240
|
-
if (
|
|
418
|
+
if (key.ctrl || key.meta || key.tab || key.escape || key.upArrow || key.downArrow || key.pageUp || key.pageDown) {
|
|
241
419
|
return;
|
|
242
420
|
}
|
|
243
|
-
|
|
421
|
+
if (event.kind !== "text") return;
|
|
422
|
+
const inserted = graphemes(event.text);
|
|
244
423
|
current.splice(currentCursor, 0, ...inserted);
|
|
245
424
|
updateValue(current, currentCursor + inserted.length);
|
|
246
425
|
},
|
|
@@ -249,18 +428,18 @@ function ImeTextInput(props) {
|
|
|
249
428
|
const rawSegments = graphemes(props.value);
|
|
250
429
|
const safeCursor = clamp(cursor, 0, rawSegments.length);
|
|
251
430
|
const displayedSegments = props.mask ? rawSegments.map(() => props.mask ?? "") : rawSegments;
|
|
252
|
-
const maximumWidth = Math.max(1, (
|
|
431
|
+
const maximumWidth = Math.max(1, (placement?.width ?? 40) - 1);
|
|
253
432
|
const visible = visibleInput(displayedSegments, safeCursor, maximumWidth);
|
|
254
|
-
return /* @__PURE__ */ jsxs(
|
|
433
|
+
return /* @__PURE__ */ jsxs(Box, { ref, flexGrow: 1, children: [
|
|
255
434
|
rawSegments.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: props.placeholder ?? "" }) : /* @__PURE__ */ jsx(Text, { children: visible.text }),
|
|
256
|
-
props.focus ? /* @__PURE__ */ jsx(TerminalCursor, {
|
|
435
|
+
props.focus && placement ? /* @__PURE__ */ jsx(TerminalCursor, { x: placement.x + visible.cursorWidth, y: placement.y }) : null
|
|
257
436
|
] });
|
|
258
437
|
}
|
|
259
438
|
|
|
260
439
|
// src/tui/components.tsx
|
|
261
440
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
262
441
|
function Header(props) {
|
|
263
|
-
return /* @__PURE__ */ jsxs2(
|
|
442
|
+
return /* @__PURE__ */ jsxs2(Box2, { paddingX: 1, justifyContent: "space-between", children: [
|
|
264
443
|
/* @__PURE__ */ jsx2(Text2, { bold: true, color: "blueBright", children: "XGEN Dex" }),
|
|
265
444
|
props.profile ? /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
266
445
|
props.profile,
|
|
@@ -273,52 +452,55 @@ function Header(props) {
|
|
|
273
452
|
] });
|
|
274
453
|
}
|
|
275
454
|
function Footer({ text }) {
|
|
276
|
-
return /* @__PURE__ */ jsx2(
|
|
455
|
+
return /* @__PURE__ */ jsx2(Box2, { paddingX: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: text }) });
|
|
277
456
|
}
|
|
278
457
|
function Loading({ label = "\uBD88\uB7EC\uC624\uB294 \uC911..." }) {
|
|
279
|
-
return /* @__PURE__ */ jsx2(
|
|
458
|
+
return /* @__PURE__ */ jsx2(Box2, { padding: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: "cyan", children: [
|
|
280
459
|
"\u25C6 ",
|
|
281
460
|
label
|
|
282
461
|
] }) });
|
|
283
462
|
}
|
|
284
463
|
function Notice({ children, error = false }) {
|
|
285
|
-
return /* @__PURE__ */ jsx2(
|
|
464
|
+
return /* @__PURE__ */ jsx2(Box2, { borderStyle: "round", borderColor: error ? "red" : "cyan", paddingX: 1, children: /* @__PURE__ */ jsx2(Text2, { color: error ? "red" : void 0, children }) });
|
|
286
465
|
}
|
|
287
466
|
function FormField(props) {
|
|
288
|
-
return
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
props.
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
467
|
+
return (
|
|
468
|
+
// 가로로 늘려 둬야 입력 칸이 남는 폭을 알 수 있다. 내용만큼만 넓으면 칸의 폭이
|
|
469
|
+
// 곧 글자 폭이 되어, 긴 주소가 스스로를 잘라 내는 꼴이 된다.
|
|
470
|
+
/* @__PURE__ */ jsxs2(Box2, { flexGrow: 1, children: [
|
|
471
|
+
/* @__PURE__ */ jsx2(Box2, { width: 14, flexShrink: 0, children: /* @__PURE__ */ jsxs2(Text2, { color: props.focus ? "cyan" : void 0, children: [
|
|
472
|
+
props.focus ? "\u203A" : " ",
|
|
473
|
+
" ",
|
|
474
|
+
props.label
|
|
475
|
+
] }) }),
|
|
476
|
+
/* @__PURE__ */ jsx2(
|
|
477
|
+
ImeTextInput,
|
|
478
|
+
{
|
|
479
|
+
value: props.value,
|
|
480
|
+
onChange: props.onChange,
|
|
481
|
+
onSubmit: props.onSubmit,
|
|
482
|
+
focus: props.focus,
|
|
483
|
+
placeholder: props.placeholder,
|
|
484
|
+
mask: props.secret ? "\u2022" : void 0
|
|
485
|
+
}
|
|
486
|
+
)
|
|
487
|
+
] })
|
|
488
|
+
);
|
|
307
489
|
}
|
|
308
490
|
|
|
309
491
|
// src/tui/command-palette.tsx
|
|
310
492
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
311
493
|
function CommandPalette(props) {
|
|
312
|
-
const [cursor, setCursor] =
|
|
494
|
+
const [cursor, setCursor] = useState3(0);
|
|
313
495
|
useInput2((_input, key) => {
|
|
314
496
|
if (key.escape) props.onCancel();
|
|
315
497
|
if (key.upArrow) setCursor((current) => Math.max(0, current - 1));
|
|
316
498
|
if (key.downArrow) setCursor((current) => Math.min(props.actions.length - 1, current + 1));
|
|
317
499
|
if (key.return && props.actions[cursor]) props.actions[cursor].run();
|
|
318
500
|
});
|
|
319
|
-
return /* @__PURE__ */ jsxs3(
|
|
501
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", flexGrow: 1, borderStyle: "double", borderColor: "magenta", padding: 1, children: [
|
|
320
502
|
/* @__PURE__ */ jsx3(Text3, { bold: true, children: "\uBA85\uB839" }),
|
|
321
|
-
/* @__PURE__ */ jsx3(
|
|
503
|
+
/* @__PURE__ */ jsx3(Box3, { flexDirection: "column", marginTop: 1, children: props.actions.map((action, index) => /* @__PURE__ */ jsxs3(Text3, { color: index === cursor ? "magentaBright" : void 0, children: [
|
|
322
504
|
index === cursor ? "\u203A" : " ",
|
|
323
505
|
" ",
|
|
324
506
|
action.label
|
|
@@ -328,15 +510,15 @@ function CommandPalette(props) {
|
|
|
328
510
|
}
|
|
329
511
|
|
|
330
512
|
// src/tui/history-screen.tsx
|
|
331
|
-
import { useEffect as
|
|
332
|
-
import { Box as
|
|
513
|
+
import { useEffect as useEffect3, useState as useState4 } from "react";
|
|
514
|
+
import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
|
|
333
515
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
334
516
|
function HistoryScreen(props) {
|
|
335
|
-
const [items, setItems] =
|
|
336
|
-
const [cursor, setCursor] =
|
|
337
|
-
const [loading, setLoading] =
|
|
338
|
-
const [error, setError] =
|
|
339
|
-
|
|
517
|
+
const [items, setItems] = useState4([]);
|
|
518
|
+
const [cursor, setCursor] = useState4(0);
|
|
519
|
+
const [loading, setLoading] = useState4(true);
|
|
520
|
+
const [error, setError] = useState4();
|
|
521
|
+
useEffect3(() => {
|
|
340
522
|
let alive = true;
|
|
341
523
|
props.engine.listConversations(props.profile).then((result) => alive && setItems(result)).catch((reason) => alive && setError(publicError(reason).message)).finally(() => alive && setLoading(false));
|
|
342
524
|
return () => {
|
|
@@ -364,7 +546,7 @@ function HistoryScreen(props) {
|
|
|
364
546
|
},
|
|
365
547
|
{ isActive: !loading }
|
|
366
548
|
);
|
|
367
|
-
return /* @__PURE__ */ jsxs4(
|
|
549
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "cyan", padding: 1, children: [
|
|
368
550
|
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "\uB300\uD654 \uAE30\uB85D" }),
|
|
369
551
|
loading ? /* @__PURE__ */ jsx4(Loading, {}) : null,
|
|
370
552
|
!loading && items.length === 0 ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\uB300\uD654 \uAE30\uB85D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }) : null,
|
|
@@ -385,18 +567,18 @@ function HistoryScreen(props) {
|
|
|
385
567
|
}
|
|
386
568
|
|
|
387
569
|
// src/tui/start-panel.tsx
|
|
388
|
-
import { useEffect as
|
|
389
|
-
import { Box as
|
|
570
|
+
import { useEffect as useEffect4, useState as useState5 } from "react";
|
|
571
|
+
import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
|
|
390
572
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
391
573
|
function StartPanel(props) {
|
|
392
574
|
const rows = [
|
|
393
575
|
{ kind: "new" },
|
|
394
576
|
...props.conversations.map((conversation) => ({ kind: "conversation", conversation }))
|
|
395
577
|
];
|
|
396
|
-
const [cursor, setCursor] =
|
|
397
|
-
const [opening, setOpening] =
|
|
398
|
-
const [error, setError] =
|
|
399
|
-
|
|
578
|
+
const [cursor, setCursor] = useState5(0);
|
|
579
|
+
const [opening, setOpening] = useState5(false);
|
|
580
|
+
const [error, setError] = useState5();
|
|
581
|
+
useEffect4(() => {
|
|
400
582
|
setCursor((current) => Math.min(current, Math.max(0, rows.length - 1)));
|
|
401
583
|
}, [rows.length]);
|
|
402
584
|
useInput4(
|
|
@@ -429,10 +611,10 @@ function StartPanel(props) {
|
|
|
429
611
|
},
|
|
430
612
|
{ isActive: !opening }
|
|
431
613
|
);
|
|
432
|
-
return /* @__PURE__ */ jsxs5(
|
|
614
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "cyan", padding: 1, children: [
|
|
433
615
|
/* @__PURE__ */ jsx5(Text5, { bold: true, children: props.agentName }),
|
|
434
616
|
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\uC5B4\uB5BB\uAC8C \uC2DC\uC791\uD560\uAE4C\uC694?" }),
|
|
435
|
-
/* @__PURE__ */ jsx5(
|
|
617
|
+
/* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginTop: 1, children: rows.map((row, index) => {
|
|
436
618
|
const active = index === cursor;
|
|
437
619
|
const mark = active ? "\u203A" : " ";
|
|
438
620
|
if (row.kind === "new") {
|
|
@@ -457,7 +639,7 @@ function StartPanel(props) {
|
|
|
457
639
|
}) }),
|
|
458
640
|
opening ? /* @__PURE__ */ jsx5(Loading, { label: "\uB300\uD654\uB97C \uBD88\uB7EC\uC624\uB294 \uC911..." }) : null,
|
|
459
641
|
error ? /* @__PURE__ */ jsx5(Notice, { error: true, children: error }) : null,
|
|
460
|
-
/* @__PURE__ */ jsx5(
|
|
642
|
+
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2191\u2193 \uC774\uB3D9 \xB7 Enter \uC120\uD0DD \xB7 Esc \uBAA9\uB85D\uC73C\uB85C" }) })
|
|
461
643
|
] });
|
|
462
644
|
}
|
|
463
645
|
function when(conversation) {
|
|
@@ -471,17 +653,17 @@ function when(conversation) {
|
|
|
471
653
|
}
|
|
472
654
|
|
|
473
655
|
// src/tui/use-terminal-size.ts
|
|
474
|
-
import { useEffect as
|
|
475
|
-
import { useStdout
|
|
656
|
+
import { useEffect as useEffect5, useState as useState6 } from "react";
|
|
657
|
+
import { useStdout } from "ink";
|
|
476
658
|
function useTerminalSize() {
|
|
477
|
-
const { stdout: stdout2 } =
|
|
659
|
+
const { stdout: stdout2 } = useStdout();
|
|
478
660
|
const read = () => {
|
|
479
661
|
const columns = stdout2.columns || 100;
|
|
480
662
|
const rows = stdout2.rows || 30;
|
|
481
663
|
return { columns, rows, wide: columns >= 88 };
|
|
482
664
|
};
|
|
483
|
-
const [size, setSize] =
|
|
484
|
-
|
|
665
|
+
const [size, setSize] = useState6(read);
|
|
666
|
+
useEffect5(() => {
|
|
485
667
|
const resize = () => setSize(read());
|
|
486
668
|
stdout2.on("resize", resize);
|
|
487
669
|
return () => {
|
|
@@ -497,7 +679,7 @@ function AgentSidebar(props) {
|
|
|
497
679
|
const radius = Math.max(3, Math.floor((props.height - 4) / 2));
|
|
498
680
|
const start = Math.max(0, props.cursor - radius);
|
|
499
681
|
const visible = props.agents.slice(start, start + radius * 2 + 1);
|
|
500
|
-
return /* @__PURE__ */ jsxs6(
|
|
682
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", width: 30, borderStyle: "round", borderColor: props.focused ? "cyan" : "gray", paddingX: 1, children: [
|
|
501
683
|
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Agents" }),
|
|
502
684
|
visible.map((agent) => {
|
|
503
685
|
const index = props.agents.indexOf(agent);
|
|
@@ -514,39 +696,46 @@ function AgentSidebar(props) {
|
|
|
514
696
|
props.agents.length === 0 ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\uC0AC\uC6A9 \uAC00\uB2A5\uD55C Agent\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." }) : null
|
|
515
697
|
] });
|
|
516
698
|
}
|
|
517
|
-
function messageColor(role) {
|
|
518
|
-
if (role === "user") return "cyan";
|
|
519
|
-
if (role === "assistant") return "green";
|
|
520
|
-
if (role === "activity") return "yellow";
|
|
521
|
-
if (role === "system") return "red";
|
|
522
|
-
return void 0;
|
|
523
|
-
}
|
|
524
|
-
function labelOf(role, agentName) {
|
|
525
|
-
if (role === "user") return "You";
|
|
526
|
-
if (role === "assistant") return agentName;
|
|
527
|
-
if (role === "activity") return "Tool";
|
|
528
|
-
return "System";
|
|
529
|
-
}
|
|
530
699
|
function ChatPane(props) {
|
|
531
|
-
const
|
|
532
|
-
const
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
700
|
+
const [ref, box] = useMeasured();
|
|
701
|
+
const width = box?.width ?? 20;
|
|
702
|
+
const height = box?.height ?? 1;
|
|
703
|
+
const lines = renderTranscript(props.messages, props.agent?.workflowName ?? "Agent", width);
|
|
704
|
+
const view = viewportOf(lines, height, props.scrollUp);
|
|
705
|
+
useEffect6(() => {
|
|
706
|
+
props.onViewport(lines.length, height);
|
|
707
|
+
}, [lines.length, height]);
|
|
708
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", flexGrow: 1, borderStyle: "round", borderColor: "blue", paddingX: 1, children: [
|
|
709
|
+
/* @__PURE__ */ jsxs6(Box6, { children: [
|
|
710
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, wrap: "truncate-end", children: props.agent?.workflowName ?? "Agent\uB97C \uC120\uD0DD\uD558\uC138\uC694" }),
|
|
711
|
+
view.below > 0 ? /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
712
|
+
" \xB7 \u2193",
|
|
713
|
+
view.below,
|
|
714
|
+
"\uC904"
|
|
715
|
+
] }) : null
|
|
541
716
|
] }),
|
|
542
|
-
|
|
717
|
+
/* @__PURE__ */ jsxs6(Box6, { ref, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [
|
|
718
|
+
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,
|
|
719
|
+
view.lines.map((line) => /* @__PURE__ */ jsx6(
|
|
720
|
+
Text6,
|
|
721
|
+
{
|
|
722
|
+
wrap: "truncate-end",
|
|
723
|
+
bold: line.role === "label",
|
|
724
|
+
dimColor: line.role === "activity",
|
|
725
|
+
color: line.color,
|
|
726
|
+
children: line.text || " "
|
|
727
|
+
},
|
|
728
|
+
line.key
|
|
729
|
+
))
|
|
730
|
+
] }),
|
|
731
|
+
props.status ? /* @__PURE__ */ jsxs6(Text6, { color: "yellow", wrap: "truncate-end", children: [
|
|
543
732
|
"\u25C6 ",
|
|
544
733
|
props.status
|
|
545
734
|
] }) : null
|
|
546
735
|
] });
|
|
547
736
|
}
|
|
548
737
|
function Composer(props) {
|
|
549
|
-
return /* @__PURE__ */ jsxs6(
|
|
738
|
+
return /* @__PURE__ */ jsxs6(Box6, { borderStyle: "round", borderColor: props.focused ? "cyan" : "gray", paddingX: 1, children: [
|
|
550
739
|
/* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "\u203A " }),
|
|
551
740
|
props.disabled ? /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\uC751\uB2F5\uC744 \uAE30\uB2E4\uB9AC\uB294 \uC911..." }) : /* @__PURE__ */ jsx6(
|
|
552
741
|
ImeTextInput,
|
|
@@ -555,7 +744,6 @@ function Composer(props) {
|
|
|
555
744
|
onChange: props.onChange,
|
|
556
745
|
onSubmit: props.onSubmit,
|
|
557
746
|
focus: props.focused,
|
|
558
|
-
cursorOrigin: props.cursorOrigin,
|
|
559
747
|
placeholder: "\uBA54\uC2DC\uC9C0\uB97C \uC785\uB825\uD558\uC138\uC694"
|
|
560
748
|
}
|
|
561
749
|
)
|
|
@@ -565,20 +753,22 @@ function Dashboard(props) {
|
|
|
565
753
|
const { exit } = useApp();
|
|
566
754
|
const size = useTerminalSize();
|
|
567
755
|
const bodyHeight = Math.max(12, size.rows - 5);
|
|
568
|
-
const [focus, setFocus] =
|
|
569
|
-
const [cursor, setCursor] =
|
|
570
|
-
const [selected, setSelected] =
|
|
756
|
+
const [focus, setFocus] = useState7("agents");
|
|
757
|
+
const [cursor, setCursor] = useState7(0);
|
|
758
|
+
const [selected, setSelected] = useState7(() => {
|
|
571
759
|
const first = props.session.agents[0];
|
|
572
760
|
return first ? { workflowId: first.workflowId, workflowName: first.workflowName } : void 0;
|
|
573
761
|
});
|
|
574
|
-
const [input, setInput] =
|
|
762
|
+
const [input, setInput] = useState7("");
|
|
575
763
|
const [chat, dispatch] = useReducer(chatReducer, initialChatState);
|
|
576
|
-
const [palette, setPalette] =
|
|
577
|
-
const [history, setHistory] =
|
|
578
|
-
const [start, setStart] =
|
|
579
|
-
const [
|
|
580
|
-
const
|
|
581
|
-
|
|
764
|
+
const [palette, setPalette] = useState7(false);
|
|
765
|
+
const [history, setHistory] = useState7(false);
|
|
766
|
+
const [start, setStart] = useState7();
|
|
767
|
+
const [scrollUp, setScrollUp] = useState7(0);
|
|
768
|
+
const viewport = useRef3({ lineCount: 0, height: 0 });
|
|
769
|
+
const [starting, setStarting] = useState7(false);
|
|
770
|
+
const controller = useRef3(null);
|
|
771
|
+
useEffect6(() => () => controller.current?.abort(), []);
|
|
582
772
|
const selectAgent = async () => {
|
|
583
773
|
const agent = props.session.agents[cursor];
|
|
584
774
|
if (!agent || chat.running || starting) return;
|
|
@@ -604,12 +794,14 @@ function Dashboard(props) {
|
|
|
604
794
|
setSelected(ref);
|
|
605
795
|
dispatch({ type: "reset" });
|
|
606
796
|
setInput("");
|
|
797
|
+
setScrollUp(0);
|
|
607
798
|
setStart(void 0);
|
|
608
799
|
setFocus("composer");
|
|
609
800
|
};
|
|
610
801
|
const openHistory = (conversation, turns) => {
|
|
611
802
|
setSelected({ workflowId: conversation.workflowId, workflowName: conversation.workflowName });
|
|
612
803
|
dispatch({ type: "history_loaded", interactionId: conversation.interactionId, turns });
|
|
804
|
+
setScrollUp(0);
|
|
613
805
|
const index = props.session.agents.findIndex((agent) => agent.workflowId === conversation.workflowId);
|
|
614
806
|
if (index >= 0) setCursor(index);
|
|
615
807
|
setHistory(false);
|
|
@@ -624,6 +816,7 @@ function Dashboard(props) {
|
|
|
624
816
|
if (chat.running) return;
|
|
625
817
|
dispatch({ type: "reset" });
|
|
626
818
|
setInput("");
|
|
819
|
+
setScrollUp(0);
|
|
627
820
|
setPalette(false);
|
|
628
821
|
setFocus("composer");
|
|
629
822
|
};
|
|
@@ -639,6 +832,7 @@ function Dashboard(props) {
|
|
|
639
832
|
input: text
|
|
640
833
|
});
|
|
641
834
|
setInput("");
|
|
835
|
+
setScrollUp(0);
|
|
642
836
|
dispatch({ type: "turn_started", interactionId: resolved.interactionId, input: text });
|
|
643
837
|
const active = new AbortController();
|
|
644
838
|
controller.current = active;
|
|
@@ -654,6 +848,12 @@ function Dashboard(props) {
|
|
|
654
848
|
controller.current = null;
|
|
655
849
|
}
|
|
656
850
|
};
|
|
851
|
+
const scrollBy = (direction) => {
|
|
852
|
+
const { lineCount, height } = viewport.current;
|
|
853
|
+
const step = Math.max(1, Math.floor(height / 2));
|
|
854
|
+
const limit = maximumScroll(lineCount, height);
|
|
855
|
+
setScrollUp((current) => Math.min(limit, Math.max(0, current - direction * step)));
|
|
856
|
+
};
|
|
657
857
|
useInput5(
|
|
658
858
|
(keyInput, key) => {
|
|
659
859
|
if (key.ctrl && keyInput === "k") setPalette(true);
|
|
@@ -664,6 +864,8 @@ function Dashboard(props) {
|
|
|
664
864
|
if (chat.running) cancelTurn();
|
|
665
865
|
setHistory(true);
|
|
666
866
|
} else if (key.ctrl && keyInput === "n") newConversation();
|
|
867
|
+
else if (key.pageUp) scrollBy(-1);
|
|
868
|
+
else if (key.pageDown) scrollBy(1);
|
|
667
869
|
else if (key.escape && chat.running) cancelTurn();
|
|
668
870
|
else if (key.escape) setFocus("agents");
|
|
669
871
|
else if (key.tab) setFocus((current) => current === "agents" ? "composer" : "agents");
|
|
@@ -744,8 +946,19 @@ function Dashboard(props) {
|
|
|
744
946
|
setFocus("agents");
|
|
745
947
|
}
|
|
746
948
|
}
|
|
747
|
-
) : /* @__PURE__ */ jsxs6(
|
|
748
|
-
/* @__PURE__ */ jsx6(
|
|
949
|
+
) : /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", flexGrow: 1, children: [
|
|
950
|
+
/* @__PURE__ */ jsx6(
|
|
951
|
+
ChatPane,
|
|
952
|
+
{
|
|
953
|
+
agent: selected,
|
|
954
|
+
messages: chat.messages,
|
|
955
|
+
status: chat.status,
|
|
956
|
+
scrollUp,
|
|
957
|
+
onViewport: (lineCount, height) => {
|
|
958
|
+
viewport.current = { lineCount, height };
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
),
|
|
749
962
|
/* @__PURE__ */ jsx6(
|
|
750
963
|
Composer,
|
|
751
964
|
{
|
|
@@ -753,17 +966,16 @@ function Dashboard(props) {
|
|
|
753
966
|
onChange: setInput,
|
|
754
967
|
onSubmit: (value) => void send(value),
|
|
755
968
|
focused: focus === "composer",
|
|
756
|
-
disabled: chat.running || !selected
|
|
757
|
-
cursorOrigin: { x: size.wide ? 34 : 4, y: bodyHeight - 1 }
|
|
969
|
+
disabled: chat.running || !selected
|
|
758
970
|
}
|
|
759
971
|
)
|
|
760
972
|
] });
|
|
761
|
-
body = size.wide ? /* @__PURE__ */ jsxs6(
|
|
973
|
+
body = size.wide ? /* @__PURE__ */ jsxs6(Box6, { height: bodyHeight, children: [
|
|
762
974
|
sidebar,
|
|
763
975
|
conversation
|
|
764
|
-
] }) : focus === "agents" ? /* @__PURE__ */ jsx6(
|
|
976
|
+
] }) : focus === "agents" ? /* @__PURE__ */ jsx6(Box6, { height: bodyHeight, children: sidebar }) : /* @__PURE__ */ jsx6(Box6, { height: bodyHeight, children: conversation });
|
|
765
977
|
}
|
|
766
|
-
return /* @__PURE__ */ jsxs6(
|
|
978
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
767
979
|
/* @__PURE__ */ jsx6(
|
|
768
980
|
Header,
|
|
769
981
|
{
|
|
@@ -773,18 +985,18 @@ function Dashboard(props) {
|
|
|
773
985
|
}
|
|
774
986
|
),
|
|
775
987
|
body,
|
|
776
|
-
/* @__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" })
|
|
988
|
+
/* @__PURE__ */ jsx6(Footer, { text: "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" })
|
|
777
989
|
] });
|
|
778
990
|
}
|
|
779
991
|
|
|
780
992
|
// src/tui/login-screen.tsx
|
|
781
|
-
import { useState as
|
|
782
|
-
import { Box as
|
|
993
|
+
import { useState as useState8 } from "react";
|
|
994
|
+
import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
|
|
783
995
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
784
996
|
function LoginScreen(props) {
|
|
785
|
-
const [email, setEmail] =
|
|
786
|
-
const [password, setPassword] =
|
|
787
|
-
const [focus, setFocus] =
|
|
997
|
+
const [email, setEmail] = useState8("");
|
|
998
|
+
const [password, setPassword] = useState8("");
|
|
999
|
+
const [focus, setFocus] = useState8("email");
|
|
788
1000
|
useInput6(
|
|
789
1001
|
(input, key) => {
|
|
790
1002
|
if (key.tab) setFocus((current) => current === "email" ? "password" : "email");
|
|
@@ -804,16 +1016,16 @@ function LoginScreen(props) {
|
|
|
804
1016
|
props.onSubmit(email.trim(), secret);
|
|
805
1017
|
}
|
|
806
1018
|
};
|
|
807
|
-
return /* @__PURE__ */ jsxs7(
|
|
1019
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
808
1020
|
/* @__PURE__ */ jsx7(Header, { profile: props.profile, connected: false }),
|
|
809
|
-
/* @__PURE__ */ jsxs7(
|
|
1021
|
+
/* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", borderStyle: "round", borderColor: "blue", padding: 1, children: [
|
|
810
1022
|
/* @__PURE__ */ jsx7(Text7, { bold: true, children: "\uB85C\uADF8\uC778" }),
|
|
811
1023
|
/* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
|
|
812
1024
|
props.serverUrl,
|
|
813
1025
|
" ",
|
|
814
1026
|
/* @__PURE__ */ jsx7(Text7, { color: "cyan", children: "(Ctrl+E \uBC14\uAFB8\uAE30)" })
|
|
815
1027
|
] }),
|
|
816
|
-
/* @__PURE__ */ jsxs7(
|
|
1028
|
+
/* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginTop: 1, children: [
|
|
817
1029
|
/* @__PURE__ */ jsx7(
|
|
818
1030
|
FormField,
|
|
819
1031
|
{
|
|
@@ -822,7 +1034,6 @@ function LoginScreen(props) {
|
|
|
822
1034
|
onChange: setEmail,
|
|
823
1035
|
onSubmit: () => setFocus("password"),
|
|
824
1036
|
focus: !props.busy && focus === "email",
|
|
825
|
-
cursorOrigin: { x: 16, y: 6 },
|
|
826
1037
|
placeholder: "me@corp.com"
|
|
827
1038
|
}
|
|
828
1039
|
),
|
|
@@ -834,7 +1045,6 @@ function LoginScreen(props) {
|
|
|
834
1045
|
onChange: setPassword,
|
|
835
1046
|
onSubmit: submit,
|
|
836
1047
|
focus: !props.busy && focus === "password",
|
|
837
|
-
cursorOrigin: { x: 16, y: 7 },
|
|
838
1048
|
secret: true
|
|
839
1049
|
}
|
|
840
1050
|
)
|
|
@@ -847,16 +1057,16 @@ function LoginScreen(props) {
|
|
|
847
1057
|
}
|
|
848
1058
|
|
|
849
1059
|
// src/tui/profile-screen.tsx
|
|
850
|
-
import { useEffect as
|
|
851
|
-
import { Box as
|
|
1060
|
+
import { useEffect as useEffect7, useState as useState9 } from "react";
|
|
1061
|
+
import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
|
|
852
1062
|
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
853
1063
|
function ProfileScreen(props) {
|
|
854
|
-
const [cursor, setCursor] =
|
|
855
|
-
const [creating, setCreating] =
|
|
856
|
-
const [focus, setFocus] =
|
|
857
|
-
const [name, setName] =
|
|
858
|
-
const [serverUrl, setServerUrl] =
|
|
859
|
-
|
|
1064
|
+
const [cursor, setCursor] = useState9(Math.max(0, props.profiles.findIndex((profile) => profile.current)));
|
|
1065
|
+
const [creating, setCreating] = useState9(false);
|
|
1066
|
+
const [focus, setFocus] = useState9("name");
|
|
1067
|
+
const [name, setName] = useState9("");
|
|
1068
|
+
const [serverUrl, setServerUrl] = useState9("");
|
|
1069
|
+
useEffect7(() => setCursor((current) => Math.min(current, Math.max(0, props.profiles.length - 1))), [props.profiles]);
|
|
860
1070
|
useInput7(
|
|
861
1071
|
(input, key) => {
|
|
862
1072
|
if (key.escape) {
|
|
@@ -887,11 +1097,11 @@ function ProfileScreen(props) {
|
|
|
887
1097
|
}
|
|
888
1098
|
if (name.trim() && serverUrl.trim()) props.onCreate(name.trim(), serverUrl.trim());
|
|
889
1099
|
};
|
|
890
|
-
return /* @__PURE__ */ jsxs8(
|
|
1100
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
891
1101
|
/* @__PURE__ */ jsx8(Header, {}),
|
|
892
|
-
/* @__PURE__ */ jsxs8(
|
|
1102
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", padding: 1, children: [
|
|
893
1103
|
/* @__PURE__ */ jsx8(Text8, { bold: true, children: creating ? "\uC0C8 \uD504\uB85C\uD544" : "\uD504\uB85C\uD544 \uC804\uD658" }),
|
|
894
|
-
creating ? /* @__PURE__ */ jsxs8(
|
|
1104
|
+
creating ? /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
|
|
895
1105
|
/* @__PURE__ */ jsx8(
|
|
896
1106
|
FormField,
|
|
897
1107
|
{
|
|
@@ -900,7 +1110,6 @@ function ProfileScreen(props) {
|
|
|
900
1110
|
onChange: setName,
|
|
901
1111
|
onSubmit: () => setFocus("url"),
|
|
902
1112
|
focus: !props.busy && focus === "name",
|
|
903
|
-
cursorOrigin: { x: 16, y: 5 },
|
|
904
1113
|
placeholder: "corp"
|
|
905
1114
|
}
|
|
906
1115
|
),
|
|
@@ -912,11 +1121,10 @@ function ProfileScreen(props) {
|
|
|
912
1121
|
onChange: setServerUrl,
|
|
913
1122
|
onSubmit: create,
|
|
914
1123
|
focus: !props.busy && focus === "url",
|
|
915
|
-
cursorOrigin: { x: 16, y: 6 },
|
|
916
1124
|
placeholder: "https://xgen.example.com"
|
|
917
1125
|
}
|
|
918
1126
|
)
|
|
919
|
-
] }) : /* @__PURE__ */ jsxs8(
|
|
1127
|
+
] }) : /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
|
|
920
1128
|
props.profiles.map((profile, index) => /* @__PURE__ */ jsxs8(Text8, { color: index === cursor ? "cyan" : void 0, children: [
|
|
921
1129
|
index === cursor ? "\u203A" : " ",
|
|
922
1130
|
" ",
|
|
@@ -942,11 +1150,11 @@ function ProfileScreen(props) {
|
|
|
942
1150
|
}
|
|
943
1151
|
|
|
944
1152
|
// src/tui/server-screen.tsx
|
|
945
|
-
import { useState as
|
|
946
|
-
import { Box as
|
|
1153
|
+
import { useState as useState10 } from "react";
|
|
1154
|
+
import { Box as Box9, Text as Text9, useInput as useInput8 } from "ink";
|
|
947
1155
|
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
948
1156
|
function ServerScreen(props) {
|
|
949
|
-
const [serverUrl, setServerUrl] =
|
|
1157
|
+
const [serverUrl, setServerUrl] = useState10(props.initialValue ?? "");
|
|
950
1158
|
const editing = props.initialValue !== void 0;
|
|
951
1159
|
useInput8(
|
|
952
1160
|
(_input, key) => {
|
|
@@ -954,12 +1162,12 @@ function ServerScreen(props) {
|
|
|
954
1162
|
},
|
|
955
1163
|
{ isActive: !props.busy && !!props.onCancel }
|
|
956
1164
|
);
|
|
957
|
-
return /* @__PURE__ */ jsxs9(
|
|
1165
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
958
1166
|
/* @__PURE__ */ jsx9(Header, { profile: props.profile, connected: false }),
|
|
959
|
-
/* @__PURE__ */ jsxs9(
|
|
1167
|
+
/* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", borderStyle: "round", borderColor: "blue", padding: 1, children: [
|
|
960
1168
|
/* @__PURE__ */ jsx9(Text9, { bold: true, children: editing ? "\uC11C\uBC84 \uC8FC\uC18C \uBC14\uAFB8\uAE30" : "\uCC98\uC74C \uC624\uC168\uAD70\uC694" }),
|
|
961
1169
|
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "\uC5F0\uACB0\uD560 XGEN Gateway \uC8FC\uC18C\uB97C \uC785\uB825\uD558\uC138\uC694." }),
|
|
962
|
-
/* @__PURE__ */ jsx9(
|
|
1170
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(
|
|
963
1171
|
FormField,
|
|
964
1172
|
{
|
|
965
1173
|
label: "Server URL",
|
|
@@ -967,7 +1175,6 @@ function ServerScreen(props) {
|
|
|
967
1175
|
onChange: setServerUrl,
|
|
968
1176
|
onSubmit: (value) => value.trim() && props.onSubmit(value.trim()),
|
|
969
1177
|
focus: !props.busy,
|
|
970
|
-
cursorOrigin: { x: 16, y: 6 },
|
|
971
1178
|
placeholder: "xgen.example.com"
|
|
972
1179
|
}
|
|
973
1180
|
) }),
|
|
@@ -988,13 +1195,13 @@ function ServerScreen(props) {
|
|
|
988
1195
|
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
989
1196
|
function App({ engine }) {
|
|
990
1197
|
const { exit } = useApp2();
|
|
991
|
-
const [route, setRoute] =
|
|
992
|
-
const [session, setSession] =
|
|
993
|
-
const [profiles, setProfiles] =
|
|
994
|
-
const [loginTarget, setLoginTarget] =
|
|
995
|
-
const [editingServer, setEditingServer] =
|
|
996
|
-
const [busy, setBusy] =
|
|
997
|
-
const [error, setError] =
|
|
1198
|
+
const [route, setRoute] = useState11("boot");
|
|
1199
|
+
const [session, setSession] = useState11();
|
|
1200
|
+
const [profiles, setProfiles] = useState11([]);
|
|
1201
|
+
const [loginTarget, setLoginTarget] = useState11();
|
|
1202
|
+
const [editingServer, setEditingServer] = useState11();
|
|
1203
|
+
const [busy, setBusy] = useState11(false);
|
|
1204
|
+
const [error, setError] = useState11();
|
|
998
1205
|
useInput9((input, key) => {
|
|
999
1206
|
if (key.ctrl && input === "q") exit();
|
|
1000
1207
|
});
|
|
@@ -1045,7 +1252,7 @@ function App({ engine }) {
|
|
|
1045
1252
|
},
|
|
1046
1253
|
[engine]
|
|
1047
1254
|
);
|
|
1048
|
-
|
|
1255
|
+
useEffect8(() => {
|
|
1049
1256
|
void bootstrap();
|
|
1050
1257
|
}, [bootstrap]);
|
|
1051
1258
|
const configure = async (serverUrl) => {
|
|
@@ -1120,7 +1327,7 @@ function App({ engine }) {
|
|
|
1120
1327
|
}
|
|
1121
1328
|
};
|
|
1122
1329
|
if (route === "boot") {
|
|
1123
|
-
return /* @__PURE__ */ jsxs10(
|
|
1330
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
|
|
1124
1331
|
/* @__PURE__ */ jsx10(Header, {}),
|
|
1125
1332
|
/* @__PURE__ */ jsx10(Loading, { label: "Dex\uB97C \uC900\uBE44\uD558\uB294 \uC911..." }),
|
|
1126
1333
|
/* @__PURE__ */ jsx10(Footer, { text: "Ctrl+Q \uC885\uB8CC" })
|
|
@@ -1185,7 +1392,7 @@ function App({ engine }) {
|
|
|
1185
1392
|
}
|
|
1186
1393
|
);
|
|
1187
1394
|
}
|
|
1188
|
-
return /* @__PURE__ */ jsxs10(
|
|
1395
|
+
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
|
|
1189
1396
|
/* @__PURE__ */ jsx10(Header, {}),
|
|
1190
1397
|
/* @__PURE__ */ jsx10(Notice, { error: true, children: error ?? "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4." }),
|
|
1191
1398
|
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "\uC11C\uBC84\uC640 \uD0A4\uCCB4\uC778 \uC0C1\uD0DC\uB97C \uD655\uC778\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694." }),
|
|
@@ -1205,6 +1412,20 @@ var ESC = "\x1B";
|
|
|
1205
1412
|
var ENTER_ALT_SCREEN = `${ESC}[?1049h`;
|
|
1206
1413
|
var LEAVE_ALT_SCREEN = `${ESC}[?1049l`;
|
|
1207
1414
|
var SHOW_CURSOR = `${ESC}[?25h`;
|
|
1415
|
+
var DISABLE_REPORTS = [
|
|
1416
|
+
`${ESC}[?1004l`,
|
|
1417
|
+
// 포커스 들어옴/나감
|
|
1418
|
+
`${ESC}[?1000l`,
|
|
1419
|
+
// 마우스 클릭
|
|
1420
|
+
`${ESC}[?1002l`,
|
|
1421
|
+
// 마우스 드래그
|
|
1422
|
+
`${ESC}[?1003l`,
|
|
1423
|
+
// 마우스 이동 전부
|
|
1424
|
+
`${ESC}[?1006l`
|
|
1425
|
+
// SGR 확장 좌표
|
|
1426
|
+
].join("");
|
|
1427
|
+
var ENABLE_BRACKETED_PASTE = `${ESC}[?2004h`;
|
|
1428
|
+
var DISABLE_BRACKETED_PASTE = `${ESC}[?2004l`;
|
|
1208
1429
|
function createScreenGuard(stream) {
|
|
1209
1430
|
const alt = Boolean(stream.isTTY);
|
|
1210
1431
|
let entered = false;
|
|
@@ -1213,12 +1434,12 @@ function createScreenGuard(stream) {
|
|
|
1213
1434
|
enter() {
|
|
1214
1435
|
if (entered || !alt) return;
|
|
1215
1436
|
entered = true;
|
|
1216
|
-
stream.write(ENTER_ALT_SCREEN);
|
|
1437
|
+
stream.write(ENTER_ALT_SCREEN + DISABLE_REPORTS + ENABLE_BRACKETED_PASTE);
|
|
1217
1438
|
},
|
|
1218
1439
|
restore() {
|
|
1219
1440
|
if (restored) return;
|
|
1220
1441
|
restored = true;
|
|
1221
|
-
if (entered) stream.write(LEAVE_ALT_SCREEN);
|
|
1442
|
+
if (entered) stream.write(DISABLE_BRACKETED_PASTE + LEAVE_ALT_SCREEN);
|
|
1222
1443
|
stream.write(SHOW_CURSOR);
|
|
1223
1444
|
}
|
|
1224
1445
|
};
|
|
@@ -1253,4 +1474,4 @@ async function runTui(engine) {
|
|
|
1253
1474
|
export {
|
|
1254
1475
|
runTui
|
|
1255
1476
|
};
|
|
1256
|
-
//# sourceMappingURL=tui-
|
|
1477
|
+
//# sourceMappingURL=tui-KJAW7DSU.js.map
|