Skip to content

Commit b82fe5b

Browse files
committed
Use cloud managed platform assistant (text-to-text)
1 parent f881ab9 commit b82fe5b

File tree

8 files changed

+402
-302
lines changed

8 files changed

+402
-302
lines changed

web/components/interface/command-viewer.tsx

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import useCommands from "@/lib/hooks/use-commands";
2-
import { Command, MenuAction } from "@/lib/types";
2+
import { Command } from "@/lib/types";
33
import {
44
addToast,
55
Button,
@@ -25,16 +25,20 @@ const inputPlaceholders = [
2525
export default function CommandViewer() {
2626
const editorContext = useContext(EditorContext);
2727

28-
const { chatWithAssistant } = usePlatformAIAssistant();
28+
const { chatWithAssistant, history } = usePlatformAIAssistant();
2929
const { commands, runCommand, setKeywordFilter } = useCommands();
30-
const { registerMenuAction, unregisterMenuAction } = useMenuActions("view");
3130

3231
const [inputPlaceholder, setInputPlaceholder] = useState("");
3332
const [selectCommandIndex, setSelectCommandIndex] = useState(-1);
3433
const [inputTextValue, setInputTextValue] = useState("");
34+
const [inputAudioValue, setInputAudioValue] = useState<
35+
ReadableStream<ArrayBuffer> | undefined
36+
>(undefined);
3537
const [isInputVoice, setIsInputVoice] = useState(false);
3638
const [isOutputVoice, setIsOutputVoice] = useState(false);
3739

40+
const historyRef = useRef<HTMLDivElement>(null);
41+
3842
const runCommandCallback = useCallback(
3943
async (command: Command) => {
4044
const result = await runCommand(command, {});
@@ -81,6 +85,13 @@ export default function CommandViewer() {
8185
handlePressedKeys(editorContext?.editorStates.pressedKeys ?? []);
8286
}, [editorContext?.editorStates.pressedKeys]);
8387

88+
// Scroll to bottom when history changes
89+
useEffect(() => {
90+
if (historyRef.current) {
91+
historyRef.current.scrollTop = historyRef.current.scrollHeight;
92+
}
93+
}, [history]);
94+
8495
function handleKeyDown(e: KeyboardEvent) {
8596
// Prevent default behavior for certain keys
8697
const key = e.key;
@@ -157,9 +168,12 @@ export default function CommandViewer() {
157168
} else if (isEnterPressed && !isControlPressed) {
158169
// Chat with assistant if ctrl is not pressed
159170
console.log("Chatting with assistant");
160-
chatWithAssistant(inputTextValue, isOutputVoice);
161-
}
162-
else if (isArrowUpPressed) {
171+
if (isInputVoice) {
172+
chatWithAssistant(inputAudioValue, isOutputVoice);
173+
} else {
174+
chatWithAssistant(inputTextValue, isOutputVoice);
175+
}
176+
} else if (isArrowUpPressed) {
163177
setSelectCommandIndex((prev) =>
164178
prev === 0 ? commands.length - 1 : prev - 1,
165179
);
@@ -222,6 +236,29 @@ export default function CommandViewer() {
222236
onKeyDown={(e) => handleKeyDown(e as any)}
223237
onKeyUp={(e) => handleKeyUp(e as any)}
224238
/>
239+
240+
{history.length > 0 && (
241+
<div
242+
ref={historyRef}
243+
className="bg-content1 flex max-h-60 flex-col gap-y-1 overflow-y-auto rounded-2xl p-2 shadow-md"
244+
>
245+
{history.map((entry, index) => (
246+
<div key={index}>
247+
{entry.role === "user" ? (
248+
<div className="text-primary-foreground bg-primary rounded-lg p-2 text-sm">
249+
<span className="font-bold">You: </span>
250+
{entry.message.content.text}
251+
</div>
252+
) : (
253+
<div className="text-default-foreground bg-default rounded-lg p-2 text-sm">
254+
<span className="font-bold">Assistant: </span>
255+
{entry.message.content.text}
256+
</div>
257+
)}
258+
</div>
259+
))}
260+
</div>
261+
)}
225262
<div className="bg-content1 rounded-2xl shadow-md">
226263
<div className="px-3 pt-2">
227264
<p className="text-sm font-bold whitespace-nowrap">

web/components/interface/editor-toolbar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import useRecorder from "@/lib/hooks/use-recorder";
1414
export default function EditorToolbar() {
1515
const editorContext = useContext(EditorContext);
1616

17-
const { chatWithAssistant: runAssistant } = usePlatformAIAssistant();
17+
const { chatWithAssistant } = usePlatformAIAssistant();
1818
const { isRecording, record } = useRecorder();
1919

2020
const [isAgentListModalOpen, setIsAgentListModalOpen] = useState(false);
@@ -110,7 +110,7 @@ export default function EditorToolbar() {
110110
onPress={() => {
111111
if (!isRecording) {
112112
const stream = record();
113-
runAssistant(stream, true);
113+
chatWithAssistant(stream, true);
114114
}
115115
}}
116116
variant={

web/lib/agent/agent-runner.ts

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,48 +13,69 @@ import toast from "react-hot-toast";
1313
import { ChatPromptTemplate } from "@langchain/core/prompts";
1414
import { JsonOutputParser } from "@langchain/core/output_parsers";
1515
import { fetchAPI } from "../pulse-editor-website/backend";
16+
import { parseJsonChunk } from "./stream-chunk-parser";
1617

1718
export function getAgentLLMConfig(agent: Agent, methodName: string) {
1819
const method = agent.availableMethods.find((m) => m.name === methodName);
1920
return method?.LLMConfig ? method.LLMConfig : agent.LLMConfig;
2021
}
2122

2223
export async function runAgentMethodCloud(
23-
llmConfig: LLMConfig,
2424
agent: Agent,
2525
methodName: string,
2626
args: Record<string, any>,
27+
onChunkUpdate?: (chunk: any) => void,
2728
): Promise<any> {
2829
const method = agent.availableMethods.find((m) => m.name === methodName);
2930
if (!method) {
3031
throw new Error(`Method ${methodName} not found in agent ${agent.name}.`);
3132
}
3233

33-
const prompt = await getPrompt(agent, method, args);
34+
const prompt = await getAgentPrompt(agent, method, args);
3435

3536
console.log("Prompt: ", prompt);
3637

37-
const response = await fetchAPI("/api/inference/platform-assistant", {
38-
method: "POST",
39-
body: JSON.stringify({
40-
prompt: prompt,
41-
}),
42-
});
38+
const response = await fetchAPI(
39+
"/api/inference/platform-assistant/text-to-text",
40+
{
41+
method: "POST",
42+
body: JSON.stringify({ prompt }),
43+
},
44+
);
4345

4446
if (!response.ok) {
4547
const error = await response.text();
46-
throw new Error(error);
48+
toast.error(`Error: ${error}`);
49+
return;
50+
}
51+
const stream = response.body;
52+
if (!stream) {
53+
toast.error("Error: No response from server.");
54+
return;
55+
}
56+
const reader = stream.getReader();
57+
const decoder = new TextDecoder("utf-8");
58+
let done = false;
59+
let result: any;
60+
61+
while (!done) {
62+
const { done: readerDone, value } = await reader.read();
63+
done = readerDone;
64+
if (value) {
65+
const chunk = decoder.decode(value, { stream: !done });
66+
const parsedChunks = parseJsonChunk(chunk);
67+
68+
const latestChunkValue = parsedChunks[parsedChunks.length - 1];
69+
70+
result = latestChunkValue;
71+
72+
if (onChunkUpdate) {
73+
onChunkUpdate(latestChunkValue);
74+
}
75+
}
4776
}
4877

49-
const llmResult = await response.text();
50-
51-
console.log("LLM result: ", llmResult);
52-
53-
const returns = await extractReturns(llmResult);
54-
55-
console.log("Agent result: ", returns);
56-
57-
return returns;
78+
return result;
5879
}
5980

6081
export async function runAgentMethodLocal(
@@ -76,7 +97,7 @@ export async function runAgentMethodLocal(
7697
throw new Error("LLM not found.");
7798
}
7899

79-
const prompt = await getPrompt(agent, method, args);
100+
const prompt = await getAgentPrompt(agent, method, args);
80101

81102
console.log("Prompt: ", prompt);
82103

@@ -112,11 +133,19 @@ function getLLM(llmConfig: LLMConfig, agentName: string, apiKey: string) {
112133
return llm;
113134
}
114135

115-
async function getPrompt(
136+
export async function getAgentPrompt(
116137
agent: Agent,
117-
method: AgentMethod,
138+
method: AgentMethod | string,
118139
args: Record<string, any>,
119140
) {
141+
if (typeof method === "string") {
142+
const foundMethod = agent.availableMethods.find((m) => m.name === method);
143+
if (!foundMethod) {
144+
throw new Error(`Method ${method} not found in agent ${agent.name}.`);
145+
}
146+
method = foundMethod;
147+
}
148+
120149
const userPromptTemplate = `\
121150
${method.prompt}
122151

web/lib/agent/built-in-agents/editor-assistant.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ Opened views:
223223
\`\`\`
224224
Available commands:
225225
\`\`\`
226-
{commands}
226+
{availableCommands}
227227
\`\`\`
228228
Project directory tree:
229229
\`\`\`
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
export function parseJsonChunk(chunk: string) {
2+
const jsonObjects = [];
3+
// Replace multiple spaces or other delimiters with a single space and trim
4+
const cleanedChunk = chunk.trim();
5+
6+
// Use a regular expression to match JSON objects
7+
// This assumes objects are separated by spaces and are valid JSON
8+
const jsonRegex = /({[^{}]*})/g;
9+
const matches = cleanedChunk.match(jsonRegex);
10+
11+
if (matches) {
12+
for (const match of matches) {
13+
try {
14+
const parsed = JSON.parse(match);
15+
jsonObjects.push(parsed);
16+
} catch (error) {
17+
console.error("Error parsing JSON object:", match, error);
18+
}
19+
}
20+
}
21+
22+
return jsonObjects;
23+
}

web/lib/hooks/use-commands.ts

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,39 +16,7 @@ import { EditorContext } from "@/components/providers/editor-context-provider";
1616
import { useTabViewManager } from "./use-tab-view-manager";
1717
import toast from "react-hot-toast";
1818
import { v4 } from "uuid";
19-
20-
// Editor built-in commands
21-
const editorCommands: CommandDefinition[] = [
22-
{
23-
info: {
24-
name: "New Canvas",
25-
description: "Create a new blank canvas",
26-
parameters: {},
27-
},
28-
handler: async (args: any) => {
29-
// Implementation of creating a new canvas
30-
console.log("Creating a new canvas with args:", args);
31-
return { success: true };
32-
},
33-
},
34-
{
35-
info: {
36-
name: "Open File",
37-
description: "Open a file in a new tab",
38-
parameters: {
39-
filePath: {
40-
type: "string",
41-
description: "Path to the file to open",
42-
},
43-
},
44-
},
45-
handler: async (args: any) => {
46-
// Implementation of opening a file
47-
console.log("Opening file with args:", args);
48-
return { success: true };
49-
},
50-
},
51-
];
19+
import { useMenuActions } from "./use-menu-actions";
5220

5321
/**
5422
* Use commands in active tab.
@@ -65,11 +33,45 @@ export default function useCommands() {
6533
createAppViewInCanvasView,
6634
findAppInTabView,
6735
} = useTabViewManager();
36+
const { menuActions } = useMenuActions();
6837

6938
const [commands, setCommands] = useState<Command[]>([]);
7039

7140
const [keyword, setKeyword] = useState<string | undefined>(undefined);
7241

42+
// Editor built-in commands
43+
const editorCommands: CommandDefinition[] = [
44+
{
45+
info: {
46+
name: "New Workflow",
47+
description: "Create a new blank Workflow",
48+
parameters: {},
49+
},
50+
handler: async (args: any) => {
51+
// Implementation of creating a new workflow
52+
menuActions?.find((a) => a.name === "New Workflow")?.actionFunc();
53+
return { success: true };
54+
}
55+
},
56+
{
57+
info: {
58+
name: "Open File",
59+
description: "Open a file in a new tab",
60+
parameters: {
61+
filePath: {
62+
type: "string",
63+
description: "Path to the file to open",
64+
},
65+
},
66+
},
67+
handler: async (args: any) => {
68+
// Implementation of opening a file
69+
console.log("Opening file with args:", args);
70+
return { success: true };
71+
},
72+
},
73+
];
74+
7375
// Update editor commands
7476
useEffect(() => {
7577
const newEditorCommands = editorCommands.map((cmd) => ({
@@ -235,7 +237,6 @@ export default function useCommands() {
235237
setKeyword(newKeyword);
236238
}
237239

238-
239240
return {
240241
runCommand,
241242
commands,

0 commit comments

Comments
 (0)