@satori-sh/cli 0.0.15 → 0.0.17
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/dist/index.js +40 -256
- package/dist/ui-AFMM6JTQ.js +252 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { readFileSync, realpathSync } from "fs";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { homedir as homedir2 } from "os";
|
|
7
|
+
import { execFile, spawn, spawnSync } from "child_process";
|
|
8
|
+
import { dirname, join as join2 } from "path";
|
|
9
|
+
import { fileURLToPath } from "url";
|
|
9
10
|
import chalk from "chalk";
|
|
10
11
|
|
|
11
12
|
// src/config.ts
|
|
@@ -227,258 +228,40 @@ ${memoryText}`
|
|
|
227
228
|
return [systemMessage, ...messages];
|
|
228
229
|
}
|
|
229
230
|
|
|
230
|
-
// src/ui.tsx
|
|
231
|
-
import { render, useRenderer, useTerminalDimensions } from "@opentui/solid";
|
|
232
|
-
import { For, Show, createSignal, onMount, onCleanup } from "solid-js";
|
|
233
|
-
import cliSpinners from "cli-spinners";
|
|
234
|
-
|
|
235
|
-
// src/logo.ts
|
|
236
|
-
import { dirname, join as join2 } from "path";
|
|
237
|
-
import { fileURLToPath } from "url";
|
|
238
|
-
async function loadLogo() {
|
|
239
|
-
const { default: fs } = await import("fs");
|
|
240
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
241
|
-
const logoPath = join2(__dirname, "..", "logos", "satori.ans");
|
|
242
|
-
return fs.readFileSync(logoPath, "utf8");
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// src/ui.tsx
|
|
246
|
-
async function runInteractiveApp({
|
|
247
|
-
initialPrompt,
|
|
248
|
-
options,
|
|
249
|
-
processUserInput,
|
|
250
|
-
infoLine,
|
|
251
|
-
infoDisplay
|
|
252
|
-
}) {
|
|
253
|
-
const logo = await loadLogo();
|
|
254
|
-
console.log(` ${logo}`);
|
|
255
|
-
const rows = process.stdout.rows ?? 24;
|
|
256
|
-
const logoHeight = logo.endsWith("\n") ? logo.slice(0, -1).split("\n").length : logo.split("\n").length;
|
|
257
|
-
const splitHeight = Math.max(1, rows - logoHeight - 1);
|
|
258
|
-
render(
|
|
259
|
-
() => /* @__PURE__ */ React.createElement(
|
|
260
|
-
App,
|
|
261
|
-
{
|
|
262
|
-
initialPrompt,
|
|
263
|
-
options,
|
|
264
|
-
processUserInput,
|
|
265
|
-
infoLine,
|
|
266
|
-
infoDisplay
|
|
267
|
-
}
|
|
268
|
-
),
|
|
269
|
-
{
|
|
270
|
-
useAlternateScreen: false,
|
|
271
|
-
exitOnCtrlC: true,
|
|
272
|
-
useMouse: true,
|
|
273
|
-
enableMouseMovement: true,
|
|
274
|
-
experimental_splitHeight: splitHeight
|
|
275
|
-
}
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
function App({ initialPrompt, options, processUserInput, infoLine, infoDisplay }) {
|
|
279
|
-
const renderer = useRenderer();
|
|
280
|
-
const dimensions = useTerminalDimensions();
|
|
281
|
-
const [messages, setMessages] = createSignal([]);
|
|
282
|
-
const [inputValue, setInputValue] = createSignal("");
|
|
283
|
-
const [showIntro, setShowIntro] = createSignal(true);
|
|
284
|
-
const [isFullScreen, setIsFullScreen] = createSignal(false);
|
|
285
|
-
const [spinnerFrame, setSpinnerFrame] = createSignal(0);
|
|
286
|
-
const [isLoading, setIsLoading] = createSignal(false);
|
|
287
|
-
const promptFg = "#00ffff";
|
|
288
|
-
const responseFg = "#ffffff";
|
|
289
|
-
const promptBg = "#2b2b2b";
|
|
290
|
-
let inputRef;
|
|
291
|
-
let currentMemoryId = options.memoryId;
|
|
292
|
-
let messageId = 0;
|
|
293
|
-
const usageText = infoDisplay?.usageLine ?? infoLine ?? "";
|
|
294
|
-
const versionText = infoDisplay?.versionLine ?? "";
|
|
295
|
-
const modelText = infoDisplay?.modelLine ?? "";
|
|
296
|
-
const appendMessage = (role, text) => {
|
|
297
|
-
setMessages((prev) => [...prev, { id: messageId++, role, text }]);
|
|
298
|
-
};
|
|
299
|
-
const exitApp = () => {
|
|
300
|
-
renderer.destroy();
|
|
301
|
-
process.exit(0);
|
|
302
|
-
};
|
|
303
|
-
const submitPrompt = async (raw) => {
|
|
304
|
-
const trimmed = raw.trim();
|
|
305
|
-
if (!trimmed) return;
|
|
306
|
-
if (trimmed.toLowerCase() === "exit" || trimmed.toLowerCase() === "quit") {
|
|
307
|
-
exitApp();
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
if (showIntro()) {
|
|
311
|
-
setShowIntro(false);
|
|
312
|
-
}
|
|
313
|
-
if (!isFullScreen()) {
|
|
314
|
-
setIsFullScreen(true);
|
|
315
|
-
}
|
|
316
|
-
setInputValue("");
|
|
317
|
-
if (inputRef) {
|
|
318
|
-
inputRef.value = "";
|
|
319
|
-
}
|
|
320
|
-
appendMessage("prompt", trimmed);
|
|
321
|
-
try {
|
|
322
|
-
setIsLoading(true);
|
|
323
|
-
const result = await processUserInput(trimmed, { ...options, memoryId: currentMemoryId }, "tui");
|
|
324
|
-
currentMemoryId = result.memoryId;
|
|
325
|
-
appendMessage("response", result.response);
|
|
326
|
-
if (result.instruction) {
|
|
327
|
-
appendMessage("response", result.instruction);
|
|
328
|
-
}
|
|
329
|
-
} catch (error) {
|
|
330
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
331
|
-
appendMessage("response", `Error: ${message}`);
|
|
332
|
-
} finally {
|
|
333
|
-
setIsLoading(false);
|
|
334
|
-
}
|
|
335
|
-
};
|
|
336
|
-
onMount(async () => {
|
|
337
|
-
const spinner = cliSpinners.dots;
|
|
338
|
-
const timer = setInterval(() => {
|
|
339
|
-
if (isLoading()) {
|
|
340
|
-
setSpinnerFrame((prev) => (prev + 1) % spinner.frames.length);
|
|
341
|
-
}
|
|
342
|
-
}, spinner.interval);
|
|
343
|
-
onCleanup(() => clearInterval(timer));
|
|
344
|
-
if (initialPrompt) {
|
|
345
|
-
await submitPrompt(initialPrompt);
|
|
346
|
-
}
|
|
347
|
-
if (inputRef) {
|
|
348
|
-
inputRef.focus();
|
|
349
|
-
}
|
|
350
|
-
});
|
|
351
|
-
const inputBoxWidth = () => Math.max(1, Math.round(dimensions().width * 0.6));
|
|
352
|
-
const inputBoxLeft = () => Math.max(0, Math.round(dimensions().width * 0.15));
|
|
353
|
-
const inputBoxHeight = () => isFullScreen() ? 7 : 14;
|
|
354
|
-
const inputBoxTop = () => isFullScreen() ? Math.max(1, dimensions().height - inputBoxHeight() - 2) : Math.max(1, Math.round(dimensions().height * 0.666));
|
|
355
|
-
const messagesTop = () => 1;
|
|
356
|
-
const messagesHeight = () => Math.max(1, inputBoxTop() - messagesTop() - 1);
|
|
357
|
-
const messagesWidth = () => Math.min(dimensions().width - 2, inputBoxWidth() + 10);
|
|
358
|
-
const messagesLeft = () => Math.max(1, inputBoxLeft() - 5);
|
|
359
|
-
return /* @__PURE__ */ React.createElement("box", { width: "100%", height: "100%", flexDirection: "column" }, /* @__PURE__ */ React.createElement(
|
|
360
|
-
"scrollbox",
|
|
361
|
-
{
|
|
362
|
-
id: "messages",
|
|
363
|
-
width: messagesWidth(),
|
|
364
|
-
height: messagesHeight(),
|
|
365
|
-
position: "absolute",
|
|
366
|
-
left: messagesLeft(),
|
|
367
|
-
top: messagesTop(),
|
|
368
|
-
paddingLeft: 1,
|
|
369
|
-
paddingRight: 1,
|
|
370
|
-
focused: true,
|
|
371
|
-
stickyScroll: true,
|
|
372
|
-
stickyStart: "bottom"
|
|
373
|
-
},
|
|
374
|
-
/* @__PURE__ */ React.createElement("box", { width: "100%", flexDirection: "column" }, /* @__PURE__ */ React.createElement(For, { each: messages() }, (message) => /* @__PURE__ */ React.createElement(
|
|
375
|
-
"box",
|
|
376
|
-
{
|
|
377
|
-
width: "100%",
|
|
378
|
-
flexDirection: "row",
|
|
379
|
-
justifyContent: message.role === "prompt" ? "flex-start" : "flex-end",
|
|
380
|
-
marginBottom: 1
|
|
381
|
-
},
|
|
382
|
-
/* @__PURE__ */ React.createElement(
|
|
383
|
-
"box",
|
|
384
|
-
{
|
|
385
|
-
paddingLeft: 1,
|
|
386
|
-
paddingRight: 1,
|
|
387
|
-
paddingTop: 1,
|
|
388
|
-
paddingBottom: 1,
|
|
389
|
-
backgroundColor: message.role === "prompt" ? promptBg : void 0
|
|
390
|
-
},
|
|
391
|
-
/* @__PURE__ */ React.createElement(
|
|
392
|
-
"text",
|
|
393
|
-
{
|
|
394
|
-
fg: message.role === "prompt" ? promptFg : responseFg,
|
|
395
|
-
width: "100%",
|
|
396
|
-
wrapMode: "word",
|
|
397
|
-
selectable: false
|
|
398
|
-
},
|
|
399
|
-
message.text
|
|
400
|
-
)
|
|
401
|
-
)
|
|
402
|
-
)))
|
|
403
|
-
), /* @__PURE__ */ React.createElement(
|
|
404
|
-
"box",
|
|
405
|
-
{
|
|
406
|
-
id: "input-box",
|
|
407
|
-
width: inputBoxWidth(),
|
|
408
|
-
height: inputBoxHeight(),
|
|
409
|
-
position: "absolute",
|
|
410
|
-
left: inputBoxLeft(),
|
|
411
|
-
top: inputBoxTop(),
|
|
412
|
-
paddingLeft: 1,
|
|
413
|
-
paddingRight: 1,
|
|
414
|
-
paddingTop: 1,
|
|
415
|
-
flexDirection: "column"
|
|
416
|
-
},
|
|
417
|
-
/* @__PURE__ */ React.createElement(For, { each: !isFullScreen() && showIntro() ? [
|
|
418
|
-
"Use Satori just like you would use ChatGPT.",
|
|
419
|
-
"Except, it stores your conversations in a long term memory.",
|
|
420
|
-
"The memories you store here can be accessed through the SDK."
|
|
421
|
-
] : [] }, (line) => /* @__PURE__ */ React.createElement("text", { fg: "cyan" }, line)),
|
|
422
|
-
/* @__PURE__ */ React.createElement(
|
|
423
|
-
"box",
|
|
424
|
-
{
|
|
425
|
-
id: "input-box",
|
|
426
|
-
width: inputBoxWidth(),
|
|
427
|
-
height: 5,
|
|
428
|
-
backgroundColor: "#1a1a1a",
|
|
429
|
-
flexDirection: "column",
|
|
430
|
-
justifyContent: "center"
|
|
431
|
-
},
|
|
432
|
-
/* @__PURE__ */ React.createElement(
|
|
433
|
-
"input",
|
|
434
|
-
{
|
|
435
|
-
id: "input",
|
|
436
|
-
width: "100%",
|
|
437
|
-
height: 1,
|
|
438
|
-
placeholder: "Type a message and press Enter...",
|
|
439
|
-
focusedBackgroundColor: "#1a1a1a",
|
|
440
|
-
onInput: (value) => setInputValue(value),
|
|
441
|
-
onSubmit: () => submitPrompt(inputValue()),
|
|
442
|
-
ref: (r) => {
|
|
443
|
-
inputRef = r;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
),
|
|
447
|
-
/* @__PURE__ */ React.createElement("box", { flexDirection: "row", flexShrink: 0, paddingTop: 1 }, /* @__PURE__ */ React.createElement("text", { fg: "#ffffff" }, modelText))
|
|
448
|
-
)
|
|
449
|
-
), /* @__PURE__ */ React.createElement(Show, { when: isLoading() }, /* @__PURE__ */ React.createElement(
|
|
450
|
-
"box",
|
|
451
|
-
{
|
|
452
|
-
id: "spinner",
|
|
453
|
-
position: "absolute",
|
|
454
|
-
left: inputBoxLeft(),
|
|
455
|
-
top: inputBoxTop() + inputBoxHeight(),
|
|
456
|
-
paddingLeft: 1
|
|
457
|
-
},
|
|
458
|
-
/* @__PURE__ */ React.createElement("text", { fg: "#00ffff" }, cliSpinners.dots.frames[spinnerFrame()])
|
|
459
|
-
)), /* @__PURE__ */ React.createElement(
|
|
460
|
-
"box",
|
|
461
|
-
{
|
|
462
|
-
id: "footer",
|
|
463
|
-
width: dimensions().width,
|
|
464
|
-
height: 1,
|
|
465
|
-
position: "absolute",
|
|
466
|
-
bottom: 0,
|
|
467
|
-
left: 0,
|
|
468
|
-
backgroundColor: "#000000",
|
|
469
|
-
paddingLeft: 1,
|
|
470
|
-
paddingRight: 1,
|
|
471
|
-
flexDirection: "row",
|
|
472
|
-
justifyContent: "space-between",
|
|
473
|
-
alignItems: "center"
|
|
474
|
-
},
|
|
475
|
-
/* @__PURE__ */ React.createElement("text", { fg: "#00ffff", wrapMode: "none", width: "100%" }, usageText),
|
|
476
|
-
/* @__PURE__ */ React.createElement("box", { flexShrink: 0, paddingLeft: 1 }, /* @__PURE__ */ React.createElement("text", { fg: "#00ffff" }, versionText))
|
|
477
|
-
));
|
|
478
|
-
}
|
|
479
|
-
|
|
480
231
|
// src/index.ts
|
|
481
232
|
async function main() {
|
|
233
|
+
const modulePath2 = realpathSync(fileURLToPath(import.meta.url));
|
|
234
|
+
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
235
|
+
if (isInteractive && !globalThis.Bun && !process.env.SATORI_BUN_REEXEC) {
|
|
236
|
+
const bunBinDir = join2(homedir2(), ".bun", "bin");
|
|
237
|
+
const bunPath = join2(bunBinDir, "bun");
|
|
238
|
+
const baseEnv = { ...process.env, PATH: `${bunBinDir}:${process.env.PATH ?? ""}` };
|
|
239
|
+
const bunCheck = spawnSync("bun", ["--version"], { stdio: "ignore", env: baseEnv });
|
|
240
|
+
if (bunCheck.status !== 0) {
|
|
241
|
+
const installResult = spawnSync(
|
|
242
|
+
"bash",
|
|
243
|
+
["-lc", "curl -fsSL https://bun.sh/install | bash"],
|
|
244
|
+
{ stdio: "inherit" }
|
|
245
|
+
);
|
|
246
|
+
if (installResult.status !== 0) {
|
|
247
|
+
console.error("Failed to install Bun. Please install manually and retry.");
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
const bunProcess = spawn(
|
|
252
|
+
bunPath,
|
|
253
|
+
[modulePath2, ...process.argv.slice(2)],
|
|
254
|
+
{ env: { ...baseEnv, SATORI_BUN_REEXEC: "1" }, stdio: "inherit" }
|
|
255
|
+
);
|
|
256
|
+
bunProcess.on("error", (error) => {
|
|
257
|
+
console.error("Failed to launch Bun:", error.message);
|
|
258
|
+
process.exit(1);
|
|
259
|
+
});
|
|
260
|
+
bunProcess.on("exit", (code) => {
|
|
261
|
+
process.exit(code ?? 0);
|
|
262
|
+
});
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
482
265
|
const argv = process.argv.slice(2);
|
|
483
266
|
const isGetApiKey = argv[0] === "get" && argv[1] === "api-key";
|
|
484
267
|
if (!isGetApiKey) {
|
|
@@ -494,8 +277,8 @@ async function main() {
|
|
|
494
277
|
program.option("--memory-id <id>", "Memory ID for scoping");
|
|
495
278
|
const DEFAULT_LLM_MODEL = "gpt-4o";
|
|
496
279
|
const getCliVersion = () => {
|
|
497
|
-
const __dirname =
|
|
498
|
-
const packagePath =
|
|
280
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
281
|
+
const packagePath = join2(__dirname, "..", "package.json");
|
|
499
282
|
const raw = readFileSync(packagePath, "utf8");
|
|
500
283
|
const data = JSON.parse(raw);
|
|
501
284
|
return data.version ?? "unknown";
|
|
@@ -583,6 +366,7 @@ ${instruction}`);
|
|
|
583
366
|
return;
|
|
584
367
|
}
|
|
585
368
|
const info = await getInfoDisplay();
|
|
369
|
+
const { runInteractiveApp } = await import("./ui-AFMM6JTQ.js");
|
|
586
370
|
await runInteractiveApp({
|
|
587
371
|
initialPrompt,
|
|
588
372
|
options,
|
|
@@ -649,7 +433,7 @@ ${instruction}`);
|
|
|
649
433
|
program.parse();
|
|
650
434
|
}
|
|
651
435
|
var entryPath = process.argv[1] ? realpathSync(process.argv[1]) : "";
|
|
652
|
-
var modulePath = realpathSync(
|
|
436
|
+
var modulePath = realpathSync(fileURLToPath(import.meta.url));
|
|
653
437
|
if (entryPath === modulePath) {
|
|
654
438
|
main();
|
|
655
439
|
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// src/ui.tsx
|
|
2
|
+
import { render, useRenderer, useTerminalDimensions } from "@opentui/solid";
|
|
3
|
+
import { For, Show, createSignal, onMount, onCleanup } from "solid-js";
|
|
4
|
+
import cliSpinners from "cli-spinners";
|
|
5
|
+
|
|
6
|
+
// src/logo.ts
|
|
7
|
+
import { dirname, join } from "path";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
9
|
+
async function loadLogo() {
|
|
10
|
+
const { default: fs } = await import("fs");
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const logoPath = join(__dirname, "..", "logos", "satori.ans");
|
|
13
|
+
return fs.readFileSync(logoPath, "utf8");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/ui.tsx
|
|
17
|
+
async function runInteractiveApp({
|
|
18
|
+
initialPrompt,
|
|
19
|
+
options,
|
|
20
|
+
processUserInput,
|
|
21
|
+
infoLine,
|
|
22
|
+
infoDisplay
|
|
23
|
+
}) {
|
|
24
|
+
const logo = await loadLogo();
|
|
25
|
+
console.log(` ${logo}`);
|
|
26
|
+
const rows = process.stdout.rows ?? 24;
|
|
27
|
+
const logoHeight = logo.endsWith("\n") ? logo.slice(0, -1).split("\n").length : logo.split("\n").length;
|
|
28
|
+
const splitHeight = Math.max(1, rows - logoHeight - 1);
|
|
29
|
+
render(
|
|
30
|
+
() => /* @__PURE__ */ React.createElement(
|
|
31
|
+
App,
|
|
32
|
+
{
|
|
33
|
+
initialPrompt,
|
|
34
|
+
options,
|
|
35
|
+
processUserInput,
|
|
36
|
+
infoLine,
|
|
37
|
+
infoDisplay
|
|
38
|
+
}
|
|
39
|
+
),
|
|
40
|
+
{
|
|
41
|
+
useAlternateScreen: false,
|
|
42
|
+
exitOnCtrlC: true,
|
|
43
|
+
useMouse: true,
|
|
44
|
+
enableMouseMovement: true,
|
|
45
|
+
experimental_splitHeight: splitHeight
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
function App({ initialPrompt, options, processUserInput, infoLine, infoDisplay }) {
|
|
50
|
+
const renderer = useRenderer();
|
|
51
|
+
const dimensions = useTerminalDimensions();
|
|
52
|
+
const [messages, setMessages] = createSignal([]);
|
|
53
|
+
const [inputValue, setInputValue] = createSignal("");
|
|
54
|
+
const [showIntro, setShowIntro] = createSignal(true);
|
|
55
|
+
const [isFullScreen, setIsFullScreen] = createSignal(false);
|
|
56
|
+
const [spinnerFrame, setSpinnerFrame] = createSignal(0);
|
|
57
|
+
const [isLoading, setIsLoading] = createSignal(false);
|
|
58
|
+
const promptFg = "#00ffff";
|
|
59
|
+
const responseFg = "#ffffff";
|
|
60
|
+
const promptBg = "#2b2b2b";
|
|
61
|
+
let inputRef;
|
|
62
|
+
let currentMemoryId = options.memoryId;
|
|
63
|
+
let messageId = 0;
|
|
64
|
+
const usageText = infoDisplay?.usageLine ?? infoLine ?? "";
|
|
65
|
+
const versionText = infoDisplay?.versionLine ?? "";
|
|
66
|
+
const modelText = infoDisplay?.modelLine ?? "";
|
|
67
|
+
const appendMessage = (role, text) => {
|
|
68
|
+
setMessages((prev) => [...prev, { id: messageId++, role, text }]);
|
|
69
|
+
};
|
|
70
|
+
const exitApp = () => {
|
|
71
|
+
renderer.destroy();
|
|
72
|
+
process.exit(0);
|
|
73
|
+
};
|
|
74
|
+
const submitPrompt = async (raw) => {
|
|
75
|
+
const trimmed = raw.trim();
|
|
76
|
+
if (!trimmed) return;
|
|
77
|
+
if (trimmed.toLowerCase() === "exit" || trimmed.toLowerCase() === "quit") {
|
|
78
|
+
exitApp();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (showIntro()) {
|
|
82
|
+
setShowIntro(false);
|
|
83
|
+
}
|
|
84
|
+
if (!isFullScreen()) {
|
|
85
|
+
setIsFullScreen(true);
|
|
86
|
+
}
|
|
87
|
+
setInputValue("");
|
|
88
|
+
if (inputRef) {
|
|
89
|
+
inputRef.value = "";
|
|
90
|
+
}
|
|
91
|
+
appendMessage("prompt", trimmed);
|
|
92
|
+
try {
|
|
93
|
+
setIsLoading(true);
|
|
94
|
+
const result = await processUserInput(trimmed, { ...options, memoryId: currentMemoryId }, "tui");
|
|
95
|
+
currentMemoryId = result.memoryId;
|
|
96
|
+
appendMessage("response", result.response);
|
|
97
|
+
if (result.instruction) {
|
|
98
|
+
appendMessage("response", result.instruction);
|
|
99
|
+
}
|
|
100
|
+
} catch (error) {
|
|
101
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
102
|
+
appendMessage("response", `Error: ${message}`);
|
|
103
|
+
} finally {
|
|
104
|
+
setIsLoading(false);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
onMount(async () => {
|
|
108
|
+
const spinner = cliSpinners.dots;
|
|
109
|
+
const timer = setInterval(() => {
|
|
110
|
+
if (isLoading()) {
|
|
111
|
+
setSpinnerFrame((prev) => (prev + 1) % spinner.frames.length);
|
|
112
|
+
}
|
|
113
|
+
}, spinner.interval);
|
|
114
|
+
onCleanup(() => clearInterval(timer));
|
|
115
|
+
if (initialPrompt) {
|
|
116
|
+
await submitPrompt(initialPrompt);
|
|
117
|
+
}
|
|
118
|
+
if (inputRef) {
|
|
119
|
+
inputRef.focus();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
const inputBoxWidth = () => Math.max(1, Math.round(dimensions().width * 0.6));
|
|
123
|
+
const inputBoxLeft = () => Math.max(0, Math.round(dimensions().width * 0.15));
|
|
124
|
+
const inputBoxHeight = () => isFullScreen() ? 7 : 14;
|
|
125
|
+
const inputBoxTop = () => isFullScreen() ? Math.max(1, dimensions().height - inputBoxHeight() - 2) : Math.max(1, Math.round(dimensions().height * 0.666));
|
|
126
|
+
const messagesTop = () => 1;
|
|
127
|
+
const messagesHeight = () => Math.max(1, inputBoxTop() - messagesTop() - 1);
|
|
128
|
+
const messagesWidth = () => Math.min(dimensions().width - 2, inputBoxWidth() + 10);
|
|
129
|
+
const messagesLeft = () => Math.max(1, inputBoxLeft() - 5);
|
|
130
|
+
return /* @__PURE__ */ React.createElement("box", { width: "100%", height: "100%", flexDirection: "column" }, /* @__PURE__ */ React.createElement(
|
|
131
|
+
"scrollbox",
|
|
132
|
+
{
|
|
133
|
+
id: "messages",
|
|
134
|
+
width: messagesWidth(),
|
|
135
|
+
height: messagesHeight(),
|
|
136
|
+
position: "absolute",
|
|
137
|
+
left: messagesLeft(),
|
|
138
|
+
top: messagesTop(),
|
|
139
|
+
paddingLeft: 1,
|
|
140
|
+
paddingRight: 1,
|
|
141
|
+
focused: true,
|
|
142
|
+
stickyScroll: true,
|
|
143
|
+
stickyStart: "bottom"
|
|
144
|
+
},
|
|
145
|
+
/* @__PURE__ */ React.createElement("box", { width: "100%", flexDirection: "column" }, /* @__PURE__ */ React.createElement(For, { each: messages() }, (message) => /* @__PURE__ */ React.createElement(
|
|
146
|
+
"box",
|
|
147
|
+
{
|
|
148
|
+
width: "100%",
|
|
149
|
+
flexDirection: "row",
|
|
150
|
+
justifyContent: message.role === "prompt" ? "flex-start" : "flex-end",
|
|
151
|
+
marginBottom: 1
|
|
152
|
+
},
|
|
153
|
+
/* @__PURE__ */ React.createElement(
|
|
154
|
+
"box",
|
|
155
|
+
{
|
|
156
|
+
paddingLeft: 1,
|
|
157
|
+
paddingRight: 1,
|
|
158
|
+
paddingTop: 1,
|
|
159
|
+
paddingBottom: 1,
|
|
160
|
+
backgroundColor: message.role === "prompt" ? promptBg : void 0
|
|
161
|
+
},
|
|
162
|
+
/* @__PURE__ */ React.createElement(
|
|
163
|
+
"text",
|
|
164
|
+
{
|
|
165
|
+
fg: message.role === "prompt" ? promptFg : responseFg,
|
|
166
|
+
width: "100%",
|
|
167
|
+
wrapMode: "word",
|
|
168
|
+
selectable: false
|
|
169
|
+
},
|
|
170
|
+
message.text
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
)))
|
|
174
|
+
), /* @__PURE__ */ React.createElement(
|
|
175
|
+
"box",
|
|
176
|
+
{
|
|
177
|
+
id: "input-box",
|
|
178
|
+
width: inputBoxWidth(),
|
|
179
|
+
height: inputBoxHeight(),
|
|
180
|
+
position: "absolute",
|
|
181
|
+
left: inputBoxLeft(),
|
|
182
|
+
top: inputBoxTop(),
|
|
183
|
+
paddingLeft: 1,
|
|
184
|
+
paddingRight: 1,
|
|
185
|
+
paddingTop: 1,
|
|
186
|
+
flexDirection: "column"
|
|
187
|
+
},
|
|
188
|
+
/* @__PURE__ */ React.createElement(For, { each: !isFullScreen() && showIntro() ? [
|
|
189
|
+
"Use Satori just like you would use ChatGPT.",
|
|
190
|
+
"Except, it stores your conversations in a long term memory.",
|
|
191
|
+
"The memories you store here can be accessed through the SDK."
|
|
192
|
+
] : [] }, (line) => /* @__PURE__ */ React.createElement("text", { fg: "cyan" }, line)),
|
|
193
|
+
/* @__PURE__ */ React.createElement(
|
|
194
|
+
"box",
|
|
195
|
+
{
|
|
196
|
+
id: "input-box",
|
|
197
|
+
width: inputBoxWidth(),
|
|
198
|
+
height: 5,
|
|
199
|
+
backgroundColor: "#1a1a1a",
|
|
200
|
+
flexDirection: "column",
|
|
201
|
+
justifyContent: "center"
|
|
202
|
+
},
|
|
203
|
+
/* @__PURE__ */ React.createElement(
|
|
204
|
+
"input",
|
|
205
|
+
{
|
|
206
|
+
id: "input",
|
|
207
|
+
width: "100%",
|
|
208
|
+
height: 1,
|
|
209
|
+
placeholder: "Type a message and press Enter...",
|
|
210
|
+
focusedBackgroundColor: "#1a1a1a",
|
|
211
|
+
onInput: (value) => setInputValue(value),
|
|
212
|
+
onSubmit: () => submitPrompt(inputValue()),
|
|
213
|
+
ref: (r) => {
|
|
214
|
+
inputRef = r;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
),
|
|
218
|
+
/* @__PURE__ */ React.createElement("box", { flexDirection: "row", flexShrink: 0, paddingTop: 1 }, /* @__PURE__ */ React.createElement("text", { fg: "#ffffff" }, modelText))
|
|
219
|
+
)
|
|
220
|
+
), /* @__PURE__ */ React.createElement(Show, { when: isLoading() }, /* @__PURE__ */ React.createElement(
|
|
221
|
+
"box",
|
|
222
|
+
{
|
|
223
|
+
id: "spinner",
|
|
224
|
+
position: "absolute",
|
|
225
|
+
left: inputBoxLeft(),
|
|
226
|
+
top: inputBoxTop() + inputBoxHeight(),
|
|
227
|
+
paddingLeft: 1
|
|
228
|
+
},
|
|
229
|
+
/* @__PURE__ */ React.createElement("text", { fg: "#00ffff" }, cliSpinners.dots.frames[spinnerFrame()])
|
|
230
|
+
)), /* @__PURE__ */ React.createElement(
|
|
231
|
+
"box",
|
|
232
|
+
{
|
|
233
|
+
id: "footer",
|
|
234
|
+
width: dimensions().width,
|
|
235
|
+
height: 1,
|
|
236
|
+
position: "absolute",
|
|
237
|
+
bottom: 0,
|
|
238
|
+
left: 0,
|
|
239
|
+
backgroundColor: "#000000",
|
|
240
|
+
paddingLeft: 1,
|
|
241
|
+
paddingRight: 1,
|
|
242
|
+
flexDirection: "row",
|
|
243
|
+
justifyContent: "space-between",
|
|
244
|
+
alignItems: "center"
|
|
245
|
+
},
|
|
246
|
+
/* @__PURE__ */ React.createElement("text", { fg: "#00ffff", wrapMode: "none", width: "100%" }, usageText),
|
|
247
|
+
/* @__PURE__ */ React.createElement("box", { flexShrink: 0, paddingLeft: 1 }, /* @__PURE__ */ React.createElement("text", { fg: "#00ffff" }, versionText))
|
|
248
|
+
));
|
|
249
|
+
}
|
|
250
|
+
export {
|
|
251
|
+
runInteractiveApp
|
|
252
|
+
};
|