-
Notifications
You must be signed in to change notification settings - Fork 116
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: paste assistant data handling and feature iteration (#3720)
* feat: paste assistant data handling and feature iteration * feat: improved data fetching and thread switching * feat: empty state * chore: cannot use server side props * chore: comments and tidy up * chore: lints * chore: missing argument
- Loading branch information
Showing
36 changed files
with
1,658 additions
and
349 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,21 @@ | ||
context("GET /api/og-image", () => { | ||
context("GET /api/component-og-image", () => { | ||
it("gets an image", () => { | ||
cy.request("GET", "/api/og-image?componentRequested=primitives/box").then((response) => { | ||
cy.request("GET", "/api/component-og-image?componentRequested=primitives/box").then((response) => { | ||
expect(response.status).to.eq(200); | ||
expect(response.headers["content-type"]).to.eq("image/png"); | ||
console.log(response); | ||
}); | ||
}); | ||
}); | ||
|
||
context("GET /api/simple-og-image", () => { | ||
it("gets an image", () => { | ||
cy.request("GET", "/api/simple-og-image?title=Hello%20World&description=This%20is%20a%20description").then( | ||
(response) => { | ||
expect(response.status).to.eq(200); | ||
expect(response.headers["content-type"]).to.eq("image/png"); | ||
console.log(response); | ||
}, | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import { type UseMutationResult, useMutation } from "@tanstack/react-query"; | ||
|
||
export const useCreateThreadMutation = (): UseMutationResult => { | ||
return useMutation({ | ||
mutationFn: async () => { | ||
return fetch("/api/paste-assistant-thread", { | ||
method: "POST", | ||
}); | ||
}, | ||
mutationKey: ["create-thread"], | ||
}); | ||
}; | ||
|
||
export const useDeleteThreadMutation = (): UseMutationResult => { | ||
return useMutation({ | ||
mutationFn: async (id) => { | ||
return fetch(`/api/paste-assistant-thread/${id}`, { | ||
method: "DELETE", | ||
}); | ||
}, | ||
mutationKey: ["delete-thread"], | ||
}); | ||
}; | ||
|
||
export const useUpdateThreadMutation = (): UseMutationResult => { | ||
return useMutation({ | ||
mutationFn: async ({ id, threadTitle }: any) => { | ||
return fetch(`/api/paste-assistant-thread/${id}`, { | ||
method: "PUT", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify({ metadata: { threadTitle } }), | ||
}); | ||
}, | ||
mutationKey: ["update-thread"], | ||
}); | ||
}; | ||
|
||
export const useCreateAssistantRunMutation = (): UseMutationResult => { | ||
return useMutation({ | ||
mutationFn: async (messageDetails) => { | ||
return fetch("/api/paste-assistant-message", { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify(messageDetails), | ||
}); | ||
}, | ||
mutationKey: ["create-assistant-run"], | ||
}); | ||
}; | ||
|
||
export const useSimpleCompletionMutation = (): UseMutationResult => { | ||
return useMutation({ | ||
mutationFn: async (completionDetails) => { | ||
return fetch("/api/paste-assistant-simple-completion/", { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify(completionDetails), | ||
}); | ||
}, | ||
mutationKey: ["simple-completion"], | ||
}); | ||
}; |
143 changes: 143 additions & 0 deletions
143
packages/paste-website/src/components/assistant/Assistant.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
/* eslint-disable camelcase */ | ||
import type { ThreadMessage } from "openai/resources/beta/threads/messages/messages"; | ||
import * as React from "react"; | ||
|
||
import { | ||
useCreateAssistantRunMutation, | ||
useCreateThreadMutation, | ||
useSimpleCompletionMutation, | ||
useUpdateThreadMutation, | ||
} from "../../api/assistantAPIs"; | ||
import { useAssistantMessagesStore } from "../../stores/assistantMessagesStore"; | ||
import { useAssistantRunStore } from "../../stores/assistantRunStore"; | ||
import { useAssistantThreadsStore } from "../../stores/assistantThreadsStore"; | ||
import useStoreWithLocalStorage from "../../stores/useStore"; | ||
import { AssistantCanvas } from "./AssistantCanvas"; | ||
import { AssistantComposer } from "./AssistantComposer"; | ||
import { AssistantEmptyState } from "./AssistantEmptyState"; | ||
import { AsssistantLayout } from "./AssistantLayout"; | ||
import { AssistantThreads } from "./AssistantThreads"; | ||
import { AssistantHeader } from "./AsststantHeader"; | ||
|
||
const getMockMessage = ({ message }: { message: string }): ThreadMessage => { | ||
const date = new Date(); | ||
|
||
return { | ||
id: "", | ||
object: "thread.message", | ||
created_at: Math.floor(date.getTime() / 1000), | ||
thread_id: "xxxx", | ||
role: "user", | ||
content: [ | ||
{ | ||
type: "text", | ||
text: { | ||
value: message, | ||
annotations: [], | ||
}, | ||
}, | ||
], | ||
file_ids: [], | ||
assistant_id: null, | ||
run_id: null, | ||
metadata: {}, | ||
}; | ||
}; | ||
|
||
export const Assistant: React.FC = () => { | ||
const threadsStore = useStoreWithLocalStorage(useAssistantThreadsStore, (state) => state); | ||
const createAssistantRun = useCreateAssistantRunMutation(); | ||
const createThreadMutation = useCreateThreadMutation(); | ||
const updateThreadMutation = useUpdateThreadMutation(); | ||
const simpleCompletionMutation = useSimpleCompletionMutation(); | ||
const setActiveRun = useAssistantRunStore((state) => state.setActiveRun); | ||
const addMessage = useAssistantMessagesStore((state) => state.addMessage); | ||
const messages = useAssistantMessagesStore((state) => state.messages); | ||
|
||
if (threadsStore == null) return null; | ||
|
||
const handleMessageCreation = (message: string, threadId: string): void => { | ||
// add the new user message to the store to optimistically render it whilst we wait for openAI to do its thing | ||
addMessage(getMockMessage({ message })); | ||
|
||
// Create a new "assistant run" on the thread so that openAI processes the new message and updates the thread with a response | ||
createAssistantRun.mutate( | ||
{ threadId, message }, | ||
{ | ||
onSuccess: async (run) => { | ||
// @ts-expect-error I don't know how to type this right now so it knows it's a response | ||
const newRun = await run.json(); | ||
setActiveRun(newRun.run); | ||
}, | ||
}, | ||
); | ||
|
||
if (messages.length === 0) { | ||
/** | ||
* summarise the first question as the title of the | ||
* thread, if this is the first message in that thread | ||
* for it to appear in the thread list as an identifier | ||
* of the thread | ||
*/ | ||
simpleCompletionMutation.mutate( | ||
{ | ||
prompt: | ||
"If this is the start of a threaded conversation, summarise the subject of the converation in as few words as possible: ", | ||
context: message, | ||
}, | ||
{ | ||
onSuccess: async (completion) => { | ||
// @ts-expect-error I don't know how to type this right now so it knows it's a response | ||
const newCompletion = await completion.json(); | ||
// update the thread title in the store | ||
threadsStore?.setThreadTitle(threadId, newCompletion.choices[0].message.content); | ||
// update the thread title in openAI | ||
updateThreadMutation.mutate({ id: threadId, threadTitle: newCompletion.choices[0].message.content }); | ||
}, | ||
}, | ||
); | ||
} | ||
}; | ||
|
||
/** | ||
* From one of the canned new thread messages, create a new thread, select it, and then | ||
* create a new message run on the newly created thread. | ||
* | ||
* @param {string} message | ||
*/ | ||
const handleCannedThreadCreation = (message: string): void => { | ||
createThreadMutation.mutate( | ||
{}, | ||
{ | ||
onSuccess: async (data) => { | ||
// @ts-expect-error I don't know how to type this right now so it knows it's a response | ||
const newThread = await data.json(); | ||
if (threadsStore == null) return; | ||
threadsStore.createAndSelectThread(newThread); | ||
handleMessageCreation(message, newThread.id); | ||
}, | ||
}, | ||
); | ||
}; | ||
|
||
return ( | ||
<AsssistantLayout.Window> | ||
<AsssistantLayout.Threads> | ||
<AsssistantLayout.ThreadsHeader> | ||
<AssistantHeader /> | ||
</AsssistantLayout.ThreadsHeader> | ||
<AssistantThreads /> | ||
</AsssistantLayout.Threads> | ||
<AsssistantLayout.Canvas> | ||
{threadsStore.selectedThreadID == null && ( | ||
<AssistantEmptyState onCannedThreadCreation={handleCannedThreadCreation} /> | ||
)} | ||
{threadsStore.selectedThreadID != null && <AssistantCanvas selectedThreadID={threadsStore.selectedThreadID} />} | ||
<AsssistantLayout.Composer> | ||
<AssistantComposer onMessageCreation={handleMessageCreation} /> | ||
</AsssistantLayout.Composer> | ||
</AsssistantLayout.Canvas> | ||
</AsssistantLayout.Window> | ||
); | ||
}; | ||
/* eslint-enable camelcase */ |
Oops, something went wrong.