@mtayfur/opencode-prompt-enhancer 0.0.17 → 0.0.19

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.
@@ -0,0 +1,859 @@
1
+ import { createComponent as _$createComponent } from "@opentui/solid";
2
+ import { effect as _$effect } from "@opentui/solid";
3
+ import { use as _$use } from "@opentui/solid";
4
+ import { createTextNode as _$createTextNode } from "@opentui/solid";
5
+ import { insertNode as _$insertNode } from "@opentui/solid";
6
+ import { setProp as _$setProp } from "@opentui/solid";
7
+ import { createElement as _$createElement } from "@opentui/solid";
8
+ /** @jsxImportSource @opentui/solid */
9
+
10
+ import { useTerminalDimensions } from "@opentui/solid";
11
+ import { Show, createMemo, createSignal } from "solid-js";
12
+ import { ENHANCER_SYSTEM_PROMPT } from "./enhancer-system-prompt";
13
+ const MAX_RECENT_MESSAGES = 3;
14
+ const MAX_CHANGED_FILES = 25;
15
+ const MAX_PROMPT_PREVIEW_LENGTH = 250;
16
+ const ENHANCEMENT_TIMEOUT_MS = 60_000;
17
+ const ENHANCEMENT_ANIMATION_INTERVAL_MS = 250;
18
+ const TOAST_DURATION_MS = 3_000;
19
+ const ENHANCEMENT_CANCELED_MESSAGE = "Prompt enhancement canceled.";
20
+ const ENHANCEMENT_ANIMATION_FRAMES = ["Enhancing prompt", "Enhancing prompt.", "Enhancing prompt..", "Enhancing prompt..."];
21
+ const DIALOG_TITLE = "Enhance Prompt";
22
+ const DIALOG_WIDTH_RATIO = 0.60;
23
+ const DIALOG_MAX_WIDTH = 120;
24
+ const DIALOG_MIN_WIDTH = 40;
25
+ const DIALOG_SCREEN_MARGIN_X = 4;
26
+ const DIALOG_SCREEN_MARGIN_Y = 4;
27
+ const DIALOG_HEIGHT = 24;
28
+ const DIALOG_PADDING_X = 2;
29
+ const DIALOG_PADDING_Y = 1;
30
+ const DIALOG_TEXTAREA_RESERVED_HEIGHT = 7;
31
+ const DIALOG_TEXTAREA_VERTICAL_PADDING = 2;
32
+ const DIALOG_BACKDROP_COLOR = "#000000";
33
+ const DIALOG_BACKDROP_OPACITY = 0.65;
34
+ const DIALOG_PLACEHOLDER = "Describe the task...";
35
+ const DIALOG_HINT = "Enter to enhance • Shift+Enter for newline • Esc to cancel";
36
+ const TOAST_TITLE = "Prompt enhancer";
37
+ function parseEnhancementInput(input) {
38
+ const match = input.trimEnd().match(/^(\/[A-Za-z0-9][A-Za-z0-9._:-]*(?:\/[A-Za-z0-9][A-Za-z0-9._:-]*)*)(?:(?: +|\n)([\s\S]*))?$/);
39
+ if (!match) return {
40
+ draft: input.trim()
41
+ };
42
+ return {
43
+ command: match[1],
44
+ draft: match[2]?.trim() ?? ""
45
+ };
46
+ }
47
+ function formatEnhancedInput(input, enhancedDraft) {
48
+ return input.command ? `${input.command} ${enhancedDraft}` : enhancedDraft;
49
+ }
50
+ function parseModelString(value) {
51
+ if (!value) return undefined;
52
+ const trimmed = value.trim();
53
+ const slash = trimmed.indexOf("/");
54
+ if (slash <= 0 || slash === trimmed.length - 1) return undefined;
55
+ return {
56
+ providerID: trimmed.slice(0, slash),
57
+ modelID: trimmed.slice(slash + 1)
58
+ };
59
+ }
60
+ function isSessionRoute(route) {
61
+ return route.name === "session";
62
+ }
63
+ function extractVisibleText(parts) {
64
+ return parts.filter(part => part.type === "text" && !part.ignored).map(part => part.text).join("").trim();
65
+ }
66
+ function resolveEnhancerModel(api, options) {
67
+ const override = typeof options?.model === "string" ? parseModelString(options.model) : undefined;
68
+ if (override) return override;
69
+ return parseModelString(api.state.config.small_model || api.state.config.model);
70
+ }
71
+ function clonePromptInfo(prompt) {
72
+ return {
73
+ input: prompt.input,
74
+ mode: prompt.mode,
75
+ parts: prompt.parts.map(part => ({
76
+ ...part
77
+ }))
78
+ };
79
+ }
80
+ function nextPromptInfo(prompt, input) {
81
+ return {
82
+ input,
83
+ mode: prompt.mode,
84
+ parts: prompt.parts.filter(part => part.type !== "text").map(part => ({
85
+ ...part
86
+ }))
87
+ };
88
+ }
89
+ function samePromptInfo(left, right) {
90
+ return left.input === right.input && left.mode === right.mode && JSON.stringify(left.parts) === JSON.stringify(right.parts);
91
+ }
92
+ function errorFromReason(reason, fallbackMessage) {
93
+ return reason instanceof Error ? reason : new Error(fallbackMessage);
94
+ }
95
+ function clamp(value, min, max) {
96
+ return Math.min(Math.max(value, min), max);
97
+ }
98
+ async function withRequestTimeout(signal, timeoutMs, run) {
99
+ const controller = new AbortController();
100
+ const timeoutMessage = `Prompt enhancement timed out after ${Math.floor(timeoutMs / 1000)} seconds.`;
101
+ const onAbort = () => controller.abort(signal.reason);
102
+ const timeout = setTimeout(() => controller.abort(new Error(timeoutMessage)), timeoutMs);
103
+ if (signal.aborted) {
104
+ controller.abort(signal.reason);
105
+ } else {
106
+ signal.addEventListener("abort", onAbort, {
107
+ once: true
108
+ });
109
+ }
110
+ try {
111
+ return await run(controller.signal);
112
+ } catch (error) {
113
+ if (controller.signal.aborted && !signal.aborted) {
114
+ throw errorFromReason(controller.signal.reason, timeoutMessage);
115
+ }
116
+ throw error;
117
+ } finally {
118
+ clearTimeout(timeout);
119
+ signal.removeEventListener("abort", onAbort);
120
+ }
121
+ }
122
+ function samePromptTarget(left, right) {
123
+ if (!left || !right || left.name !== right.name) return false;
124
+ if (left.name === "session" && right.name === "session") {
125
+ return left.sessionID === right.sessionID;
126
+ }
127
+ return true;
128
+ }
129
+ function isPromptHandleActive(api, state, handle) {
130
+ const route = api.route.current;
131
+ const target = handle.target;
132
+ if (target.name === "home") {
133
+ if (route.name !== "home") return false;
134
+ } else if (!isSessionRoute(route) || route.params.sessionID !== target.sessionID) {
135
+ return false;
136
+ }
137
+ if (api.state.path.directory !== handle.directory) return false;
138
+ if (handle.ref) {
139
+ return state.promptRef === handle.ref && samePromptTarget(state.promptTarget, handle.target);
140
+ }
141
+ return true;
142
+ }
143
+ function bindPromptRef(state, target, forwarded, ref) {
144
+ if (ref) {
145
+ state.promptRef = ref;
146
+ state.promptTarget = target;
147
+ } else if (samePromptTarget(state.promptTarget, target)) {
148
+ state.promptRef = undefined;
149
+ state.promptTarget = undefined;
150
+ }
151
+ forwarded?.(ref);
152
+ }
153
+ async function applyPromptUpdate(api, state, handle, update, signal) {
154
+ if (!isPromptHandleActive(api, state, handle)) return false;
155
+ const promptRef = handle.ref;
156
+ if (promptRef) {
157
+ promptRef.set(update.createPromptInfo(promptRef.current));
158
+ if (update.refAction === "focus") promptRef.focus();else promptRef.blur();
159
+ return true;
160
+ }
161
+ const requestOptions = {
162
+ signal,
163
+ throwOnError: true
164
+ };
165
+ await api.client.tui.clearPrompt({
166
+ directory: handle.directory
167
+ }, requestOptions);
168
+ if (update.fallbackInput) {
169
+ await api.client.tui.appendPrompt({
170
+ directory: handle.directory,
171
+ text: update.fallbackInput
172
+ }, requestOptions);
173
+ }
174
+ return true;
175
+ }
176
+ function clearPrompt(api, state, handle, signal, template) {
177
+ return applyPromptUpdate(api, state, handle, {
178
+ fallbackInput: "",
179
+ createPromptInfo: current => nextPromptInfo(template ?? current, ""),
180
+ refAction: "blur"
181
+ }, signal);
182
+ }
183
+ function writePrompt(api, state, handle, input, signal, template) {
184
+ return applyPromptUpdate(api, state, handle, {
185
+ fallbackInput: input,
186
+ createPromptInfo: current => nextPromptInfo(template ?? current, input),
187
+ refAction: "focus"
188
+ }, signal);
189
+ }
190
+ function restorePrompt(api, state, handle, prompt, signal) {
191
+ return applyPromptUpdate(api, state, handle, {
192
+ fallbackInput: prompt.input,
193
+ createPromptInfo: () => clonePromptInfo(prompt),
194
+ refAction: "focus"
195
+ }, signal);
196
+ }
197
+ function restoreEnhancementPrompt(api, state, enhancement, signal) {
198
+ return enhancement.originalPrompt ? restorePrompt(api, state, enhancement.handle, enhancement.originalPrompt, signal) : writePrompt(api, state, enhancement.handle, enhancement.input, signal);
199
+ }
200
+ async function cancelActiveEnhancement(api, state, signal) {
201
+ const active = state.activeEnhancement;
202
+ if (!state.enhancing || !active) return false;
203
+ active.canceled = true;
204
+ active.stopAnimation?.();
205
+ active.controller.abort(new Error(ENHANCEMENT_CANCELED_MESSAGE));
206
+ return restoreEnhancementPrompt(api, state, active, signal);
207
+ }
208
+ function startEnhancementAnimation(api, state, handle, template) {
209
+ const promptRef = handle.ref;
210
+ if (!promptRef) return () => {};
211
+ let frame = 0;
212
+ let stopped = false;
213
+ const render = () => {
214
+ if (stopped) return;
215
+ if (!isPromptHandleActive(api, state, handle)) {
216
+ stopped = true;
217
+ return;
218
+ }
219
+ promptRef.set(nextPromptInfo(template ?? promptRef.current, ENHANCEMENT_ANIMATION_FRAMES[frame]));
220
+ frame = (frame + 1) % ENHANCEMENT_ANIMATION_FRAMES.length;
221
+ };
222
+ render();
223
+ const interval = setInterval(render, ENHANCEMENT_ANIMATION_INTERVAL_MS);
224
+ return () => {
225
+ stopped = true;
226
+ clearInterval(interval);
227
+ };
228
+ }
229
+ function gatherContext(api) {
230
+ const sections = [];
231
+ const dir = api.state.path.directory;
232
+ sections.push(`Working directory: ${dir}`);
233
+ const branch = api.state.vcs?.branch;
234
+ if (branch) {
235
+ sections.push(`Current branch: ${branch}`);
236
+ }
237
+ const route = api.route.current;
238
+ if (isSessionRoute(route)) {
239
+ const sessionID = route.params.sessionID;
240
+ const messages = api.state.session.messages(sessionID);
241
+ const userMessages = messages.filter(message => message.role === "user");
242
+ const recent = userMessages.slice(-MAX_RECENT_MESSAGES).reverse();
243
+ if (recent.length > 0) {
244
+ const prompts = [];
245
+ for (const msg of recent) {
246
+ const text = extractVisibleText(api.state.part(msg.id));
247
+ if (text) {
248
+ prompts.push(text.length > MAX_PROMPT_PREVIEW_LENGTH ? `${text.slice(0, MAX_PROMPT_PREVIEW_LENGTH)}...` : text);
249
+ }
250
+ }
251
+ if (prompts.length > 0) {
252
+ sections.push(`Recent user prompts in this session (newest first):\n${prompts.map((p, i) => `${i + 1}. ${p}`).join("\n")}`);
253
+ }
254
+ }
255
+ const diff = api.state.session.diff(sessionID);
256
+ if (diff.length > 0) {
257
+ const files = diff.slice(0, MAX_CHANGED_FILES).map(f => ` @${f.file}`);
258
+ sections.push(`Files changed in session:\n${files.join("\n")}`);
259
+ }
260
+ }
261
+ return sections.join("\n\n");
262
+ }
263
+ async function enhanceWithModel(api, options, input, signal) {
264
+ const directory = api.state.path.directory;
265
+ const model = resolveEnhancerModel(api, options);
266
+ const context = gatherContext(api);
267
+ const userMessage = [`--- CONTEXT ---\n${context}\n---`, `--- DRAFT ---\n${input}\n---`].join("\n\n");
268
+ const created = await api.client.session.create({
269
+ directory,
270
+ title: `Prompt Enhancer ${Math.random().toString(36).slice(2, 8)}`,
271
+ permission: [{
272
+ permission: "*",
273
+ action: "deny",
274
+ pattern: "*"
275
+ }]
276
+ }, {
277
+ signal,
278
+ throwOnError: true
279
+ });
280
+ const tempSessionID = created.data?.id;
281
+ if (!tempSessionID) throw new Error("Failed to start prompt enhancer.");
282
+ try {
283
+ const response = await withRequestTimeout(signal, ENHANCEMENT_TIMEOUT_MS, requestSignal => api.client.session.prompt({
284
+ sessionID: tempSessionID,
285
+ directory,
286
+ model,
287
+ system: ENHANCER_SYSTEM_PROMPT,
288
+ parts: [{
289
+ type: "text",
290
+ text: userMessage
291
+ }]
292
+ }, {
293
+ signal: requestSignal,
294
+ throwOnError: true
295
+ }));
296
+ const parts = response.data?.parts;
297
+ if (!parts) throw new Error("Enhancer returned no response.");
298
+ const enhanced = extractVisibleText(parts);
299
+ if (!enhanced) throw new Error("Enhancer returned no text.");
300
+ return enhanced;
301
+ } finally {
302
+ void api.client.session.abort({
303
+ sessionID: tempSessionID,
304
+ directory
305
+ }).catch(() => {}).then(() => api.client.session.delete({
306
+ sessionID: tempSessionID,
307
+ directory
308
+ })).catch(() => {});
309
+ }
310
+ }
311
+ function renderEnhanceDialog(api, dialog) {
312
+ const theme = api.theme.current;
313
+ const terminal = useTerminalDimensions();
314
+ const screenWidth = createMemo(() => Math.max(1, terminal().width));
315
+ const screenHeight = createMemo(() => Math.max(1, terminal().height));
316
+ const panelWidth = createMemo(() => {
317
+ const width = screenWidth();
318
+ const availableWidth = Math.max(1, width - DIALOG_SCREEN_MARGIN_X);
319
+ const minWidth = Math.min(DIALOG_MIN_WIDTH, availableWidth);
320
+ const maxWidth = Math.min(DIALOG_MAX_WIDTH, availableWidth);
321
+ return clamp(Math.floor(width * DIALOG_WIDTH_RATIO), minWidth, maxWidth);
322
+ });
323
+ const panelHeight = createMemo(() => {
324
+ const availableHeight = Math.max(1, screenHeight() - DIALOG_SCREEN_MARGIN_Y);
325
+ return Math.min(DIALOG_HEIGHT, availableHeight);
326
+ });
327
+ const textareaBoxHeight = createMemo(() => Math.max(1, panelHeight() - DIALOG_TEXTAREA_RESERVED_HEIGHT));
328
+ const textareaHeight = createMemo(() => Math.max(1, textareaBoxHeight() - DIALOG_TEXTAREA_VERTICAL_PADDING));
329
+ let dialogInput;
330
+ return (() => {
331
+ var _el$ = _$createElement("box"),
332
+ _el$2 = _$createElement("box"),
333
+ _el$3 = _$createElement("box"),
334
+ _el$4 = _$createElement("text"),
335
+ _el$6 = _$createElement("box"),
336
+ _el$7 = _$createElement("textarea"),
337
+ _el$8 = _$createElement("text");
338
+ _$insertNode(_el$, _el$2);
339
+ _$insertNode(_el$, _el$3);
340
+ _$setProp(_el$, "position", "absolute");
341
+ _$setProp(_el$, "top", 0);
342
+ _$setProp(_el$, "left", 0);
343
+ _$setProp(_el$, "justifyContent", "center");
344
+ _$setProp(_el$, "alignItems", "center");
345
+ _$setProp(_el$2, "position", "absolute");
346
+ _$setProp(_el$2, "top", 0);
347
+ _$setProp(_el$2, "left", 0);
348
+ _$setProp(_el$2, "backgroundColor", "#000000");
349
+ _$setProp(_el$2, "opacity", 0.65);
350
+ _$insertNode(_el$3, _el$4);
351
+ _$insertNode(_el$3, _el$6);
352
+ _$insertNode(_el$3, _el$8);
353
+ _$setProp(_el$3, "flexDirection", "column");
354
+ _$setProp(_el$3, "gap", 1);
355
+ _$setProp(_el$3, "paddingX", 2);
356
+ _$setProp(_el$3, "paddingY", 1);
357
+ _$setProp(_el$3, "border", true);
358
+ _$insertNode(_el$4, _$createTextNode(`Enhance Prompt`));
359
+ _$insertNode(_el$6, _el$7);
360
+ _$setProp(_el$6, "border", true);
361
+ _$setProp(_el$6, "flexDirection", "column");
362
+ _$setProp(_el$6, "paddingX", 1);
363
+ _$setProp(_el$6, "paddingY", 1);
364
+ _$use(node => {
365
+ dialogInput = node;
366
+ if (!node) return;
367
+ node.cursorOffset = node.plainText.length;
368
+ queueMicrotask(() => {
369
+ if (dialogInput === node) node.focus();
370
+ });
371
+ }, _el$7);
372
+ _$setProp(_el$7, "width", "100%");
373
+ _$setProp(_el$7, "placeholder", "Describe the task...");
374
+ _$setProp(_el$7, "wrapMode", "word");
375
+ _$setProp(_el$7, "keyBindings", [{
376
+ name: "return",
377
+ action: "submit"
378
+ }, {
379
+ name: "linefeed",
380
+ action: "submit"
381
+ }, {
382
+ name: "return",
383
+ shift: true,
384
+ action: "newline"
385
+ }, {
386
+ name: "linefeed",
387
+ shift: true,
388
+ action: "newline"
389
+ }]);
390
+ _$setProp(_el$7, "onKeyDown", key => {
391
+ if (key.name === "escape" || key.ctrl && key.name === "c") dialog.onCancel();
392
+ });
393
+ _$setProp(_el$7, "onSubmit", () => dialog.onConfirm(dialogInput?.plainText ?? ""));
394
+ _$insertNode(_el$8, _$createTextNode(`Enter to enhance • Shift+Enter for newline • Esc to cancel`));
395
+ _$effect(_p$ => {
396
+ var _v$ = screenWidth(),
397
+ _v$2 = screenHeight(),
398
+ _v$3 = screenWidth(),
399
+ _v$4 = screenHeight(),
400
+ _v$5 = panelWidth(),
401
+ _v$6 = panelHeight(),
402
+ _v$7 = theme.backgroundPanel,
403
+ _v$8 = theme.border,
404
+ _v$9 = theme.borderActive,
405
+ _v$0 = textareaBoxHeight(),
406
+ _v$1 = textareaHeight(),
407
+ _v$10 = dialog.initialValue,
408
+ _v$11 = theme.text,
409
+ _v$12 = theme.textMuted,
410
+ _v$13 = theme.backgroundPanel,
411
+ _v$14 = theme.backgroundPanel,
412
+ _v$15 = theme.text,
413
+ _v$16 = theme.primary,
414
+ _v$17 = theme.textMuted;
415
+ _v$ !== _p$.e && (_p$.e = _$setProp(_el$, "width", _v$, _p$.e));
416
+ _v$2 !== _p$.t && (_p$.t = _$setProp(_el$, "height", _v$2, _p$.t));
417
+ _v$3 !== _p$.a && (_p$.a = _$setProp(_el$2, "width", _v$3, _p$.a));
418
+ _v$4 !== _p$.o && (_p$.o = _$setProp(_el$2, "height", _v$4, _p$.o));
419
+ _v$5 !== _p$.i && (_p$.i = _$setProp(_el$3, "width", _v$5, _p$.i));
420
+ _v$6 !== _p$.n && (_p$.n = _$setProp(_el$3, "height", _v$6, _p$.n));
421
+ _v$7 !== _p$.s && (_p$.s = _$setProp(_el$3, "backgroundColor", _v$7, _p$.s));
422
+ _v$8 !== _p$.h && (_p$.h = _$setProp(_el$3, "borderColor", _v$8, _p$.h));
423
+ _v$9 !== _p$.r && (_p$.r = _$setProp(_el$6, "borderColor", _v$9, _p$.r));
424
+ _v$0 !== _p$.d && (_p$.d = _$setProp(_el$6, "height", _v$0, _p$.d));
425
+ _v$1 !== _p$.l && (_p$.l = _$setProp(_el$7, "height", _v$1, _p$.l));
426
+ _v$10 !== _p$.u && (_p$.u = _$setProp(_el$7, "initialValue", _v$10, _p$.u));
427
+ _v$11 !== _p$.c && (_p$.c = _$setProp(_el$7, "textColor", _v$11, _p$.c));
428
+ _v$12 !== _p$.w && (_p$.w = _$setProp(_el$7, "placeholderColor", _v$12, _p$.w));
429
+ _v$13 !== _p$.m && (_p$.m = _$setProp(_el$7, "backgroundColor", _v$13, _p$.m));
430
+ _v$14 !== _p$.f && (_p$.f = _$setProp(_el$7, "focusedBackgroundColor", _v$14, _p$.f));
431
+ _v$15 !== _p$.y && (_p$.y = _$setProp(_el$7, "focusedTextColor", _v$15, _p$.y));
432
+ _v$16 !== _p$.g && (_p$.g = _$setProp(_el$7, "cursorColor", _v$16, _p$.g));
433
+ _v$17 !== _p$.p && (_p$.p = _$setProp(_el$8, "fg", _v$17, _p$.p));
434
+ return _p$;
435
+ }, {
436
+ e: undefined,
437
+ t: undefined,
438
+ a: undefined,
439
+ o: undefined,
440
+ i: undefined,
441
+ n: undefined,
442
+ s: undefined,
443
+ h: undefined,
444
+ r: undefined,
445
+ d: undefined,
446
+ l: undefined,
447
+ u: undefined,
448
+ c: undefined,
449
+ w: undefined,
450
+ m: undefined,
451
+ f: undefined,
452
+ y: undefined,
453
+ g: undefined,
454
+ p: undefined
455
+ });
456
+ return _el$;
457
+ })();
458
+ }
459
+ function openEnhanceDialog(api, options, state, setEnhanceDialog, signal) {
460
+ if (state.enhancing) {
461
+ api.ui.toast({
462
+ variant: "warning",
463
+ title: TOAST_TITLE,
464
+ message: "Enhancement in progress."
465
+ });
466
+ return;
467
+ }
468
+ if (signal.aborted) return;
469
+ const target = state.promptTarget ?? (api.route.current.name === "home" ? {
470
+ name: "home"
471
+ } : isSessionRoute(api.route.current) ? {
472
+ name: "session",
473
+ sessionID: api.route.current.params.sessionID
474
+ } : undefined);
475
+ if (!target) {
476
+ api.ui.toast({
477
+ variant: "warning",
478
+ title: TOAST_TITLE,
479
+ message: "Enhancement only works from a prompt."
480
+ });
481
+ return;
482
+ }
483
+ const handle = {
484
+ target,
485
+ directory: api.state.path.directory,
486
+ ref: state.promptRef
487
+ };
488
+ const originalPrompt = handle.ref ? clonePromptInfo(handle.ref.current) : undefined;
489
+ const initialValue = originalPrompt?.input ?? "";
490
+ const closeDialog = () => setEnhanceDialog(undefined);
491
+ const confirmInput = value => {
492
+ if (state.enhancing) return;
493
+ const input = value.trim();
494
+ if (!input) {
495
+ api.ui.toast({
496
+ variant: "warning",
497
+ title: TOAST_TITLE,
498
+ message: "Enter a prompt first."
499
+ });
500
+ closeDialog();
501
+ return;
502
+ }
503
+ const enhancementInput = parseEnhancementInput(value);
504
+ if (!enhancementInput.draft) {
505
+ api.ui.toast({
506
+ variant: "warning",
507
+ title: TOAST_TITLE,
508
+ message: "Enter instructions after the slash command."
509
+ });
510
+ closeDialog();
511
+ return;
512
+ }
513
+ if (!isPromptHandleActive(api, state, handle)) {
514
+ api.ui.toast({
515
+ variant: "warning",
516
+ title: TOAST_TITLE,
517
+ message: "Prompt changed while dialog was open."
518
+ });
519
+ closeDialog();
520
+ return;
521
+ }
522
+ state.enhancing = true;
523
+ closeDialog();
524
+ api.ui.toast({
525
+ variant: "info",
526
+ title: TOAST_TITLE,
527
+ message: "Enhancing prompt...",
528
+ duration: TOAST_DURATION_MS
529
+ });
530
+ const enhancementController = new AbortController();
531
+ const onLifecycleAbort = () => enhancementController.abort(signal.reason);
532
+ const activeEnhancement = {
533
+ controller: enhancementController,
534
+ handle,
535
+ originalPrompt,
536
+ input
537
+ };
538
+ state.activeEnhancement = activeEnhancement;
539
+ if (signal.aborted) {
540
+ enhancementController.abort(signal.reason);
541
+ } else {
542
+ signal.addEventListener("abort", onLifecycleAbort, {
543
+ once: true
544
+ });
545
+ }
546
+ void (async () => {
547
+ let stopAnimation = () => {};
548
+ try {
549
+ const cleared = await clearPrompt(api, state, handle, signal, originalPrompt);
550
+ if (!cleared) {
551
+ api.ui.toast({
552
+ variant: "warning",
553
+ title: TOAST_TITLE,
554
+ message: "Prompt changed before enhancement started."
555
+ });
556
+ return;
557
+ }
558
+ stopAnimation = startEnhancementAnimation(api, state, handle, originalPrompt);
559
+ if (state.activeEnhancement) {
560
+ state.activeEnhancement.stopAnimation = stopAnimation;
561
+ }
562
+ const enhancedDraft = await enhanceWithModel(api, options, enhancementInput.draft, enhancementController.signal);
563
+ const enhanced = formatEnhancedInput(enhancementInput, enhancedDraft);
564
+ if (signal.aborted) return;
565
+ if (enhancementController.signal.aborted) {
566
+ throw errorFromReason(enhancementController.signal.reason, ENHANCEMENT_CANCELED_MESSAGE);
567
+ }
568
+ stopAnimation();
569
+ const wrote = await writePrompt(api, state, handle, enhanced, signal, originalPrompt);
570
+ if (!wrote) {
571
+ api.ui.toast({
572
+ variant: "warning",
573
+ title: TOAST_TITLE,
574
+ message: "Enhanced prompt is ready, but that prompt is no longer active."
575
+ });
576
+ return;
577
+ }
578
+ if (enhancementController.signal.aborted) {
579
+ throw errorFromReason(enhancementController.signal.reason, ENHANCEMENT_CANCELED_MESSAGE);
580
+ }
581
+ state.lastEnhancement = originalPrompt ? {
582
+ original: clonePromptInfo(originalPrompt),
583
+ enhancedInput: enhanced,
584
+ target: {
585
+ ...handle.target
586
+ },
587
+ directory: handle.directory
588
+ } : undefined;
589
+ api.ui.toast({
590
+ variant: "success",
591
+ title: "Prompt enhanced",
592
+ message: "Enhanced prompt added to input.",
593
+ duration: TOAST_DURATION_MS
594
+ });
595
+ } catch (error) {
596
+ if (signal.aborted) return;
597
+ stopAnimation();
598
+ const canceled = enhancementController.signal.aborted && !signal.aborted;
599
+ if (canceled && state.activeEnhancement?.canceled) {
600
+ // cancelActiveEnhancement already stopped the animation and restored the prompt.
601
+ return;
602
+ }
603
+ let restored;
604
+ try {
605
+ restored = await restoreEnhancementPrompt(api, state, activeEnhancement, signal);
606
+ } catch {
607
+ // Best-effort restore; do not suppress the error toast.
608
+ restored = false;
609
+ }
610
+ let baseMessage;
611
+ if (canceled) {
612
+ baseMessage = ENHANCEMENT_CANCELED_MESSAGE;
613
+ } else if (error instanceof Error) {
614
+ baseMessage = error.message;
615
+ } else {
616
+ baseMessage = "Prompt enhancement failed.";
617
+ }
618
+ let message;
619
+ if (canceled && restored) {
620
+ message = "Enhancement canceled. Original prompt restored.";
621
+ } else if (restored) {
622
+ message = baseMessage;
623
+ } else {
624
+ message = `${baseMessage} Original prompt could not be restored because the prompt changed. Please re-enter your prompt manually.`;
625
+ }
626
+ api.ui.toast({
627
+ variant: canceled && restored ? "info" : "error",
628
+ title: TOAST_TITLE,
629
+ message
630
+ });
631
+ } finally {
632
+ stopAnimation();
633
+ signal.removeEventListener("abort", onLifecycleAbort);
634
+ if (state.activeEnhancement?.controller === enhancementController) {
635
+ state.activeEnhancement = undefined;
636
+ }
637
+ state.enhancing = false;
638
+ }
639
+ })();
640
+ };
641
+ setEnhanceDialog({
642
+ initialValue,
643
+ onCancel: closeDialog,
644
+ onConfirm: confirmInput
645
+ });
646
+ }
647
+ function revertEnhancement(api, state, signal) {
648
+ if (state.enhancing && state.activeEnhancement) {
649
+ void (async () => {
650
+ try {
651
+ const restored = await cancelActiveEnhancement(api, state, signal);
652
+ if (!restored) {
653
+ api.ui.toast({
654
+ variant: "warning",
655
+ title: TOAST_TITLE,
656
+ message: "Prompt changed while canceling."
657
+ });
658
+ return;
659
+ }
660
+ api.ui.toast({
661
+ variant: "info",
662
+ title: TOAST_TITLE,
663
+ message: "Enhancement canceled. Original prompt restored.",
664
+ duration: TOAST_DURATION_MS
665
+ });
666
+ } catch (error) {
667
+ if (signal.aborted) return;
668
+ const message = error instanceof Error ? error.message : "Cancel failed.";
669
+ api.ui.toast({
670
+ variant: "error",
671
+ title: TOAST_TITLE,
672
+ message
673
+ });
674
+ }
675
+ })();
676
+ return;
677
+ }
678
+ const lastEnhancement = state.lastEnhancement;
679
+ if (!lastEnhancement) {
680
+ api.ui.toast({
681
+ variant: "warning",
682
+ title: TOAST_TITLE,
683
+ message: "No enhancement to revert."
684
+ });
685
+ return;
686
+ }
687
+ const target = state.promptTarget;
688
+ if (!target) {
689
+ api.ui.toast({
690
+ variant: "warning",
691
+ title: TOAST_TITLE,
692
+ message: "Revert only works from a prompt."
693
+ });
694
+ return;
695
+ }
696
+ if (api.state.path.directory !== lastEnhancement.directory || !samePromptTarget(target, lastEnhancement.target)) {
697
+ api.ui.toast({
698
+ variant: "warning",
699
+ title: TOAST_TITLE,
700
+ message: "The enhanced prompt is no longer active."
701
+ });
702
+ return;
703
+ }
704
+ const handle = {
705
+ target,
706
+ directory: api.state.path.directory,
707
+ ref: state.promptRef
708
+ };
709
+ if (!isPromptHandleActive(api, state, handle)) {
710
+ api.ui.toast({
711
+ variant: "warning",
712
+ title: TOAST_TITLE,
713
+ message: "Prompt changed since enhancement."
714
+ });
715
+ return;
716
+ }
717
+ const currentPrompt = handle.ref?.current;
718
+ const expectedEnhancedPrompt = nextPromptInfo(lastEnhancement.original, lastEnhancement.enhancedInput);
719
+ if (!currentPrompt || !samePromptInfo(currentPrompt, expectedEnhancedPrompt)) {
720
+ api.ui.toast({
721
+ variant: "warning",
722
+ title: TOAST_TITLE,
723
+ message: "Prompt was manually changed after enhancement. Revert skipped."
724
+ });
725
+ return;
726
+ }
727
+ void (async () => {
728
+ try {
729
+ const wrote = await restorePrompt(api, state, handle, lastEnhancement.original, signal);
730
+ if (!wrote) {
731
+ api.ui.toast({
732
+ variant: "warning",
733
+ title: TOAST_TITLE,
734
+ message: "Prompt changed while reverting."
735
+ });
736
+ return;
737
+ }
738
+ state.lastEnhancement = undefined;
739
+ api.ui.toast({
740
+ variant: "success",
741
+ title: TOAST_TITLE,
742
+ message: "Reverted to original prompt.",
743
+ duration: TOAST_DURATION_MS
744
+ });
745
+ } catch (error) {
746
+ if (signal.aborted) return;
747
+ const message = error instanceof Error ? error.message : "Revert failed.";
748
+ api.ui.toast({
749
+ variant: "error",
750
+ title: TOAST_TITLE,
751
+ message
752
+ });
753
+ }
754
+ })();
755
+ }
756
+ const tui = async (api, options) => {
757
+ const state = {
758
+ enhancing: false
759
+ };
760
+ const [enhanceDialog, setEnhanceDialog] = createSignal();
761
+ const promptSlots = {
762
+ slots: {
763
+ app() {
764
+ return _$createComponent(Show, {
765
+ get when() {
766
+ return enhanceDialog();
767
+ },
768
+ children: dialog => renderEnhanceDialog(api, dialog())
769
+ });
770
+ },
771
+ home_prompt(_ctx, props) {
772
+ return _$createComponent(api.ui.Prompt, {
773
+ ref: ref => bindPromptRef(state, {
774
+ name: "home"
775
+ }, props.ref, ref),
776
+ get right() {
777
+ return _$createComponent(api.ui.Slot, {
778
+ name: "home_prompt_right"
779
+ });
780
+ }
781
+ });
782
+ },
783
+ session_prompt(_ctx, props) {
784
+ return _$createComponent(api.ui.Prompt, {
785
+ ref: ref => bindPromptRef(state, {
786
+ name: "session",
787
+ sessionID: props.session_id
788
+ }, props.ref, ref),
789
+ get sessionID() {
790
+ return props.session_id;
791
+ },
792
+ get visible() {
793
+ return props.visible;
794
+ },
795
+ get disabled() {
796
+ return props.disabled;
797
+ },
798
+ get onSubmit() {
799
+ return props.on_submit;
800
+ },
801
+ get right() {
802
+ return _$createComponent(api.ui.Slot, {
803
+ name: "session_prompt_right",
804
+ get session_id() {
805
+ return props.session_id;
806
+ }
807
+ });
808
+ }
809
+ });
810
+ }
811
+ }
812
+ };
813
+ api.slots.register(promptSlots);
814
+ const unregister = api.keymap.registerLayer({
815
+ commands: [{
816
+ name: "prompt-enhancer.enhance",
817
+ title: DIALOG_TITLE,
818
+ desc: "Enhance current prompt",
819
+ category: "Prompt",
820
+ suggested: true,
821
+ run: () => {
822
+ openEnhanceDialog(api, options, state, setEnhanceDialog, api.lifecycle.signal);
823
+ }
824
+ }, {
825
+ name: "prompt-enhancer.revert",
826
+ title: "Revert Enhanced Prompt",
827
+ desc: "Revert last prompt enhancement",
828
+ category: "Prompt",
829
+ run: () => {
830
+ revertEnhancement(api, state, api.lifecycle.signal);
831
+ }
832
+ }],
833
+ bindings: [{
834
+ key: "ctrl+e",
835
+ cmd: "prompt-enhancer.enhance"
836
+ }, {
837
+ key: "ctrl+shift+e",
838
+ cmd: "prompt-enhancer.revert"
839
+ }]
840
+ });
841
+ api.lifecycle.onDispose(() => {
842
+ unregister();
843
+ setEnhanceDialog(undefined);
844
+ const active = state.activeEnhancement;
845
+ if (active) {
846
+ active.controller.abort(api.lifecycle.signal.reason);
847
+ }
848
+ state.activeEnhancement = undefined;
849
+ state.enhancing = false;
850
+ state.promptRef = undefined;
851
+ state.promptTarget = undefined;
852
+ state.lastEnhancement = undefined;
853
+ });
854
+ };
855
+ const plugin = {
856
+ id: "prompt-enhancer",
857
+ tui
858
+ };
859
+ export default plugin;