opencode-summary 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +157 -2
- package/dist/tui.js +953 -0
- package/package.json +60 -4
- package/src/lib.ts +135 -0
- package/src/prompt.ts +12 -0
- package/src/tui.tsx +558 -0
package/dist/tui.js
ADDED
|
@@ -0,0 +1,953 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/tui.tsx
|
|
3
|
+
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
|
4
|
+
import { insertNode as _$insertNode } from "@opentui/solid";
|
|
5
|
+
import { use as _$use } from "@opentui/solid";
|
|
6
|
+
import { memo as _$memo } from "@opentui/solid";
|
|
7
|
+
import { createComponent as _$createComponent } from "@opentui/solid";
|
|
8
|
+
import { setProp as _$setProp } from "@opentui/solid";
|
|
9
|
+
import { effect as _$effect } from "@opentui/solid";
|
|
10
|
+
import { insert as _$insert } from "@opentui/solid";
|
|
11
|
+
import { createElement as _$createElement } from "@opentui/solid";
|
|
12
|
+
import { Plugin } from "@opencode/plugin/tui";
|
|
13
|
+
import { SyntaxStyle, TextAttributes } from "@opentui/core";
|
|
14
|
+
import { useKeyboard, useTerminalDimensions } from "@opentui/solid";
|
|
15
|
+
import { createMemo, createResource, createSignal, For, onCleanup, onMount, Show } from "solid-js";
|
|
16
|
+
import { spawn } from "child_process";
|
|
17
|
+
import { mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
18
|
+
import { homedir } from "os";
|
|
19
|
+
import path2 from "path";
|
|
20
|
+
|
|
21
|
+
// src/lib.ts
|
|
22
|
+
import path from "path";
|
|
23
|
+
|
|
24
|
+
// src/prompt.ts
|
|
25
|
+
var GUARD = "Do not call any tools and do not take any actions.";
|
|
26
|
+
var DEFAULT_PROMPT = `Summarize the conversation so far for the user.
|
|
27
|
+
Output markdown only, no preamble, about 300 words or less. Use exactly these sections, bullets only below TL;DR; omit a section if empty:
|
|
28
|
+
|
|
29
|
+
## TL;DR
|
|
30
|
+
2-3 sentences.
|
|
31
|
+
## Goal
|
|
32
|
+
## Decisions
|
|
33
|
+
## Progress / current state
|
|
34
|
+
## Open questions / next steps
|
|
35
|
+
## Key files and references`;
|
|
36
|
+
|
|
37
|
+
// src/lib.ts
|
|
38
|
+
var METADATA_KEY = "opencode-summary";
|
|
39
|
+
var buildRequest = (prompt) => `${GUARD}
|
|
40
|
+
|
|
41
|
+
${prompt}`;
|
|
42
|
+
function promptId(text) {
|
|
43
|
+
let hash = 2166136261;
|
|
44
|
+
for (let i = 0;i < text.length; i++)
|
|
45
|
+
hash = Math.imul(hash ^ text.charCodeAt(i), 16777619) >>> 0;
|
|
46
|
+
return hash.toString(16).padStart(8, "0");
|
|
47
|
+
}
|
|
48
|
+
function readSummary(metadata) {
|
|
49
|
+
if (!metadata || typeof metadata !== "object")
|
|
50
|
+
return;
|
|
51
|
+
const value = metadata[METADATA_KEY];
|
|
52
|
+
if (!value || typeof value !== "object")
|
|
53
|
+
return;
|
|
54
|
+
const { text, generatedAt, promptId: promptId2, lastMessageID } = value;
|
|
55
|
+
if (typeof text !== "string" || typeof generatedAt !== "number")
|
|
56
|
+
return;
|
|
57
|
+
if (typeof promptId2 !== "string" || typeof lastMessageID !== "string")
|
|
58
|
+
return;
|
|
59
|
+
return { text, generatedAt, promptId: promptId2, lastMessageID };
|
|
60
|
+
}
|
|
61
|
+
var withSummary = (metadata, record) => ({
|
|
62
|
+
...metadata,
|
|
63
|
+
[METADATA_KEY]: record
|
|
64
|
+
});
|
|
65
|
+
function withoutSummary(metadata) {
|
|
66
|
+
const { [METADATA_KEY]: _removed, ...rest } = metadata ?? {};
|
|
67
|
+
return rest;
|
|
68
|
+
}
|
|
69
|
+
var isBlank = (text) => !text || text.trim() === "";
|
|
70
|
+
function relativeTime(ts, now) {
|
|
71
|
+
const s = Math.floor((now - ts) / 1000);
|
|
72
|
+
const m = Math.floor(s / 60);
|
|
73
|
+
const h = Math.floor(m / 60);
|
|
74
|
+
const d = Math.floor(h / 24);
|
|
75
|
+
if (s < 60)
|
|
76
|
+
return "just now";
|
|
77
|
+
if (m < 60)
|
|
78
|
+
return `${m}m ago`;
|
|
79
|
+
if (h < 24)
|
|
80
|
+
return `${h}h ago`;
|
|
81
|
+
if (d < 7)
|
|
82
|
+
return `${d}d ago`;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
function datetime(ts) {
|
|
86
|
+
const date = new Date(ts);
|
|
87
|
+
return `${date.toLocaleTimeString(undefined, { timeStyle: "short" })} \xB7 ${date.toLocaleDateString()}`;
|
|
88
|
+
}
|
|
89
|
+
var TURN_LIMIT = 200;
|
|
90
|
+
var GENERATE_TIMEOUT_MS = 180000;
|
|
91
|
+
function turnsSince(userMessageIDs, lastMessageID, limit = TURN_LIMIT) {
|
|
92
|
+
const count = userMessageIDs.filter((id) => id > lastMessageID).length;
|
|
93
|
+
return { count, capped: userMessageIDs.length >= limit && count >= limit };
|
|
94
|
+
}
|
|
95
|
+
function turnsText({ count, capped }) {
|
|
96
|
+
if (capped)
|
|
97
|
+
return `${TURN_LIMIT}+ turns ago`;
|
|
98
|
+
if (count === 0)
|
|
99
|
+
return "up to date";
|
|
100
|
+
return count === 1 ? "1 turn ago" : `${count} turns ago`;
|
|
101
|
+
}
|
|
102
|
+
var turnLabel = (turns) => `summary ${turnsText(turns)}`;
|
|
103
|
+
function generatedLine(record, now, turns) {
|
|
104
|
+
const relative = relativeTime(record.generatedAt, now);
|
|
105
|
+
const parts = ["Generated", ...relative ? [`${relative} \xB7`] : [], datetime(record.generatedAt)];
|
|
106
|
+
return turns ? `${parts.join(" ")} \xB7 ${turnsText(turns)}` : parts.join(" ");
|
|
107
|
+
}
|
|
108
|
+
function errorMessage(cause) {
|
|
109
|
+
if (cause instanceof Error && cause.message)
|
|
110
|
+
return cause.message;
|
|
111
|
+
return typeof cause === "string" && cause ? cause : "Unknown error";
|
|
112
|
+
}
|
|
113
|
+
function blockReason(input) {
|
|
114
|
+
if (input.inFlight)
|
|
115
|
+
return "in-flight";
|
|
116
|
+
if (input.status === "running")
|
|
117
|
+
return "running";
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
function lastVisibleID(idsDesc, revertBoundary) {
|
|
121
|
+
return revertBoundary === undefined ? idsDesc[0] : idsDesc.find((id) => id < revertBoundary);
|
|
122
|
+
}
|
|
123
|
+
function blockMessage(reason) {
|
|
124
|
+
switch (reason) {
|
|
125
|
+
case "in-flight":
|
|
126
|
+
return "Already generating";
|
|
127
|
+
case "running":
|
|
128
|
+
return "Agent is running - wait for it to finish";
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function isMessageNotFound(error) {
|
|
132
|
+
if (!error || typeof error !== "object")
|
|
133
|
+
return false;
|
|
134
|
+
const { _tag, name } = error;
|
|
135
|
+
return _tag === "MessageNotFoundError" || name === "MessageNotFoundError";
|
|
136
|
+
}
|
|
137
|
+
var validityAction = (check) => !check.found && check.notFound ? "delete" : "keep";
|
|
138
|
+
var resolvePrompt = (override) => isBlank(override) ? { text: DEFAULT_PROMPT, source: "default" } : { text: override.trim(), source: "override" };
|
|
139
|
+
function saveAction(edited) {
|
|
140
|
+
if (isBlank(edited))
|
|
141
|
+
return "noop";
|
|
142
|
+
return edited.trim() === DEFAULT_PROMPT.trim() ? "delete" : "write";
|
|
143
|
+
}
|
|
144
|
+
function promptPaths(env, home) {
|
|
145
|
+
const base = env.XDG_CONFIG_HOME?.trim() || path.join(home, ".config");
|
|
146
|
+
const dir = path.join(base, "opencode", "opencode-summary");
|
|
147
|
+
return { dir, override: path.join(dir, "summary.md") };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/tui.tsx
|
|
151
|
+
var paths = promptPaths(process.env, homedir());
|
|
152
|
+
var tui_default = Plugin.define({
|
|
153
|
+
id: "opencode-summary",
|
|
154
|
+
setup(context) {
|
|
155
|
+
const {
|
|
156
|
+
client,
|
|
157
|
+
ui
|
|
158
|
+
} = context;
|
|
159
|
+
const theme = () => context.theme.surface("dialog");
|
|
160
|
+
const syntax = SyntaxStyle.fromStyles({
|
|
161
|
+
default: {
|
|
162
|
+
fg: context.theme.markdown.text
|
|
163
|
+
},
|
|
164
|
+
"markup.heading": {
|
|
165
|
+
fg: context.theme.markdown.heading,
|
|
166
|
+
bold: true
|
|
167
|
+
},
|
|
168
|
+
"markup.strong": {
|
|
169
|
+
fg: context.theme.markdown.strong,
|
|
170
|
+
bold: true
|
|
171
|
+
},
|
|
172
|
+
"markup.italic": {
|
|
173
|
+
fg: context.theme.markdown.emphasis,
|
|
174
|
+
italic: true
|
|
175
|
+
},
|
|
176
|
+
"markup.list": {
|
|
177
|
+
fg: context.theme.markdown.listItem
|
|
178
|
+
},
|
|
179
|
+
"markup.quote": {
|
|
180
|
+
fg: context.theme.markdown.blockQuote,
|
|
181
|
+
italic: true
|
|
182
|
+
},
|
|
183
|
+
"markup.raw": {
|
|
184
|
+
fg: context.theme.markdown.code
|
|
185
|
+
},
|
|
186
|
+
"markup.link": {
|
|
187
|
+
fg: context.theme.markdown.link,
|
|
188
|
+
underline: true
|
|
189
|
+
}
|
|
190
|
+
}, context.renderer.nativeScene);
|
|
191
|
+
const toastError = (cause) => ui.toast.show({
|
|
192
|
+
variant: "error",
|
|
193
|
+
message: errorMessage(cause)
|
|
194
|
+
});
|
|
195
|
+
async function readMetadata(sessionID) {
|
|
196
|
+
return (await client.session.get({
|
|
197
|
+
sessionID
|
|
198
|
+
})).metadata ?? {};
|
|
199
|
+
}
|
|
200
|
+
async function writeSummary(sessionID, record) {
|
|
201
|
+
const current = await readMetadata(sessionID);
|
|
202
|
+
const metadata = record ? withSummary(current, record) : withoutSummary(current);
|
|
203
|
+
await client.session.update({
|
|
204
|
+
sessionID,
|
|
205
|
+
metadata
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
async function loadSummary(sessionID) {
|
|
209
|
+
const record = readSummary(await readMetadata(sessionID));
|
|
210
|
+
if (!record)
|
|
211
|
+
return;
|
|
212
|
+
const check = await client.session.message.get({
|
|
213
|
+
sessionID,
|
|
214
|
+
messageID: record.lastMessageID
|
|
215
|
+
}).then(() => ({
|
|
216
|
+
found: true
|
|
217
|
+
}), (error) => ({
|
|
218
|
+
found: false,
|
|
219
|
+
notFound: isMessageNotFound(error)
|
|
220
|
+
}));
|
|
221
|
+
if (validityAction(check) === "keep")
|
|
222
|
+
return record;
|
|
223
|
+
await writeSummary(sessionID, undefined);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
async function blocked(sessionID) {
|
|
227
|
+
return blockReason({
|
|
228
|
+
status: context.data.session.status(sessionID),
|
|
229
|
+
inFlight: inFlight(sessionID)
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
const [tick, setTick] = createSignal(0);
|
|
233
|
+
let pending;
|
|
234
|
+
const bump = () => {
|
|
235
|
+
clearTimeout(pending);
|
|
236
|
+
pending = setTimeout(() => setTick((n) => n + 1), 150);
|
|
237
|
+
};
|
|
238
|
+
const stops = ["session.inbox.delivered", "session.metadata.updated", "session.revert.committed", "session.revert.staged", "session.revert.cleared", "session.execution.started", "session.execution.succeeded", "session.execution.failed", "session.execution.interrupted"].map((type) => context.data.on(type, bump));
|
|
239
|
+
const [flight, setFlight] = context.storage.memory("inflight", {
|
|
240
|
+
initial: {
|
|
241
|
+
sessions: []
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
const inFlight = (sessionID) => flight.sessions.includes(sessionID);
|
|
245
|
+
const [modalFor, setModalFor] = createSignal();
|
|
246
|
+
async function countTurns(sessionID, lastMessageID) {
|
|
247
|
+
const page = await client.message.list({
|
|
248
|
+
sessionID,
|
|
249
|
+
type: "user",
|
|
250
|
+
order: "desc",
|
|
251
|
+
limit: TURN_LIMIT
|
|
252
|
+
});
|
|
253
|
+
return turnsSince(page.data.map((message) => message.id), lastMessageID);
|
|
254
|
+
}
|
|
255
|
+
async function loadView(sessionID) {
|
|
256
|
+
const record = await loadSummary(sessionID);
|
|
257
|
+
return record ? {
|
|
258
|
+
record,
|
|
259
|
+
turns: await countTurns(sessionID, record.lastMessageID)
|
|
260
|
+
} : undefined;
|
|
261
|
+
}
|
|
262
|
+
async function readOverride() {
|
|
263
|
+
return readFile(paths.override, "utf8").catch((error) => error.code === "ENOENT" ? undefined : Promise.reject(error));
|
|
264
|
+
}
|
|
265
|
+
const loadPrompt = async () => resolvePrompt(await readOverride());
|
|
266
|
+
async function applyEdited(edited, previous) {
|
|
267
|
+
switch (saveAction(edited)) {
|
|
268
|
+
case "noop":
|
|
269
|
+
if (previous === undefined)
|
|
270
|
+
await rm(paths.override, {
|
|
271
|
+
force: true
|
|
272
|
+
});
|
|
273
|
+
else
|
|
274
|
+
await writeFile(paths.override, previous);
|
|
275
|
+
return void ui.toast.show({
|
|
276
|
+
variant: "warning",
|
|
277
|
+
message: "Empty prompt - not saved"
|
|
278
|
+
});
|
|
279
|
+
case "delete":
|
|
280
|
+
await rm(paths.override, {
|
|
281
|
+
force: true
|
|
282
|
+
});
|
|
283
|
+
return void ui.toast.show({
|
|
284
|
+
message: "Prompt matches default - override removed"
|
|
285
|
+
});
|
|
286
|
+
case "write":
|
|
287
|
+
await mkdir(path2.dirname(paths.override), {
|
|
288
|
+
recursive: true
|
|
289
|
+
});
|
|
290
|
+
await writeFile(paths.override, edited.trim() + `
|
|
291
|
+
`);
|
|
292
|
+
return void ui.toast.show({
|
|
293
|
+
variant: "success",
|
|
294
|
+
message: "Prompt saved"
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function spawnEditor(command, file) {
|
|
299
|
+
const [bin, ...args] = command.split(" ");
|
|
300
|
+
return new Promise((resolve, reject) => {
|
|
301
|
+
const child = spawn(bin, [...args, file], {
|
|
302
|
+
stdio: "inherit"
|
|
303
|
+
});
|
|
304
|
+
child.on("error", reject);
|
|
305
|
+
child.on("exit", (code, signal) => code === 0 ? resolve() : reject(new Error(`Editor exited with ${signal ? `signal ${signal}` : `code ${code}`}`)));
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
const externalEditor = () => process.env.VISUAL || process.env.EDITOR;
|
|
309
|
+
async function editWithEditor(sessionID, editor) {
|
|
310
|
+
const previous = await readOverride();
|
|
311
|
+
await mkdir(path2.dirname(paths.override), {
|
|
312
|
+
recursive: true
|
|
313
|
+
});
|
|
314
|
+
if (previous === undefined)
|
|
315
|
+
await writeFile(paths.override, (await loadPrompt()).text + `
|
|
316
|
+
`);
|
|
317
|
+
await context.renderer.suspend();
|
|
318
|
+
try {
|
|
319
|
+
await spawnEditor(editor, paths.override);
|
|
320
|
+
} catch (cause) {
|
|
321
|
+
toastError(cause);
|
|
322
|
+
return;
|
|
323
|
+
} finally {
|
|
324
|
+
await context.renderer.resume();
|
|
325
|
+
}
|
|
326
|
+
await applyEdited(await readOverride() ?? "", previous);
|
|
327
|
+
openModal(sessionID, "prompt");
|
|
328
|
+
}
|
|
329
|
+
async function resetPrompt(sessionID, reopen = true) {
|
|
330
|
+
const confirmed = await ui.dialog.confirm({
|
|
331
|
+
title: "Reset summary prompt?",
|
|
332
|
+
message: `Delete ${ui.format.path(paths.override)} and use the default prompt.`,
|
|
333
|
+
label: {
|
|
334
|
+
confirm: "Reset",
|
|
335
|
+
cancel: "Cancel"
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
if (confirmed) {
|
|
339
|
+
await rm(paths.override, {
|
|
340
|
+
force: true
|
|
341
|
+
});
|
|
342
|
+
ui.toast.show({
|
|
343
|
+
message: "Prompt reset to default"
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
if (reopen)
|
|
347
|
+
openModal(sessionID, "prompt");
|
|
348
|
+
}
|
|
349
|
+
async function generate(sessionID) {
|
|
350
|
+
const prompt = (await loadPrompt()).text;
|
|
351
|
+
const [info, page] = await Promise.all([client.session.get({
|
|
352
|
+
sessionID
|
|
353
|
+
}), client.message.list({
|
|
354
|
+
sessionID,
|
|
355
|
+
order: "desc",
|
|
356
|
+
limit: TURN_LIMIT
|
|
357
|
+
})]);
|
|
358
|
+
const lastMessageID = lastVisibleID(page.data.map((message) => message.id), info.revert?.messageID);
|
|
359
|
+
if (!lastMessageID) {
|
|
360
|
+
ui.toast.show({
|
|
361
|
+
variant: "warning",
|
|
362
|
+
message: "Nothing to summarize yet"
|
|
363
|
+
});
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
let timer;
|
|
367
|
+
const timeout = new Promise((_, reject) => {
|
|
368
|
+
timer = setTimeout(() => reject(new Error("Summary timed out")), GENERATE_TIMEOUT_MS);
|
|
369
|
+
});
|
|
370
|
+
const result = await Promise.race([client.session.generate({
|
|
371
|
+
sessionID,
|
|
372
|
+
prompt: buildRequest(prompt)
|
|
373
|
+
}), timeout]).catch((cause) => (toastError(cause), undefined)).finally(() => clearTimeout(timer));
|
|
374
|
+
if (!result)
|
|
375
|
+
return false;
|
|
376
|
+
if (isBlank(result.text)) {
|
|
377
|
+
ui.toast.show({
|
|
378
|
+
variant: "error",
|
|
379
|
+
message: "Summary came back empty"
|
|
380
|
+
});
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
await writeSummary(sessionID, {
|
|
384
|
+
text: result.text.trim(),
|
|
385
|
+
generatedAt: Date.now(),
|
|
386
|
+
promptId: promptId(prompt),
|
|
387
|
+
lastMessageID
|
|
388
|
+
});
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
const [settings, setSettings] = context.storage.store("settings", {
|
|
392
|
+
initial: {
|
|
393
|
+
indicator: true
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
function copyText(text) {
|
|
397
|
+
if (!context.renderer.copyToClipboardOSC52(text))
|
|
398
|
+
ui.toast.show({
|
|
399
|
+
variant: "warning",
|
|
400
|
+
message: "Copy not supported by this terminal"
|
|
401
|
+
});
|
|
402
|
+
else
|
|
403
|
+
ui.toast.show({
|
|
404
|
+
variant: "success",
|
|
405
|
+
message: "Summary copied"
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
function currentSession() {
|
|
409
|
+
const route = ui.router.current();
|
|
410
|
+
if (route.type === "session")
|
|
411
|
+
return route.sessionID;
|
|
412
|
+
ui.toast.show({
|
|
413
|
+
variant: "warning",
|
|
414
|
+
message: "Open a session first"
|
|
415
|
+
});
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
async function deleteSummary(sessionID) {
|
|
419
|
+
if (!await loadSummary(sessionID))
|
|
420
|
+
return void ui.toast.show({
|
|
421
|
+
message: "No summary to delete"
|
|
422
|
+
});
|
|
423
|
+
const confirmed = await ui.dialog.confirm({
|
|
424
|
+
title: "Delete summary?",
|
|
425
|
+
message: "Remove this session's stored summary.",
|
|
426
|
+
label: {
|
|
427
|
+
confirm: "Delete",
|
|
428
|
+
cancel: "Cancel"
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
if (!confirmed)
|
|
432
|
+
return;
|
|
433
|
+
await writeSummary(sessionID, undefined);
|
|
434
|
+
ui.toast.show({
|
|
435
|
+
message: "Summary deleted"
|
|
436
|
+
});
|
|
437
|
+
bump();
|
|
438
|
+
}
|
|
439
|
+
async function startGenerate(sessionID) {
|
|
440
|
+
const reason = await blocked(sessionID);
|
|
441
|
+
if (reason)
|
|
442
|
+
return void ui.toast.show({
|
|
443
|
+
variant: "warning",
|
|
444
|
+
message: blockMessage(reason)
|
|
445
|
+
});
|
|
446
|
+
setFlight((draft) => void draft.sessions.push(sessionID));
|
|
447
|
+
try {
|
|
448
|
+
const written = await generate(sessionID);
|
|
449
|
+
if (written && modalFor() !== sessionID)
|
|
450
|
+
ui.toast.show({
|
|
451
|
+
variant: "success",
|
|
452
|
+
message: "Summary ready",
|
|
453
|
+
sessionID
|
|
454
|
+
});
|
|
455
|
+
} catch (cause) {
|
|
456
|
+
toastError(cause);
|
|
457
|
+
} finally {
|
|
458
|
+
setFlight((draft) => void (draft.sessions = draft.sessions.filter((id) => id !== sessionID)));
|
|
459
|
+
bump();
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
function Spinner(props) {
|
|
463
|
+
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
464
|
+
const [i, setI] = createSignal(0);
|
|
465
|
+
const timer = setInterval(() => setI((n) => (n + 1) % frames.length), 80);
|
|
466
|
+
onCleanup(() => clearInterval(timer));
|
|
467
|
+
return (() => {
|
|
468
|
+
var _el$ = _$createElement("text");
|
|
469
|
+
_$insert(_el$, () => `${frames[i()]} ${props.label}`);
|
|
470
|
+
_$effect((_$p) => _$setProp(_el$, "fg", context.theme.hue.interactive[200], _$p));
|
|
471
|
+
return _el$;
|
|
472
|
+
})();
|
|
473
|
+
}
|
|
474
|
+
function FooterStatus(props) {
|
|
475
|
+
const [view] = createResource(() => props.sessionID ? [props.sessionID, tick()] : undefined, ([sessionID]) => loadView(sessionID).catch(() => {
|
|
476
|
+
return;
|
|
477
|
+
}));
|
|
478
|
+
return _$createComponent(Show, {
|
|
479
|
+
get when() {
|
|
480
|
+
return props.sessionID;
|
|
481
|
+
},
|
|
482
|
+
children: (sessionID) => _$createComponent(Show, {
|
|
483
|
+
get when() {
|
|
484
|
+
return !inFlight(sessionID());
|
|
485
|
+
},
|
|
486
|
+
get fallback() {
|
|
487
|
+
return _$createComponent(Spinner, {
|
|
488
|
+
label: "/summary"
|
|
489
|
+
});
|
|
490
|
+
},
|
|
491
|
+
get children() {
|
|
492
|
+
return _$createComponent(Show, {
|
|
493
|
+
get when() {
|
|
494
|
+
return _$memo(() => !!settings.indicator)() && view();
|
|
495
|
+
},
|
|
496
|
+
children: (current) => (() => {
|
|
497
|
+
var _el$2 = _$createElement("text");
|
|
498
|
+
_$insert(_el$2, () => turnLabel(current().turns));
|
|
499
|
+
_$effect((_$p) => _$setProp(_el$2, "fg", context.theme.text.muted, _$p));
|
|
500
|
+
return _el$2;
|
|
501
|
+
})()
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
})
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
ui.slot({
|
|
508
|
+
append: "prompt.footer.status",
|
|
509
|
+
render: (props) => _$createComponent(FooterStatus, {
|
|
510
|
+
get sessionID() {
|
|
511
|
+
return props.sessionID;
|
|
512
|
+
}
|
|
513
|
+
})
|
|
514
|
+
});
|
|
515
|
+
function Hints(props) {
|
|
516
|
+
return (() => {
|
|
517
|
+
var _el$3 = _$createElement("box");
|
|
518
|
+
_$setProp(_el$3, "flexDirection", "row");
|
|
519
|
+
_$setProp(_el$3, "gap", 2);
|
|
520
|
+
_$insert(_el$3, _$createComponent(For, {
|
|
521
|
+
get each() {
|
|
522
|
+
return props.items;
|
|
523
|
+
},
|
|
524
|
+
children: ([title, key]) => (() => {
|
|
525
|
+
var _el$4 = _$createElement("text");
|
|
526
|
+
_$insert(_el$4, `${title} ${key}`);
|
|
527
|
+
_$effect((_$p) => _$setProp(_el$4, "fg", theme().text.muted, _$p));
|
|
528
|
+
return _el$4;
|
|
529
|
+
})()
|
|
530
|
+
}));
|
|
531
|
+
return _el$3;
|
|
532
|
+
})();
|
|
533
|
+
}
|
|
534
|
+
function PromptEditor(props) {
|
|
535
|
+
const [target, setTarget] = createSignal();
|
|
536
|
+
let textarea;
|
|
537
|
+
const save = async () => {
|
|
538
|
+
const previous = await readOverride();
|
|
539
|
+
await applyEdited(textarea?.plainText ?? "", previous).then(() => props.onDone(true), toastError);
|
|
540
|
+
};
|
|
541
|
+
context.keymap.layer(() => ({
|
|
542
|
+
mode: "modal",
|
|
543
|
+
priority: 1,
|
|
544
|
+
target,
|
|
545
|
+
enabled: target() !== undefined,
|
|
546
|
+
commands: [{
|
|
547
|
+
bind: "ctrl+s",
|
|
548
|
+
title: "Save prompt",
|
|
549
|
+
group: "Dialog",
|
|
550
|
+
run: () => void save()
|
|
551
|
+
}, {
|
|
552
|
+
bind: "escape",
|
|
553
|
+
title: "Cancel edit",
|
|
554
|
+
group: "Dialog",
|
|
555
|
+
run: () => props.onDone(false)
|
|
556
|
+
}]
|
|
557
|
+
}));
|
|
558
|
+
onMount(() => setTimeout(() => textarea && !textarea.isDestroyed && textarea.focus(), 1));
|
|
559
|
+
return (() => {
|
|
560
|
+
var _el$5 = _$createElement("box"), _el$6 = _$createElement("text"), _el$7 = _$createElement("textarea");
|
|
561
|
+
_$insertNode(_el$5, _el$6);
|
|
562
|
+
_$insertNode(_el$5, _el$7);
|
|
563
|
+
_$setProp(_el$5, "gap", 1);
|
|
564
|
+
_$insert(_el$6, `fixed: ${GUARD}`);
|
|
565
|
+
_$use((element) => (textarea = element, setTarget(element)), _el$7);
|
|
566
|
+
_$setProp(_el$7, "wrapMode", "word");
|
|
567
|
+
_$insert(_el$5, _$createComponent(Hints, {
|
|
568
|
+
items: [["save", "ctrl+s"], ["cancel", "esc"]]
|
|
569
|
+
}), null);
|
|
570
|
+
_$effect((_p$) => {
|
|
571
|
+
var _v$ = theme().text.muted, _v$2 = props.initial, _v$3 = props.height, _v$4 = theme().text.formfield.base, _v$5 = theme().text.formfield.base, _v$6 = theme().text.base;
|
|
572
|
+
_v$ !== _p$.e && (_p$.e = _$setProp(_el$6, "fg", _v$, _p$.e));
|
|
573
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$7, "initialValue", _v$2, _p$.t));
|
|
574
|
+
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$7, "height", _v$3, _p$.a));
|
|
575
|
+
_v$4 !== _p$.o && (_p$.o = _$setProp(_el$7, "textColor", _v$4, _p$.o));
|
|
576
|
+
_v$5 !== _p$.i && (_p$.i = _$setProp(_el$7, "focusedTextColor", _v$5, _p$.i));
|
|
577
|
+
_v$6 !== _p$.n && (_p$.n = _$setProp(_el$7, "cursorColor", _v$6, _p$.n));
|
|
578
|
+
return _p$;
|
|
579
|
+
}, {
|
|
580
|
+
e: undefined,
|
|
581
|
+
t: undefined,
|
|
582
|
+
a: undefined,
|
|
583
|
+
o: undefined,
|
|
584
|
+
i: undefined,
|
|
585
|
+
n: undefined
|
|
586
|
+
});
|
|
587
|
+
return _el$5;
|
|
588
|
+
})();
|
|
589
|
+
}
|
|
590
|
+
function SummaryDialog(props) {
|
|
591
|
+
const dimensions = useTerminalDimensions();
|
|
592
|
+
const maxHeight = createMemo(() => Math.max(8, Math.floor(dimensions().height * 0.6)));
|
|
593
|
+
const [mode, setMode] = createSignal(props.mode);
|
|
594
|
+
const [view] = createResource(() => [props.sessionID, tick()], ([id]) => loadView(id).catch(toastError));
|
|
595
|
+
const [reason] = createResource(() => [props.sessionID, tick()], ([id]) => blocked(id).catch(() => {
|
|
596
|
+
return;
|
|
597
|
+
}));
|
|
598
|
+
const [prompt, {
|
|
599
|
+
refetch: refetchPrompt
|
|
600
|
+
}] = createResource(() => [mode(), tick()], () => loadPrompt());
|
|
601
|
+
const record = () => view()?.record;
|
|
602
|
+
const busy = () => inFlight(props.sessionID);
|
|
603
|
+
const stale = () => view() && prompt() && view().record.promptId !== promptId(prompt().text);
|
|
604
|
+
let scroll;
|
|
605
|
+
const run = () => void startGenerate(props.sessionID);
|
|
606
|
+
const copy = () => {
|
|
607
|
+
const text = record()?.text;
|
|
608
|
+
if (text)
|
|
609
|
+
copyText(text);
|
|
610
|
+
};
|
|
611
|
+
const edit = () => {
|
|
612
|
+
const editor = externalEditor();
|
|
613
|
+
if (editor)
|
|
614
|
+
editWithEditor(props.sessionID, editor).catch(toastError);
|
|
615
|
+
else
|
|
616
|
+
setMode("edit");
|
|
617
|
+
};
|
|
618
|
+
context.keymap.layer(() => ({
|
|
619
|
+
mode: "modal",
|
|
620
|
+
enabled: mode() !== "edit",
|
|
621
|
+
commands: [{
|
|
622
|
+
bind: "g",
|
|
623
|
+
title: "Generate summary",
|
|
624
|
+
group: "Dialog",
|
|
625
|
+
run
|
|
626
|
+
}, {
|
|
627
|
+
bind: "v",
|
|
628
|
+
title: "Toggle prompt view",
|
|
629
|
+
group: "Dialog",
|
|
630
|
+
run: () => setMode((m) => m === "summary" ? "prompt" : "summary")
|
|
631
|
+
}]
|
|
632
|
+
}));
|
|
633
|
+
context.keymap.layer(() => ({
|
|
634
|
+
mode: "modal",
|
|
635
|
+
enabled: mode() === "summary",
|
|
636
|
+
commands: [{
|
|
637
|
+
bind: "c",
|
|
638
|
+
title: "Copy summary",
|
|
639
|
+
group: "Dialog",
|
|
640
|
+
run: copy
|
|
641
|
+
}]
|
|
642
|
+
}));
|
|
643
|
+
context.keymap.layer(() => ({
|
|
644
|
+
mode: "modal",
|
|
645
|
+
enabled: mode() === "prompt",
|
|
646
|
+
commands: [{
|
|
647
|
+
bind: "e",
|
|
648
|
+
title: "Edit prompt",
|
|
649
|
+
group: "Dialog",
|
|
650
|
+
run: edit
|
|
651
|
+
}, {
|
|
652
|
+
bind: "r",
|
|
653
|
+
title: "Reset prompt",
|
|
654
|
+
group: "Dialog",
|
|
655
|
+
run: () => void resetPrompt(props.sessionID).catch(toastError)
|
|
656
|
+
}]
|
|
657
|
+
}));
|
|
658
|
+
useKeyboard((event) => {
|
|
659
|
+
if (!scroll || mode() === "edit")
|
|
660
|
+
return;
|
|
661
|
+
if (event.name === "up" || event.name === "k")
|
|
662
|
+
return scroll.scrollBy(-1);
|
|
663
|
+
if (event.name === "down" || event.name === "j")
|
|
664
|
+
return scroll.scrollBy(1);
|
|
665
|
+
if (event.name === "pageup")
|
|
666
|
+
return scroll.scrollBy(-maxHeight());
|
|
667
|
+
if (event.name === "pagedown")
|
|
668
|
+
return scroll.scrollBy(maxHeight());
|
|
669
|
+
if (event.name === "home")
|
|
670
|
+
return scroll.scrollTo(0);
|
|
671
|
+
if (event.name === "end")
|
|
672
|
+
return scroll.scrollTo(scroll.scrollHeight);
|
|
673
|
+
});
|
|
674
|
+
const summaryBody = () => _$createComponent(Show, {
|
|
675
|
+
get when() {
|
|
676
|
+
return record();
|
|
677
|
+
},
|
|
678
|
+
children: (current) => (() => {
|
|
679
|
+
var _el$8 = _$createElement("scrollbox"), _el$9 = _$createElement("markdown");
|
|
680
|
+
_$insertNode(_el$8, _el$9);
|
|
681
|
+
_$use((element) => scroll = element, _el$8);
|
|
682
|
+
_$setProp(_el$8, "scrollbarOptions", {
|
|
683
|
+
visible: false
|
|
684
|
+
});
|
|
685
|
+
_$setProp(_el$9, "syntaxStyle", syntax);
|
|
686
|
+
_$setProp(_el$9, "conceal", true);
|
|
687
|
+
_$effect((_p$) => {
|
|
688
|
+
var _v$7 = maxHeight(), _v$8 = current().text, _v$9 = theme().markdown.text;
|
|
689
|
+
_v$7 !== _p$.e && (_p$.e = _$setProp(_el$8, "maxHeight", _v$7, _p$.e));
|
|
690
|
+
_v$8 !== _p$.t && (_p$.t = _$setProp(_el$9, "content", _v$8, _p$.t));
|
|
691
|
+
_v$9 !== _p$.a && (_p$.a = _$setProp(_el$9, "fg", _v$9, _p$.a));
|
|
692
|
+
return _p$;
|
|
693
|
+
}, {
|
|
694
|
+
e: undefined,
|
|
695
|
+
t: undefined,
|
|
696
|
+
a: undefined
|
|
697
|
+
});
|
|
698
|
+
return _el$8;
|
|
699
|
+
})()
|
|
700
|
+
});
|
|
701
|
+
return (() => {
|
|
702
|
+
var _el$0 = _$createElement("box"), _el$1 = _$createElement("box"), _el$10 = _$createElement("text"), _el$12 = _$createElement("text"), _el$14 = _$createElement("text");
|
|
703
|
+
_$insertNode(_el$0, _el$1);
|
|
704
|
+
_$insertNode(_el$0, _el$14);
|
|
705
|
+
_$setProp(_el$0, "paddingLeft", 3);
|
|
706
|
+
_$setProp(_el$0, "paddingRight", 3);
|
|
707
|
+
_$setProp(_el$0, "paddingBottom", 1);
|
|
708
|
+
_$setProp(_el$0, "gap", 1);
|
|
709
|
+
_$insertNode(_el$1, _el$10);
|
|
710
|
+
_$insertNode(_el$1, _el$12);
|
|
711
|
+
_$setProp(_el$1, "flexDirection", "row");
|
|
712
|
+
_$setProp(_el$1, "justifyContent", "space-between");
|
|
713
|
+
_$insertNode(_el$10, _$createTextNode(`/summary`));
|
|
714
|
+
_$insertNode(_el$12, _$createTextNode(`esc`));
|
|
715
|
+
_$insert(_el$14, (() => {
|
|
716
|
+
var _c$ = _$memo(() => !!busy());
|
|
717
|
+
return () => _c$() ? "Generating\u2026" : _$memo(() => !!view())() ? generatedLine(view().record, Date.now(), view().turns) : "No summary yet - press g to generate";
|
|
718
|
+
})());
|
|
719
|
+
_$insert(_el$0, _$createComponent(Show, {
|
|
720
|
+
get when() {
|
|
721
|
+
return _$memo(() => mode() === "edit")() && prompt();
|
|
722
|
+
},
|
|
723
|
+
get children() {
|
|
724
|
+
return _$createComponent(PromptEditor, {
|
|
725
|
+
get initial() {
|
|
726
|
+
return prompt().text;
|
|
727
|
+
},
|
|
728
|
+
get height() {
|
|
729
|
+
return Math.max(6, maxHeight() - 4);
|
|
730
|
+
},
|
|
731
|
+
onDone: (saved) => {
|
|
732
|
+
setMode("prompt");
|
|
733
|
+
if (saved)
|
|
734
|
+
refetchPrompt();
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
}), null);
|
|
739
|
+
_$insert(_el$0, _$createComponent(Show, {
|
|
740
|
+
get when() {
|
|
741
|
+
return mode() === "prompt";
|
|
742
|
+
},
|
|
743
|
+
get children() {
|
|
744
|
+
var _el$15 = _$createElement("scrollbox"), _el$16 = _$createElement("box"), _el$17 = _$createElement("text"), _el$18 = _$createElement("text"), _el$19 = _$createElement("text");
|
|
745
|
+
_$insertNode(_el$15, _el$16);
|
|
746
|
+
_$use((element) => scroll = element, _el$15);
|
|
747
|
+
_$setProp(_el$15, "scrollbarOptions", {
|
|
748
|
+
visible: false
|
|
749
|
+
});
|
|
750
|
+
_$insertNode(_el$16, _el$17);
|
|
751
|
+
_$insertNode(_el$16, _el$18);
|
|
752
|
+
_$insertNode(_el$16, _el$19);
|
|
753
|
+
_$setProp(_el$16, "gap", 1);
|
|
754
|
+
_$insert(_el$17, `fixed: ${GUARD}`);
|
|
755
|
+
_$insert(_el$18, () => prompt()?.text ?? "");
|
|
756
|
+
_$insert(_el$19, (() => {
|
|
757
|
+
var _c$2 = _$memo(() => prompt()?.source === "override");
|
|
758
|
+
return () => _c$2() ? `source: override (${ui.format.path(paths.override)})` : "source: default (prompt.ts)";
|
|
759
|
+
})());
|
|
760
|
+
_$effect((_p$) => {
|
|
761
|
+
var _v$0 = maxHeight(), _v$1 = theme().text.muted, _v$10 = theme().text.base, _v$11 = theme().text.muted;
|
|
762
|
+
_v$0 !== _p$.e && (_p$.e = _$setProp(_el$15, "maxHeight", _v$0, _p$.e));
|
|
763
|
+
_v$1 !== _p$.t && (_p$.t = _$setProp(_el$17, "fg", _v$1, _p$.t));
|
|
764
|
+
_v$10 !== _p$.a && (_p$.a = _$setProp(_el$18, "fg", _v$10, _p$.a));
|
|
765
|
+
_v$11 !== _p$.o && (_p$.o = _$setProp(_el$19, "fg", _v$11, _p$.o));
|
|
766
|
+
return _p$;
|
|
767
|
+
}, {
|
|
768
|
+
e: undefined,
|
|
769
|
+
t: undefined,
|
|
770
|
+
a: undefined,
|
|
771
|
+
o: undefined
|
|
772
|
+
});
|
|
773
|
+
return _el$15;
|
|
774
|
+
}
|
|
775
|
+
}), null);
|
|
776
|
+
_$insert(_el$0, _$createComponent(Show, {
|
|
777
|
+
get when() {
|
|
778
|
+
return mode() === "summary";
|
|
779
|
+
},
|
|
780
|
+
get children() {
|
|
781
|
+
return summaryBody();
|
|
782
|
+
}
|
|
783
|
+
}), null);
|
|
784
|
+
_$insert(_el$0, _$createComponent(Show, {
|
|
785
|
+
get when() {
|
|
786
|
+
return _$memo(() => mode() === "summary")() && stale();
|
|
787
|
+
},
|
|
788
|
+
get children() {
|
|
789
|
+
var _el$20 = _$createElement("text");
|
|
790
|
+
_$insertNode(_el$20, _$createTextNode(`Generated with a different prompt`));
|
|
791
|
+
_$effect((_$p) => _$setProp(_el$20, "fg", theme().text.muted, _$p));
|
|
792
|
+
return _el$20;
|
|
793
|
+
}
|
|
794
|
+
}), null);
|
|
795
|
+
_$insert(_el$0, _$createComponent(Show, {
|
|
796
|
+
get when() {
|
|
797
|
+
return mode() !== "edit";
|
|
798
|
+
},
|
|
799
|
+
get children() {
|
|
800
|
+
return _$createComponent(Hints, {
|
|
801
|
+
get items() {
|
|
802
|
+
return [[reason() ? `generate (${reason()})` : "generate", "g"], [mode() === "summary" ? "prompt" : "summary", "v"], ...mode() === "prompt" ? [["edit", "e"], ["reset", "r"]] : [], ...mode() === "summary" ? [["copy", "c"]] : [], ["scroll", "j/k"]];
|
|
803
|
+
}
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
}), null);
|
|
807
|
+
_$effect((_p$) => {
|
|
808
|
+
var _v$12 = TextAttributes.BOLD, _v$13 = theme().text.base, _v$14 = theme().text.muted, _v$15 = theme().text.muted;
|
|
809
|
+
_v$12 !== _p$.e && (_p$.e = _$setProp(_el$10, "attributes", _v$12, _p$.e));
|
|
810
|
+
_v$13 !== _p$.t && (_p$.t = _$setProp(_el$10, "fg", _v$13, _p$.t));
|
|
811
|
+
_v$14 !== _p$.a && (_p$.a = _$setProp(_el$12, "fg", _v$14, _p$.a));
|
|
812
|
+
_v$15 !== _p$.o && (_p$.o = _$setProp(_el$14, "fg", _v$15, _p$.o));
|
|
813
|
+
return _p$;
|
|
814
|
+
}, {
|
|
815
|
+
e: undefined,
|
|
816
|
+
t: undefined,
|
|
817
|
+
a: undefined,
|
|
818
|
+
o: undefined
|
|
819
|
+
});
|
|
820
|
+
return _el$0;
|
|
821
|
+
})();
|
|
822
|
+
}
|
|
823
|
+
function openModal(sessionID, mode = "summary") {
|
|
824
|
+
setModalFor(sessionID);
|
|
825
|
+
ui.dialog.show(() => _$createComponent(SummaryDialog, {
|
|
826
|
+
sessionID,
|
|
827
|
+
mode
|
|
828
|
+
}), () => setModalFor(undefined));
|
|
829
|
+
ui.dialog.set({
|
|
830
|
+
size: "large",
|
|
831
|
+
centered: true
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
ui.slot({
|
|
835
|
+
append: "app",
|
|
836
|
+
render() {
|
|
837
|
+
context.keymap.layer(() => ({
|
|
838
|
+
mode: "global",
|
|
839
|
+
commands: [{
|
|
840
|
+
id: "summary.open",
|
|
841
|
+
title: "Open summary modal",
|
|
842
|
+
description: "View or generate a summary of this session",
|
|
843
|
+
group: "Summary",
|
|
844
|
+
palette: true,
|
|
845
|
+
slash: {
|
|
846
|
+
name: "summary"
|
|
847
|
+
},
|
|
848
|
+
run() {
|
|
849
|
+
const sessionID = currentSession();
|
|
850
|
+
if (sessionID)
|
|
851
|
+
openModal(sessionID);
|
|
852
|
+
}
|
|
853
|
+
}, {
|
|
854
|
+
id: "summary.generate",
|
|
855
|
+
title: "Generate summary",
|
|
856
|
+
description: "Generate a summary without opening the modal",
|
|
857
|
+
group: "Summary",
|
|
858
|
+
palette: true,
|
|
859
|
+
run() {
|
|
860
|
+
const sessionID = currentSession();
|
|
861
|
+
if (sessionID)
|
|
862
|
+
startGenerate(sessionID);
|
|
863
|
+
}
|
|
864
|
+
}, {
|
|
865
|
+
id: "summary.copy",
|
|
866
|
+
title: "Copy summary",
|
|
867
|
+
description: "Copy the stored summary to the clipboard",
|
|
868
|
+
group: "Summary",
|
|
869
|
+
palette: true,
|
|
870
|
+
run() {
|
|
871
|
+
const sessionID = currentSession();
|
|
872
|
+
if (!sessionID)
|
|
873
|
+
return;
|
|
874
|
+
loadSummary(sessionID).then((record) => record ? copyText(record.text) : ui.toast.show({
|
|
875
|
+
message: "No summary yet"
|
|
876
|
+
}), toastError);
|
|
877
|
+
}
|
|
878
|
+
}, {
|
|
879
|
+
id: "summary.delete",
|
|
880
|
+
title: "Delete summary",
|
|
881
|
+
description: "Remove the stored summary of this session",
|
|
882
|
+
group: "Summary",
|
|
883
|
+
palette: true,
|
|
884
|
+
run() {
|
|
885
|
+
const sessionID = currentSession();
|
|
886
|
+
if (sessionID)
|
|
887
|
+
deleteSummary(sessionID).catch(toastError);
|
|
888
|
+
}
|
|
889
|
+
}, {
|
|
890
|
+
id: "summary.prompt.view",
|
|
891
|
+
title: "View summary prompt",
|
|
892
|
+
description: "Show the prompt used for summaries",
|
|
893
|
+
group: "Summary",
|
|
894
|
+
palette: true,
|
|
895
|
+
run() {
|
|
896
|
+
const sessionID = currentSession();
|
|
897
|
+
if (sessionID)
|
|
898
|
+
openModal(sessionID, "prompt");
|
|
899
|
+
}
|
|
900
|
+
}, {
|
|
901
|
+
id: "summary.prompt.edit",
|
|
902
|
+
title: "Edit summary prompt",
|
|
903
|
+
description: "Edit the prompt used for summaries",
|
|
904
|
+
group: "Summary",
|
|
905
|
+
palette: true,
|
|
906
|
+
run() {
|
|
907
|
+
const sessionID = currentSession();
|
|
908
|
+
if (!sessionID)
|
|
909
|
+
return;
|
|
910
|
+
const editor = externalEditor();
|
|
911
|
+
if (editor)
|
|
912
|
+
editWithEditor(sessionID, editor).catch(toastError);
|
|
913
|
+
else
|
|
914
|
+
openModal(sessionID, "edit");
|
|
915
|
+
}
|
|
916
|
+
}, {
|
|
917
|
+
id: "summary.prompt.reset",
|
|
918
|
+
title: "Reset summary prompt",
|
|
919
|
+
description: "Restore the default summary prompt",
|
|
920
|
+
group: "Summary",
|
|
921
|
+
palette: true,
|
|
922
|
+
run() {
|
|
923
|
+
const sessionID = currentSession();
|
|
924
|
+
if (sessionID)
|
|
925
|
+
resetPrompt(sessionID, false).catch(toastError);
|
|
926
|
+
}
|
|
927
|
+
}, {
|
|
928
|
+
id: "summary.indicator.toggle",
|
|
929
|
+
title: "Toggle summary footer indicator",
|
|
930
|
+
description: "Show or hide the turns-ago indicator in the prompt footer",
|
|
931
|
+
group: "Summary",
|
|
932
|
+
palette: true,
|
|
933
|
+
run() {
|
|
934
|
+
const next = !settings.indicator;
|
|
935
|
+
setSettings((draft) => void (draft.indicator = next)).then(() => ui.toast.show({
|
|
936
|
+
message: next ? "Footer indicator shown" : "Footer indicator hidden"
|
|
937
|
+
}), toastError);
|
|
938
|
+
}
|
|
939
|
+
}]
|
|
940
|
+
}));
|
|
941
|
+
return null;
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
return () => {
|
|
945
|
+
clearTimeout(pending);
|
|
946
|
+
stops.forEach((stop) => stop());
|
|
947
|
+
context.renderer.idle().then(() => syntax.destroy());
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
export {
|
|
952
|
+
tui_default as default
|
|
953
|
+
};
|