Skip to content

Commit

Permalink
feat: add debug log + refactor to use MST to handle AI + transcript m…
Browse files Browse the repository at this point in the history
…odels
  • Loading branch information
lublagg committed Nov 27, 2024
1 parent 1d2a437 commit e4b27c1
Show file tree
Hide file tree
Showing 13 changed files with 348 additions and 146 deletions.
90 changes: 73 additions & 17 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@
"@anthropic-ai/sdk": "^0.32.1",
"@types/dom-speech-recognition": "^0.0.4",
"dotenv": "^16.4.5",
"mobx-react-lite": "^4.0.7",
"mobx-state-tree": "^7.0.0",
"openai": "^4.72.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
13 changes: 13 additions & 0 deletions src/components/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,17 @@
position: absolute;
width: 1px;
}

.show-debug-controls {
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 5px;
font-size: .75em;
margin-top: 10px;
input:hover {
cursor: pointer;
}
}
}
148 changes: 39 additions & 109 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import React, { useEffect, useRef, useState } from "react";
import { initializePlugin, getAttributeList, getDataContext, IResult, selectSelf, getListOfDataContexts } from "@concord-consortium/codap-plugin-api";
import { TextContentBlock } from "openai/resources/beta/threads/messages";
import React, { useEffect, useState } from "react";
import { observer } from "mobx-react-lite";
import { initializePlugin, selectSelf } from "@concord-consortium/codap-plugin-api";
import { assistantStore } from "../models/assistant-model";
import { transcriptStore } from "../models/chat-transcript-model";
import { ChatInputComponent } from "./chat-input";
import { ChatTranscriptComponent } from "./chat-transcript";
import { ChatTranscript } from "../types";
import { timeStamp } from "../utils/utils";
import { getTools, initLlmConnection } from "../utils/llm-utils";
import { createGraph } from "../utils/codap-utils";
import { ReadAloudMenu } from "./readaloud-menu";
import { KeyboardShortcutControls } from "./keyboard-shortcut-controls";

Expand All @@ -19,46 +17,26 @@ const kInitialDimensions = {
height: 680
};

export const App = () => {
const greeting = "Hello! I'm DAVAI, your Data Analysis through Voice and Artificial Intelligence partner.";
const thread = useRef<any>();
const [assistant, setAssistant] = useState<any>(null);
const [chatTranscript, setChatTranscript] = useState<ChatTranscript>({messages: [{speaker: "DAVAI", content: greeting, timestamp: timeStamp()}]});
export const App = observer(() => {
const [readAloudEnabled, setReadAloudEnabled] = useState(false);
const [playbackSpeed, setPlaybackSpeed] = useState(1);
const isShortcutEnabled = JSON.parse(localStorage.getItem("keyboardShortcutEnabled") || "true");
const [keyboardShortcutEnabled, setKeyboardShortcutEnabled] = useState(isShortcutEnabled);
const shortcutKeys = localStorage.getItem("keyboardShortcutKeys") || "ctrl+?";
const [keyboardShortcutKeys, setKeyboardShortcutKeys] = useState(shortcutKeys);
const url = new URL(window.location.href);
const params = new URLSearchParams(url.search);
const hasDebugParams = params.has("debug");
const [showDebugLog, setShowDebugLog] = useState(hasDebugParams);

const davai = initLlmConnection();

useEffect(() => {
initializePlugin({pluginName: kPluginName, version: kVersion, dimensions: kInitialDimensions});
selectSelf();
assistantStore.initialize();
}, []);

useEffect(() => {
const initAssistant = async () => {
const assistantInstructions = "You are DAVAI, an Data Analysis through Voice and Artificial Intelligence partner. You are an intermediary for a user who is blind who wants to interact with data tables in a data analysis app named CODAP. ";
const tools = getTools();

const newAssistant = await davai.beta.assistants.create({
instructions: assistantInstructions,
model: "gpt-4o-mini",
tools
});

setAssistant(newAssistant);

thread.current = await davai.beta.threads.create();
};

initAssistant();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

if (!assistant) {
if (!assistantStore.assistant) {
return <div>Loading...</div>;
}

Expand All @@ -85,79 +63,8 @@ export const App = () => {
};

const handleChatInputSubmit = async (messageText: string) => {
setChatTranscript(prevTranscript => ({
messages: [...prevTranscript.messages, { speaker: "User", content: messageText, timestamp: timeStamp() }]
}));

// Add the message to the thread
await davai.beta.threads.messages.create(thread.current.id, {
role: "user",
content: messageText,
});

// Run the assistant
const run = await davai.beta.threads.runs.create(thread.current.id, { assistant_id: assistant.id });
let runState = await davai.beta.threads.runs.retrieve(thread.current.id, run.id);

// Wait for the run to complete
while (runState.status !== "completed" && runState.status !== "requires_action") {
runState = await davai.beta.threads.runs.retrieve(thread.current.id, run.id);
}

// If an action is required, run it and send the result to the assistant
if (runState.status === "requires_action") {
const dataContextList = await getListOfDataContexts();
const dataContext = await getDataContext(dataContextList.values[0].name);
const rootCollection = dataContext.values.collections[0];
let attributeList: IResult = { values: [], success: false };
const toolOutputs = runState.required_action?.submit_tool_outputs.tool_calls
? await Promise.all(
runState.required_action.submit_tool_outputs.tool_calls.map(async (toolCall: any) => {
if (toolCall.function.name === "get_attributes") {
attributeList = await getAttributeList(dataContext.values.name, rootCollection.name);
return { tool_call_id: toolCall.id, output: JSON.stringify(attributeList) };
} else {
const { name, xAttribute, yAttribute } = JSON.parse(toolCall.function.arguments);
createGraph(dataContext, name, xAttribute, yAttribute);
return { tool_call_id: toolCall.id, output: "Graph created." };
}
})
)
: [];

if (toolOutputs) {
davai.beta.threads.runs.submitToolOutputsStream(
thread.current.id, run.id, { tool_outputs: toolOutputs }
);

const threadMessageList = await davai.beta.threads.messages.list(thread.current.id);
const threadMessages = threadMessageList.data.map((msg: any) => ({
role: msg.role,
content: msg.content[0].text.value,
}));

await davai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
...threadMessages
],
});
}
}

// Get the last assistant message from the messages array
const messages = await davai.beta.threads.messages.list(thread.current.id);
const lastMessageForRun = messages.data.filter(
(msg) => msg.run_id === run.id && msg.role === "assistant"
).pop();

const davaiResponse = lastMessageForRun?.content[0]
? (lastMessageForRun.content[0] as TextContentBlock).text.value
: "There was an error processing your request.";

setChatTranscript(prevTranscript => ({
messages: [...prevTranscript.messages, { speaker: "DAVAI", content: davaiResponse, timestamp: timeStamp() }]
}));
transcriptStore.addMessage("User", messageText);
assistantStore.handleMessageSubmit(messageText);
};

return (
Expand All @@ -168,7 +75,30 @@ export const App = () => {
<span>(Data Analysis through Voice and Artificial Intelligence)</span>
</h1>
</header>
<ChatTranscriptComponent chatTranscript={chatTranscript} />
<ChatTranscriptComponent
chatTranscript={transcriptStore}
showDebugLog={showDebugLog}
/>
{hasDebugParams &&
<div className="show-debug-controls">
<label htmlFor="debug-log-toggle">
Show Debug Log:
</label>
<input
type="checkbox"
id="debug-log-toggle"
name="ShowDebugLog"
aria-checked={showDebugLog}
checked={showDebugLog}
onChange={() => setShowDebugLog(!showDebugLog)}
onKeyDown={(e) => {
if (e.key === "Enter") {
setShowDebugLog(!showDebugLog);
}
}}
/>
</div>
}
<ChatInputComponent
keyboardShortcutEnabled={keyboardShortcutEnabled}
shortcutKeys={keyboardShortcutKeys}
Expand All @@ -191,4 +121,4 @@ export const App = () => {
/>
</div>
);
};
});
16 changes: 13 additions & 3 deletions src/components/chat-transcript.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,14 @@
margin: 0;
padding: 8px 10px;

&:nth-child(odd) {
&.davai {
background: $light-teal-2;
}

&.debug {
background: lightyellow;
}

h3, li, p {
font-size: .75rem;
line-height: 17px;
Expand All @@ -34,14 +38,20 @@
font-size: .75rem;
}
.chat-message-content {
&.chat-message-content--user {
&.user {
white-space: pre-line; // maintain line breaks
}
&.chat-message-content--davai {
&.davai {
pre {
white-space: normal;
}
}
&.debug {
pre {
white-space: pre-wrap;
word-break: break-word;
}
}
}
}
}
Expand Down
Loading

0 comments on commit e4b27c1

Please sign in to comment.