mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-16 08:02:17 +00:00
Add setEditorText/getEditorText to hook UI context, improve custom() API
- Add setEditorText() and getEditorText() to HookUIContext for prompt generator pattern - custom() now accepts async factories for fire-and-forget work - Add CancellableLoader component to tui package - Add BorderedLoader component for hooks with cancel UI - Export HookAPI, HookContext, HookFactory from main package - Update all examples to import from packages instead of relative paths - Update hooks.md and custom-tools.md documentation fixes #350
This commit is contained in:
parent
02d0d6e192
commit
6f7c10e323
39 changed files with 477 additions and 163 deletions
|
|
@ -0,0 +1,41 @@
|
|||
import { CancellableLoader, Container, Spacer, Text, type TUI } from "@mariozechner/pi-tui";
|
||||
import type { Theme } from "../theme/theme.js";
|
||||
import { DynamicBorder } from "./dynamic-border.js";
|
||||
|
||||
/** Loader wrapped with borders for hook UI */
|
||||
export class BorderedLoader extends Container {
|
||||
private loader: CancellableLoader;
|
||||
|
||||
constructor(tui: TUI, theme: Theme, message: string) {
|
||||
super();
|
||||
this.addChild(new DynamicBorder());
|
||||
this.addChild(new Spacer(1));
|
||||
this.loader = new CancellableLoader(
|
||||
tui,
|
||||
(s) => theme.fg("accent", s),
|
||||
(s) => theme.fg("muted", s),
|
||||
message,
|
||||
);
|
||||
this.addChild(this.loader);
|
||||
this.addChild(new Spacer(1));
|
||||
this.addChild(new Text(theme.fg("muted", "esc cancel"), 1, 0));
|
||||
this.addChild(new Spacer(1));
|
||||
this.addChild(new DynamicBorder());
|
||||
}
|
||||
|
||||
get signal(): AbortSignal {
|
||||
return this.loader.signal;
|
||||
}
|
||||
|
||||
set onAbort(fn: (() => void) | undefined) {
|
||||
this.loader.onAbort = fn;
|
||||
}
|
||||
|
||||
handleInput(data: string): void {
|
||||
this.loader.handleInput(data);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.loader.dispose();
|
||||
}
|
||||
}
|
||||
|
|
@ -54,7 +54,15 @@ import { ToolExecutionComponent } from "./components/tool-execution.js";
|
|||
import { TreeSelectorComponent } from "./components/tree-selector.js";
|
||||
import { UserMessageComponent } from "./components/user-message.js";
|
||||
import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
|
||||
import { getAvailableThemes, getEditorTheme, getMarkdownTheme, onThemeChange, setTheme, theme } from "./theme/theme.js";
|
||||
import {
|
||||
getAvailableThemes,
|
||||
getEditorTheme,
|
||||
getMarkdownTheme,
|
||||
onThemeChange,
|
||||
setTheme,
|
||||
type Theme,
|
||||
theme,
|
||||
} from "./theme/theme.js";
|
||||
|
||||
/** Interface for components that can be expanded/collapsed */
|
||||
interface Expandable {
|
||||
|
|
@ -356,7 +364,9 @@ export class InteractiveMode {
|
|||
confirm: (title, message) => this.showHookConfirm(title, message),
|
||||
input: (title, placeholder) => this.showHookInput(title, placeholder),
|
||||
notify: (message, type) => this.showHookNotify(message, type),
|
||||
custom: (component) => this.showHookCustom(component),
|
||||
custom: (factory) => this.showHookCustom(factory),
|
||||
setEditorText: (text) => this.editor.setText(text),
|
||||
getEditorText: () => this.editor.getText(),
|
||||
};
|
||||
this.setToolUIContext(uiContext, true);
|
||||
|
||||
|
|
@ -537,38 +547,37 @@ export class InteractiveMode {
|
|||
|
||||
/**
|
||||
* Show a custom component with keyboard focus.
|
||||
* Returns a function to call when done.
|
||||
*/
|
||||
private showHookCustom(component: Component & { dispose?(): void }): {
|
||||
close: () => void;
|
||||
requestRender: () => void;
|
||||
} {
|
||||
// Store current editor content
|
||||
private async showHookCustom<T>(
|
||||
factory: (
|
||||
tui: TUI,
|
||||
theme: Theme,
|
||||
done: (result: T) => void,
|
||||
) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,
|
||||
): Promise<T> {
|
||||
const savedText = this.editor.getText();
|
||||
|
||||
// Replace editor with custom component
|
||||
this.editorContainer.clear();
|
||||
this.editorContainer.addChild(component);
|
||||
this.ui.setFocus(component);
|
||||
this.ui.requestRender();
|
||||
return new Promise((resolve) => {
|
||||
let component: Component & { dispose?(): void };
|
||||
|
||||
// Return control object
|
||||
return {
|
||||
close: () => {
|
||||
// Call dispose if available
|
||||
const close = (result: T) => {
|
||||
component.dispose?.();
|
||||
|
||||
// Restore editor
|
||||
this.editorContainer.clear();
|
||||
this.editorContainer.addChild(this.editor);
|
||||
this.editor.setText(savedText);
|
||||
this.ui.setFocus(this.editor);
|
||||
this.ui.requestRender();
|
||||
},
|
||||
requestRender: () => {
|
||||
resolve(result);
|
||||
};
|
||||
|
||||
Promise.resolve(factory(this.ui, theme, close)).then((c) => {
|
||||
component = c;
|
||||
this.editorContainer.clear();
|
||||
this.editorContainer.addChild(component);
|
||||
this.ui.setFocus(component);
|
||||
this.ui.requestRender();
|
||||
},
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue