Skip to content

Commit e861e23

Browse files
committed
builder output, context menu, add to context...
1 parent 84ca20a commit e861e23

2 files changed

Lines changed: 64 additions & 5 deletions

File tree

frontend/app/aipanel/waveai-model.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,12 +299,18 @@ export class WaveAIModel {
299299
return input != null && input.trim().length > 0;
300300
}
301301

302-
appendText(text: string) {
302+
appendText(text: string, newLine?: boolean) {
303303
const currentInput = globalStore.get(this.inputAtom);
304304
let newInput = currentInput;
305305

306-
if (newInput.length > 0 && !newInput.endsWith(" ") && !newInput.endsWith("\n")) {
307-
newInput += " ";
306+
if (newInput.length > 0) {
307+
if (newLine) {
308+
if (!newInput.endsWith("\n")) {
309+
newInput += "\n";
310+
}
311+
} else if (!newInput.endsWith(" ") && !newInput.endsWith("\n")) {
312+
newInput += " ";
313+
}
308314
}
309315

310316
newInput += text;

frontend/builder/builder-buildpanel.tsx

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,42 @@
11
// Copyright 2025, Command Line Inc.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
import { WaveAIModel } from "@/app/aipanel/waveai-model";
5+
import { ContextMenuModel } from "@/app/store/contextmenu";
46
import { BuilderBuildPanelModel } from "@/builder/store/builder-buildpanel-model";
57
import { useAtomValue } from "jotai";
6-
import { memo, useEffect, useRef } from "react";
8+
import { memo, useCallback, useEffect, useRef } from "react";
9+
import { debounce } from "throttle-debounce";
10+
11+
function handleBuildPanelContextMenu(e: React.MouseEvent, selectedText: string): void {
12+
e.preventDefault();
13+
e.stopPropagation();
14+
15+
if (!selectedText) {
16+
return;
17+
}
18+
19+
const menu: ContextMenuItem[] = [
20+
{ role: "copy" },
21+
{ type: "separator" },
22+
{
23+
label: "Add to Context",
24+
click: () => {
25+
const model = WaveAIModel.getInstance();
26+
const formattedText = `from builder output:\n\`\`\`\n${selectedText}\n\`\`\``;
27+
model.appendText(formattedText, true);
28+
model.focusInput();
29+
},
30+
},
31+
];
32+
ContextMenuModel.showContextMenu(menu, e);
33+
}
734

835
const BuilderBuildPanel = memo(() => {
936
const model = BuilderBuildPanelModel.getInstance();
1037
const outputLines = useAtomValue(model.outputLines);
1138
const scrollRef = useRef<HTMLDivElement>(null);
39+
const preRef = useRef<HTMLPreElement>(null);
1240

1341
useEffect(() => {
1442
model.initialize();
@@ -23,13 +51,38 @@ const BuilderBuildPanel = memo(() => {
2351
}
2452
}, [outputLines]);
2553

54+
const debouncedCopyOnSelect = useCallback(
55+
debounce(50, () => {
56+
const selection = window.getSelection();
57+
if (selection && selection.toString().length > 0) {
58+
navigator.clipboard.writeText(selection.toString());
59+
}
60+
}),
61+
[]
62+
);
63+
64+
const handleMouseUp = useCallback(() => {
65+
debouncedCopyOnSelect();
66+
}, [debouncedCopyOnSelect]);
67+
68+
const handleContextMenu = useCallback((e: React.MouseEvent) => {
69+
const selection = window.getSelection();
70+
const selectedText = selection ? selection.toString() : "";
71+
handleBuildPanelContextMenu(e, selectedText);
72+
}, []);
73+
2674
return (
2775
<div className="w-full h-full flex flex-col bg-black">
2876
<div className="flex-shrink-0 px-3 py-2 border-b border-gray-700">
2977
<span className="text-sm font-semibold text-gray-300">Build Output</span>
3078
</div>
3179
<div ref={scrollRef} className="flex-1 overflow-y-auto overflow-x-auto p-2">
32-
<pre className="font-mono text-xs text-gray-100 whitespace-pre">
80+
<pre
81+
ref={preRef}
82+
className="font-mono text-xs text-gray-100 whitespace-pre"
83+
onMouseUp={handleMouseUp}
84+
onContextMenu={handleContextMenu}
85+
>
3386
{outputLines.length === 0 ? (
3487
<span className="text-secondary">Waiting for output...</span>
3588
) : (

0 commit comments

Comments
 (0)