diff --git a/README.md b/README.md
index 6819bcd..f33b1e6 100755
--- a/README.md
+++ b/README.md
@@ -69,14 +69,10 @@ Default configuration setting values are defined in the `app-config.json` file.
Settings related to accessibility in the UI:
- **`keyboardShortcut`** (string): Custom keystroke for placing focus in the main text input field (e.g., `ctrl+?`).
-### Assistant
-
-- **`assistant`** (Object)
- Settings to configure the AI assistant:
- - **`assistantId`** (string): The unique ID of an existing assistant to use.
- - **`instructions`** (string): Instructions to use when creating new assistants (e.g., `You are helpful data analysis partner.`).
- - **`modelName`** (string): The name of the model the assistant should use (e.g., `gpt-4o-mini`).
- - **`useExisting`** (boolean): Whether to use an existing assistant.
+### AssistantId
+
+- **`assistantId`** (string)
+ The unique ID of an existing assistant to use, or "mock" for a mocked assistant.
### Dimensions
diff --git a/cypress/e2e/workspace.test.ts b/cypress/e2e/workspace.test.ts
index 2124c68..105ec1e 100644
--- a/cypress/e2e/workspace.test.ts
+++ b/cypress/e2e/workspace.test.ts
@@ -1,6 +1,8 @@
context("Test the overall app", () => {
it("renders without crashing", () => {
cy.visit("/");
- cy.get("body").should("contain", "Loading...");
+ cy.get("body").should("contain", "DAVAI");
+ cy.get("[data-testid=chat-transcript]").should("exist");
+ cy.get("[data-testid=chat-input]").should("exist");
});
});
diff --git a/src/app-config.json b/src/app-config.json
index 2c22176..231c9ac 100644
--- a/src/app-config.json
+++ b/src/app-config.json
@@ -2,12 +2,7 @@
"accessibility": {
"keyboardShortcut": "ctrl+?"
},
- "assistant": {
- "assistantId": "asst_xmAX5oxByssXrkBymMbcsVEm",
- "instructions": "You are DAVAI, a 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.",
- "modelName": "gpt-4o-mini",
- "useExisting": true
- },
+ "assistantId": "asst_xmAX5oxByssXrkBymMbcsVEm",
"dimensions": {
"height": 680,
"width": 380
diff --git a/src/components/App.test.tsx b/src/components/App.test.tsx
index aa769f7..406d534 100755
--- a/src/components/App.test.tsx
+++ b/src/components/App.test.tsx
@@ -1,3 +1,4 @@
+import "openai/shims/node";
import React from "react";
import { render, screen } from "@testing-library/react";
import { App } from "./App";
@@ -6,7 +7,7 @@ import { MockAppConfigProvider } from "../test-utils/app-config-provider";
jest.mock("../hooks/use-assistant-store", () => ({
useAssistantStore: jest.fn(() => ({
- initialize: jest.fn(),
+ initializeAssistant: jest.fn(),
transcriptStore: {
messages: [],
addMessage: jest.fn(),
@@ -29,6 +30,8 @@ describe("test load app", () => {
);
- expect(screen.getByText("Loading...")).toBeDefined();
+ expect(screen.getByText("DAVAI")).toBeDefined();
+ expect(screen.getByTestId("chat-transcript")).toBeDefined();
+ expect(screen.getByTestId("chat-input")).toBeDefined();
});
});
diff --git a/src/components/App.tsx b/src/components/App.tsx
index e35f2d6..3e1af09 100755
--- a/src/components/App.tsx
+++ b/src/components/App.tsx
@@ -34,10 +34,13 @@ export const App = observer(() => {
useEffect(() => {
initializePlugin({pluginName: kPluginName, version: kVersion, dimensions});
selectSelf();
- assistantStore.initialize();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
+ useEffect(() => {
+ assistantStore.initializeAssistant();
+ }, [assistantStore, appConfig.assistantId]);
+
const handleFocusShortcut = () => {
selectSelf();
};
@@ -89,24 +92,23 @@ export const App = observer(() => {
return true;
};
- const handleMockAssistant = async () => {
- if (!appConfig.isAssistantMocked) {
- // If we switch to a mocked assistant, we delete the current thread and clear the transcript.
- // First make sure the user is OK with that.
- const threadDeleted = await handleDeleteThread();
- if (!threadDeleted) return;
+ const handleSelectAssistant = async (id: string) => {
+ // If we switch assistants, we delete the current thread and clear the transcript.
+ // First make sure the user is OK with that.
+ const threadDeleted = await handleDeleteThread();
+ if (!threadDeleted) return;
+ if (id === "mock") {
transcriptStore.clearTranscript();
transcriptStore.addMessage(DAVAI_SPEAKER, {content: GREETING});
- appConfig.toggleMockAssistant();
- } else {
- appConfig.toggleMockAssistant();
+ appConfig.setMockAssistant(true);
+ appConfig.setAssistantId(id);
+ return;
}
- };
- if (!assistantStore.assistant) {
- return
Loading...
;
- }
+ appConfig.setMockAssistant(false);
+ appConfig.setAssistantId(id);
+ };
return (
@@ -170,7 +172,7 @@ export const App = observer(() => {
assistantStore={assistantStore}
onCreateThread={handleCreateThread}
onDeleteThread={handleDeleteThread}
- onMockAssistant={handleMockAssistant}
+ onSelectAssistant={handleSelectAssistant}
/>
>
}
diff --git a/src/components/developer-options.scss b/src/components/developer-options.scss
index e841343..0b6932a 100644
--- a/src/components/developer-options.scss
+++ b/src/components/developer-options.scss
@@ -30,9 +30,14 @@
font-size: .75rem;
font-weight: normal;
line-height: 1.4;
- margin: 0 0 0 5px;
+ margin: 0 10px;
padding: 0;
user-select: none;
white-space: nowrap;
}
+
+ select {
+ margin: 0 10px 10px;
+ padding: 7px 10px;
+ }
}
\ No newline at end of file
diff --git a/src/components/developer-options.test.tsx b/src/components/developer-options.test.tsx
index 9c1d053..f45d55a 100644
--- a/src/components/developer-options.test.tsx
+++ b/src/components/developer-options.test.tsx
@@ -1,12 +1,13 @@
import "openai/shims/node";
import React from "react";
-import { fireEvent, render, screen } from "@testing-library/react";
+import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { DeveloperOptionsComponent } from "./developer-options";
import { AssistantModel } from "../models/assistant-model";
import { ChatTranscriptModel } from "../models/chat-transcript-model";
import { MockAppConfigProvider } from "../test-utils/app-config-provider";
import { mockAppConfig } from "../test-utils/mock-app-config";
+import { MockOpenAiConnectionProvider } from "../test-utils/openai-connection-provider";
const mockTranscriptStore = ChatTranscriptModel.create({
messages: [
@@ -20,13 +21,14 @@ const mockTranscriptStore = ChatTranscriptModel.create({
});
const mockAssistantStore = AssistantModel.create({
+ apiConnection: {
+ apiKey: "abc123",
+ dangerouslyAllowBrowser: true
+ },
assistant: {},
assistantId: "asst_abc123",
- instructions: "This is just a test",
- modelName: "test-model",
thread: {},
transcriptStore: mockTranscriptStore,
- useExistingAssistant: true,
});
jest.mock("../models/app-config-model", () => ({
@@ -39,35 +41,41 @@ jest.mock("../models/app-config-model", () => ({
describe("test developer options component", () => {
const onCreateThread = jest.fn();
const onDeleteThread = jest.fn();
- const onMockAssistant = jest.fn();
+ const onSelectAssistant = jest.fn();
const WrapperComponent = () => {
return (
-
+
+
+
);
};
- it("renders a developer options component with mock assistant checkbox and thread buttons", () => {
+ it("renders a developer options component with mock assistant checkbox and thread buttons", async () => {
render(
);
const developerOptions = screen.getByTestId("developer-options");
expect(developerOptions).toBeInTheDocument();
- const mockAssistantCheckbox = screen.getByTestId("mock-assistant-checkbox");
- expect(mockAssistantCheckbox).toBeInTheDocument();
- expect(mockAssistantCheckbox).toHaveAttribute("type", "checkbox");
- expect(mockAssistantCheckbox).toHaveProperty("checked", false);
- const mockAssistantCheckboxLabel = screen.getByTestId("mock-assistant-checkbox-label");
- expect(mockAssistantCheckboxLabel).toHaveTextContent("Use Mock Assistant");
- fireEvent.click(mockAssistantCheckbox);
- expect(onMockAssistant).toHaveBeenCalledTimes(1);
+ const selectAssistantOptionLabel = screen.getByTestId("assistant-select-label");
+ expect(selectAssistantOptionLabel).toHaveTextContent("Select an Assistant");
+ const selectAssistantOption = screen.getByTestId("assistant-select");
+ expect(selectAssistantOption).toBeInTheDocument();
+ await waitFor(() => {
+ expect(selectAssistantOption).toHaveValue("asst_abc123");
+ });
+ await waitFor(() => {
+ expect(selectAssistantOption).toHaveTextContent("Jest Mock Assistant");
+ });
+ fireEvent.change(selectAssistantOption, { target: { value: "mock" } });
+ expect(onSelectAssistant).toHaveBeenCalledTimes(1);
const deleteThreadButton = screen.getByTestId("delete-thread-button");
expect(deleteThreadButton).toBeInTheDocument();
diff --git a/src/components/developer-options.tsx b/src/components/developer-options.tsx
index 67c4fe6..d7fac1f 100644
--- a/src/components/developer-options.tsx
+++ b/src/components/developer-options.tsx
@@ -1,7 +1,9 @@
-import React from "react";
+import React, { useEffect, useState } from "react";
+import { OpenAI } from "openai";
import { observer } from "mobx-react-lite";
import { AssistantModelType } from "../models/assistant-model";
import { useAppConfigContext } from "../hooks/use-app-config-context";
+import { useOpenAIContext } from "../hooks/use-openai-context";
import "./developer-options.scss";
@@ -9,33 +11,70 @@ interface IProps {
assistantStore: AssistantModelType;
onCreateThread: () => void;
onDeleteThread: () => void;
- onMockAssistant: () => void;
+ onSelectAssistant: (id: string) => void;
}
-export const DeveloperOptionsComponent = observer(function DeveloperOptions({assistantStore, onCreateThread, onDeleteThread, onMockAssistant}: IProps) {
+export const DeveloperOptionsComponent = observer(function DeveloperOptions({assistantStore, onCreateThread, onDeleteThread, onSelectAssistant}: IProps) {
const appConfig = useAppConfigContext();
+ const apiConnection = useOpenAIContext();
+ const selectedAssistant = assistantStore.assistantId ? assistantStore.assistantId : "mock";
+ const [assistantOptions, setAssistantOptions] = useState
>();
+
+ useEffect(() => {
+ const fetchAssistants = async () => {
+ try {
+ const res = await apiConnection.beta.assistants.list();
+ const assistants = new Map();
+ res.data.map((assistant: OpenAI.Beta.Assistant) => {
+ const assistantName = assistant.name || assistant.id;
+ assistants.set(assistant.id, assistantName);
+ });
+ setAssistantOptions(assistants);
+ } catch (err) {
+ console.error(err);
+ }
+ };
+
+ fetchAssistants();
+ }, [apiConnection.beta.assistants]);
+
+ const handleSelectAssistant = (e: React.ChangeEvent) => {
+ const id = e.target.value;
+ onSelectAssistant(id);
+ };
+
return (
-
-
- Use Mock Assistant
+
+ Select an Assistant
+
+ Mock Assistant
+ {Array.from(assistantOptions?.entries() || []).map(([assistantId, assistantName]) => (
+
+ {assistantName}
+
+ ))}
+
Delete Thread
New Thread
diff --git a/src/constants.ts b/src/constants.ts
index ecfae24..9a6e8a6 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -3,3 +3,4 @@ export const DAVAI_SPEAKER = "DAVAI";
export const USER_SPEAKER = "User";
export const GREETING = `Hello! I'm DAVAI, your Data Analysis through Voice and Artificial Intelligence partner.`;
+
diff --git a/src/app-config-context.ts b/src/contexts/app-config-context.ts
similarity index 66%
rename from src/app-config-context.ts
rename to src/contexts/app-config-context.ts
index 58a185a..7b81cf0 100644
--- a/src/app-config-context.ts
+++ b/src/contexts/app-config-context.ts
@@ -1,4 +1,4 @@
import { createContext } from "react";
-import { AppConfigModelType } from "./models/app-config-model";
+import { AppConfigModelType } from "../models/app-config-model";
export const AppConfigContext = createContext(undefined);
diff --git a/src/app-config-provider.tsx b/src/contexts/app-config-provider.tsx
similarity index 57%
rename from src/app-config-provider.tsx
rename to src/contexts/app-config-provider.tsx
index b6b10d2..7eb0424 100644
--- a/src/app-config-provider.tsx
+++ b/src/contexts/app-config-provider.tsx
@@ -1,15 +1,17 @@
import React from "react";
-import { AppConfig, isAppMode } from "./types";
-import appConfigJson from "./app-config.json";
-import { AppConfigModel, AppConfigModelSnapshot } from "./models/app-config-model";
-import { getUrlParam } from "./utils/utils";
+import { AppConfig, isAppMode } from "../types";
+import appConfigJson from "../app-config.json";
+import { AppConfigModel, AppConfigModelSnapshot } from "../models/app-config-model";
+import { getUrlParam } from "../utils/utils";
import { AppConfigContext } from "./app-config-context";
export const loadAppConfig = (): AppConfig => {
const defaultConfig = appConfigJson as AppConfig;
const urlParamMode = getUrlParam("mode");
+ const assistantId = getUrlParam("assistantId");
const configOverrides: Partial = {
- mode: isAppMode(urlParamMode) ? urlParamMode : defaultConfig.mode
+ mode: isAppMode(urlParamMode) ? urlParamMode : defaultConfig.mode,
+ assistantId: assistantId || defaultConfig.assistantId,
};
return {
@@ -21,5 +23,9 @@ export const loadAppConfig = (): AppConfig => {
export const AppConfigProvider = ({ children }: { children: React.ReactNode }) => {
const appConfigSnapshot = loadAppConfig() as AppConfigModelSnapshot;
const appConfig = AppConfigModel.create(appConfigSnapshot);
- return {children} ;
+ return (
+
+ {children}
+
+ );
};
diff --git a/src/contexts/openai-connection-provider.tsx b/src/contexts/openai-connection-provider.tsx
new file mode 100644
index 0000000..6066b69
--- /dev/null
+++ b/src/contexts/openai-connection-provider.tsx
@@ -0,0 +1,23 @@
+import React, { createContext } from "react";
+import { OpenAI } from "openai";
+
+export const createNewConnection = () => {
+ return new OpenAI({
+ apiKey: process.env.REACT_APP_OPENAI_API_KEY || "fake-key",
+ baseURL: process.env.REACT_APP_OPENAI_BASE_URL,
+ dangerouslyAllowBrowser: true,
+ organization: "org-jbU1egKECzYlQI73HMMi7EOZ",
+ project: "proj_VsykADfoZHvqcOJUHyVAYoDG",
+ });
+};
+
+export const OpenAIConnectionContext = createContext(undefined);
+
+export const OpenAIConnectionProvider = ({ children }: {children: React.ReactNode}) => {
+ const apiConnection = createNewConnection();
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/hooks/use-app-config-context.ts b/src/hooks/use-app-config-context.ts
index cac532d..4a2b39e 100644
--- a/src/hooks/use-app-config-context.ts
+++ b/src/hooks/use-app-config-context.ts
@@ -1,6 +1,6 @@
import { useContext } from "react";
import { AppConfigModelType } from "../models/app-config-model";
-import { AppConfigContext } from "../app-config-context";
+import { AppConfigContext } from "../contexts/app-config-context";
export const useAppConfigContext = (): AppConfigModelType => {
const context = useContext(AppConfigContext);
diff --git a/src/hooks/use-assistant-store.ts b/src/hooks/use-assistant-store.ts
index eb4b94c..1dc043f 100644
--- a/src/hooks/use-assistant-store.ts
+++ b/src/hooks/use-assistant-store.ts
@@ -1,21 +1,33 @@
import { useMemo } from "react";
-import { useAppConfigContext } from "./use-app-config-context";
import { AssistantModel } from "../models/assistant-model";
-import { useChatTranscriptStore } from "./use-chat-transcript-store";
+import { useOpenAIContext } from "./use-openai-context";
+import { useAppConfigContext } from "./use-app-config-context";
+import { ChatTranscriptModel } from "../models/chat-transcript-model";
+import { DAVAI_SPEAKER, GREETING } from "../constants";
+import { timeStamp } from "../utils/utils";
export const useAssistantStore = () => {
+ const apiConnection = useOpenAIContext();
const appConfig = useAppConfigContext();
- const transcriptStore = useChatTranscriptStore();
- const { assistantId, instructions, modelName, useExisting } = appConfig.assistant;
+ const assistantId = appConfig.assistantId;
const assistantStore = useMemo(() => {
+ const newTranscriptStore = ChatTranscriptModel.create({
+ messages: [
+ {
+ speaker: DAVAI_SPEAKER,
+ messageContent: { content: GREETING },
+ timestamp: timeStamp(),
+ id: "initial-message",
+ },
+ ],
+ });
+
return AssistantModel.create({
+ apiConnection,
assistantId,
- modelName,
- instructions,
- transcriptStore,
- useExisting,
+ transcriptStore: newTranscriptStore
});
- }, [assistantId, instructions, modelName, transcriptStore, useExisting]);
+ }, [apiConnection, assistantId]);
return assistantStore;
};
diff --git a/src/hooks/use-openai-context.ts b/src/hooks/use-openai-context.ts
new file mode 100644
index 0000000..68608bb
--- /dev/null
+++ b/src/hooks/use-openai-context.ts
@@ -0,0 +1,11 @@
+import { useContext } from "react";
+import { OpenAI } from "openai";
+import { OpenAIConnectionContext } from "../contexts/openai-connection-provider";
+
+export const useOpenAIContext = (): OpenAI => {
+ const context = useContext(OpenAIConnectionContext);
+ if (!context) {
+ throw new Error("useOpenAIContext must be used within a OpenAIConnectionContext.Provider");
+ }
+ return context;
+};
diff --git a/src/index.tsx b/src/index.tsx
index 67b2594..a2f9e4c 100755
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,7 +1,8 @@
import React from "react";
import { createRoot } from "react-dom/client";
import { App } from "./components/App";
-import { AppConfigProvider } from "./app-config-provider";
+import { AppConfigProvider } from "./contexts/app-config-provider";
+import { OpenAIConnectionProvider } from "./contexts/openai-connection-provider";
import "./index.scss";
@@ -15,7 +16,9 @@ if (container) {
root.render(
-
+
+
+
);
}
diff --git a/src/models/app-config-model.ts b/src/models/app-config-model.ts
index be3a81e..89b90ae 100644
--- a/src/models/app-config-model.ts
+++ b/src/models/app-config-model.ts
@@ -4,49 +4,39 @@ import { AppMode, AppModeValues } from "../types";
/**
* AppConfigModel encapsulates the application's configuration settings.
* It includes properties and methods for managing accessibility, AI assistant settings, and the application's mode.
- *
+ *
* @property {Object} accessibility - Settings related to accessibility in the UI.
* @property {string} accessibility.keyboardShortcut - Custom keystroke for placing focus in the main text input field (e.g., `ctrl+?`).
- *
- * @property {Object} assistant - Settings to configure the AI assistant.
- * @property {string} assistant.assistantId - The unique ID of an existing assistant to use.
- * @property {string} assistant.instructions - Instructions to use when creating new assistants (e.g., `You are helpful data analysis partner.`).
- * @property {string} assistant.modelName - The name of the model the assistant should use (e.g., `gpt-4o-mini`).
- * @property {boolean} assistant.useExisting - Whether to use an existing assistant.
- *
+ * @property {string} assistantId - The unique ID of an existing assistant to use, or "mock" for a mocked assistant.
* @property {Object} dimensions - Dimensions of the application's component within CODAP.
* @property {number} dimensions.width - The width of the application (in pixels).
* @property {number} dimensions.height - The height of the application (in pixels).
- *
+ *
* @property {boolean|null} mockAssistant - A flag indicating whether to mock AI interactions. (optional).
- *
+ *
* @property {"development"|"production"|"test"} mode - The mode in which the application runs.
*/
export const AppConfigModel = types.model("AppConfigModel", {
accessibility: types.model({
keyboardShortcut: types.string,
}),
- assistant: types.model({
- assistantId: types.string,
- instructions: types.string,
- modelName: types.string,
- useExisting: types.boolean,
- }),
+ assistantId: types.string,
dimensions: types.model({
width: types.number,
height: types.number,
}),
- mockAssistant: types.maybe(types.boolean),
mode: types.enumeration("Mode", AppModeValues),
})
.volatile((self) => ({
- isAssistantMocked: self.mode === "development" && self.mockAssistant,
+ isAssistantMocked: self.assistantId === "mock",
}))
.actions((self) => ({
- toggleMockAssistant() {
- self.mockAssistant = !self.mockAssistant;
- self.isAssistantMocked = self.mode === "development" && self.mockAssistant;
+ setAssistantId(assistantId: string) {
+ self.assistantId = assistantId;
},
+ setMockAssistant(mockAssistant: boolean) {
+ self.isAssistantMocked = self.mode === "development" && mockAssistant;
+ }
}));
export interface AppConfigModelSnapshot extends SnapshotIn {}
diff --git a/src/models/assistant-model.ts b/src/models/assistant-model.ts
index ea7cfd7..b377ab5 100644
--- a/src/models/assistant-model.ts
+++ b/src/models/assistant-model.ts
@@ -3,9 +3,28 @@ import { Message } from "openai/resources/beta/threads/messages";
import { codapInterface } from "@concord-consortium/codap-plugin-api";
import { DAVAI_SPEAKER, DEBUG_SPEAKER } from "../constants";
import { formatJsonMessage } from "../utils/utils";
-import { getTools, initLlmConnection } from "../utils/llm-utils";
-import { ChatTranscriptModel } from "./chat-transcript-model";
import { requestThreadDeletion } from "../utils/openai-utils";
+import { ChatTranscriptModel } from "./chat-transcript-model";
+import { OpenAI } from "openai";
+
+const OpenAIType = types.custom({
+ name: "OpenAIType",
+ fromSnapshot(snapshot: OpenAI) {
+ return new OpenAI({
+ apiKey: snapshot.apiKey,
+ dangerouslyAllowBrowser: true,
+ });
+ },
+ toSnapshot() {
+ return undefined; // OpenAI instance is non-serializable
+ },
+ isTargetType(value) {
+ return value instanceof OpenAI;
+ },
+ getValidationMessage() {
+ return "";
+ },
+});
/**
* AssistantModel encapsulates the AI assistant and its interactions with the user.
@@ -13,24 +32,18 @@ import { requestThreadDeletion } from "../utils/openai-utils";
* thread and transcript.
*
* @property {Object|null} assistant - The assistant object, or `null` if not initialized.
- * @property {string} assistantId - The unique ID of the assistant being used.
- * @property {string} instructions - Instructions provided when creating or configuring a new assistant.
- * @property {string} modelName - The identifier for the assistant's model (e.g., "gpt-4o-mini").
- * @property {Object|null} apiConnection - The API connection object for interacting with the assistant, or `null` if not connected.
+ * @property {string} assistantId - The unique ID of the assistant being used, or `null` if not initialized.
+ * @property {Object} apiConnection - The API connection object for interacting with the assistant
* @property {Object|null} thread - The assistant's thread used for the current chat, or `null` if no thread is active.
* @property {ChatTranscriptModel} transcriptStore - The assistant's chat transcript store for recording and managing chat messages.
- * @property {boolean} useExisting - A flag indicating whether to use an existing assistant (`true`) or create a new one (`false`).
*/
export const AssistantModel = types
.model("AssistantModel", {
+ apiConnection: OpenAIType,
assistant: types.maybe(types.frozen()),
assistantId: types.string,
- instructions: types.string,
- modelName: types.string,
- apiConnection: types.maybe(types.frozen()),
thread: types.maybe(types.frozen()),
transcriptStore: ChatTranscriptModel,
- useExisting: true,
})
.volatile(() => ({
isLoadingResponse: false,
@@ -44,26 +57,18 @@ export const AssistantModel = types
{ content: "I'm just a mock assistant and can't process that request." }
);
}, 1000);
- }
- }))
- .actions((self) => ({
- afterCreate(){
- self.apiConnection = initLlmConnection();
+ },
+ setTranscriptStore(transcriptStore: any) {
+ self.transcriptStore = transcriptStore;
}
}))
.actions((self) => {
- const initialize = flow(function* () {
- try {
- const tools = getTools();
-
- const davaiAssistant = self.useExisting && self.assistantId
- ? yield self.apiConnection.beta.assistants.retrieve(self.assistantId)
- : yield self.apiConnection.beta.assistants.create({instructions: self.instructions, model: self.modelName, tools });
+ const initializeAssistant = flow(function* () {
+ if (self.assistantId === "mock") return;
- if (!self.useExisting) {
- self.assistantId = davaiAssistant.id;
- }
- self.assistant = davaiAssistant;
+ try {
+ if (!self.apiConnection) throw new Error("API connection is not initialized");
+ self.assistant = yield self.apiConnection.beta.assistants.retrieve(self.assistantId);
self.thread = yield self.apiConnection.beta.threads.create();
self.transcriptStore.addMessage(DEBUG_SPEAKER, {
description: "You are chatting with assistant",
@@ -235,7 +240,7 @@ export const AssistantModel = types
}
});
- return { createThread, deleteThread, initialize, handleMessageSubmit };
+ return { createThread, deleteThread, initializeAssistant, handleMessageSubmit };
});
export interface AssistantModelType extends Instance {}
diff --git a/src/test-utils/app-config-provider.tsx b/src/test-utils/app-config-provider.tsx
index 8603949..988b0d7 100644
--- a/src/test-utils/app-config-provider.tsx
+++ b/src/test-utils/app-config-provider.tsx
@@ -1,5 +1,5 @@
import React from "react";
-import { AppConfigContext } from "../app-config-context";
+import { AppConfigContext } from "../contexts/app-config-context";
import { AppConfigModel } from "../models/app-config-model";
import { mockAppConfig } from "./mock-app-config";
diff --git a/src/test-utils/mock-app-config.ts b/src/test-utils/mock-app-config.ts
index dfaef1f..939a116 100644
--- a/src/test-utils/mock-app-config.ts
+++ b/src/test-utils/mock-app-config.ts
@@ -4,12 +4,7 @@ export const mockAppConfig = {
accessibility: {
keyboardShortcut: "ctrl+?"
},
- assistant: {
- assistantId: "asst_abc123",
- instructions: "You are just a test AI. Don't do anything fancy.",
- model: "test-model",
- useExisting: true
- },
+ assistantId: "asst_abc123",
dimensions: {
height: 680,
width: 380
diff --git a/src/test-utils/openai-connection-provider.tsx b/src/test-utils/openai-connection-provider.tsx
new file mode 100644
index 0000000..5d65bb2
--- /dev/null
+++ b/src/test-utils/openai-connection-provider.tsx
@@ -0,0 +1,38 @@
+import React from "react";
+import { OpenAIConnectionContext } from "../contexts/openai-connection-provider";
+import { OpenAI } from "openai";
+
+const assistant: Partial = {
+ id: "asst_abc123",
+ name: "Jest Mock Assistant",
+};
+const listAssistants: Partial = jest.fn(() => {
+ return { data: [assistant]};
+});
+
+const assistants: Partial = {
+ create: jest.fn(),
+ del: jest.fn(),
+ list: listAssistants as OpenAI.Beta.Assistants["list"],
+ retrieve: jest.fn(),
+ update: jest.fn()
+};
+
+const beta: Partial = {
+ assistants: assistants as OpenAI.Beta.Assistants,
+};
+
+const mockOpenAiConnection: Partial = {
+ apiKey: "mock-api-key",
+ beta: beta as OpenAI.Beta,
+ organization: "mock-organization",
+ project: "mock-project"
+};
+
+export const MockOpenAiConnectionProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/types.ts b/src/types.ts
index 86ffcaf..72bc1b9 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -8,12 +8,7 @@ export type AppConfig = {
accessibility: {
keyboardShortcut: string;
};
- assistant: {
- assistantId: string;
- instructions: string;
- modelName: string;
- useExisting: boolean;
- };
+ assistantId: string;
dimensions: {
height: number;
width: number;
diff --git a/src/utils/llm-utils.ts b/src/utils/llm-utils.ts
deleted file mode 100644
index fabc2fa..0000000
--- a/src/utils/llm-utils.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { newOpenAI, openAiTools } from "./openai-utils";
-
-export const initLlmConnection = () => {
- return newOpenAI();
-};
-
-export const getTools = () => {
- return openAiTools;
-};
diff --git a/src/utils/openai-utils.ts b/src/utils/openai-utils.ts
index f2714fb..7803b7f 100644
--- a/src/utils/openai-utils.ts
+++ b/src/utils/openai-utils.ts
@@ -1,16 +1,5 @@
-import { OpenAI } from "openai";
import { AssistantTool } from "openai/resources/beta/assistants";
-export const newOpenAI = () => {
- return new OpenAI({
- apiKey: process.env.REACT_APP_OPENAI_API_KEY || "fake-key",
- baseURL: process.env.REACT_APP_OPENAI_BASE_URL,
- dangerouslyAllowBrowser: true,
- organization: "org-jbU1egKECzYlQI73HMMi7EOZ",
- project: "proj_VsykADfoZHvqcOJUHyVAYoDG",
- });
-};
-
export const openAiTools: AssistantTool[] = [
{
type: "function",