Skip to content

Commit b246ea5

Browse files
committed
fix bugs in web sockets
1 parent ba33684 commit b246ea5

File tree

12 files changed

+115
-94
lines changed

12 files changed

+115
-94
lines changed

.prettierignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
*.json

.prettierrc.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"singleAttributePerLine": true
3+
}

components/PracticeExam.vue

+26-23
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,43 @@
22
const value = ref("");
33
44
async function handleSubmit() {
5-
const startChat = await $fetch("/api/tutor", {
6-
method: "POST",
7-
body: {
8-
message: value.value,
9-
},
10-
});
5+
const startChat = await $fetch("/api/tutor", {
6+
method: "POST",
7+
body: {
8+
message: value.value,
9+
},
10+
});
1111
12-
console.log(startChat);
13-
//
12+
console.log(startChat);
13+
//
1414
}
1515
1616
async function handleCreateThread() {
17-
await $fetch("/api/threads", {
18-
method: "POST",
19-
});
17+
await $fetch("/api/threads", {
18+
method: "POST",
19+
});
2020
}
2121
2222
async function handleShowThreads() {
23-
const threads = await $fetch("/api/threads", {
24-
method: "GET",
25-
});
23+
const threads = await $fetch("/api/threads", {
24+
method: "GET",
25+
});
2626
27-
console.log(threads);
28-
//
27+
console.log(threads);
28+
//
2929
}
3030
</script>
3131

3232
<template>
33-
<!-- -->
34-
<form @submit.prevent="handleSubmit">
35-
<UTextarea v-model="value" placeholder="Start chatting to AI..." />
36-
<UButton type="submit">Start!</UButton>
37-
</form>
33+
<!-- -->
34+
<form @submit.prevent="handleSubmit">
35+
<UTextarea
36+
v-model="value"
37+
placeholder="Start chatting to AI..."
38+
/>
39+
<UButton type="submit">Start!</UButton>
40+
</form>
3841

39-
<UButton @click="handleCreateThread">New Conversation</UButton>
40-
<UButton @click="handleShowThreads">List conversations</UButton>
42+
<UButton @click="handleCreateThread">New Conversation</UButton>
43+
<UButton @click="handleShowThreads">List conversations</UButton>
4144
</template>

composables/useWebSocket.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export interface TextChunkPayload {
2+
type: "text-chunk";
3+
text: string;
4+
}
5+
6+
export type Payload = TextChunkPayload;
7+
8+
/**
9+
* @description Create a web socket with listener
10+
**/
11+
export function useWebSocket(options: {
12+
onMessage: (payload: Payload) => void;
13+
}) {
14+
const isSecure = location.protocol === "https:";
15+
const url = (isSecure ? "wss://" : "ws://") + location.host + "/_ws";
16+
17+
console.log("Connecting to", url, "...");
18+
const ws = new WebSocket(url);
19+
20+
ws.addEventListener("open", () => {
21+
console.log("WebSocket opened");
22+
});
23+
24+
ws.addEventListener("close", () => {
25+
console.log("WebSocket closed");
26+
});
27+
28+
ws.addEventListener("message", (event: MessageEvent<any>) => {
29+
options.onMessage(JSON.parse(event.data));
30+
});
31+
32+
return ws;
33+
}

package-lock.json

+20
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"dev": "nuxt dev",
88
"generate": "nuxt generate",
99
"preview": "nuxt preview",
10-
"postinstall": "nuxt prepare"
10+
"postinstall": "nuxt prepare",
11+
"lint": "prettier --write ."
1112
},
1213
"dependencies": {
1314
"@nuxt/ui": "^2.16.0",
@@ -16,6 +17,7 @@
1617
"nuxt": "^3.11.2",
1718
"openai": "^4.47.2",
1819
"pinia": "^2.1.7",
20+
"prettier": "^3.2.5",
1921
"sqlite3": "^5.1.7",
2022
"vue": "^3.4.27",
2123
"vue-router": "^4.3.2"

pages/index.vue

+4-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ connect();
4444
<div class="flex flex-col items-center justify-center h-full">
4545
<h2>Welcome.</h2>
4646
<div>
47-
<UButton color="indigo" :loading="loading" @click="run"
47+
<UButton
48+
color="indigo"
49+
:loading="loading"
50+
@click="run"
4851
>Start Studying</UButton
4952
>
5053
</div>

pages/threads/[id].vue

+16-28
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script setup lang="ts">
22
import type { SerializeObject } from "nitropack";
33
import type { Message } from "openai/resources/beta/threads/messages";
4+
import type { Payload } from "~/composables/useWebSocket";
45
56
const route = useRoute();
67
@@ -32,39 +33,23 @@ async function handleRun() {
3233
ws.send(JSON.stringify({ threadId: id }));
3334
}
3435
35-
const conversation = computed(() => {
36-
return (data.value ?? []).slice().reverse();
37-
});
38-
3936
let ws: WebSocket;
4037
4138
const replies = ref("");
4239
43-
async function connectWebSocket() {
44-
const isSecure = location.protocol === "https:";
45-
const url = (isSecure ? "wss://" : "ws://") + location.host + "/_ws";
46-
if (ws) {
47-
console.log("Closing previous connection before reconnecting...");
48-
ws.close();
40+
function onMessage(payload: Payload) {
41+
if (payload.type === "text-chunk") {
42+
replies.value += payload.text;
4943
}
50-
51-
console.log("Connecting to", url, "...");
52-
ws = new WebSocket(url);
53-
54-
ws.addEventListener("open", () => {
55-
console.log("WebSocket opened");
56-
});
57-
58-
ws.addEventListener("message", (event) => {
59-
const data = JSON.parse(event.data);
60-
if (data.type === "text-chunk") {
61-
console.log(data.text);
62-
replies.value += data.text;
63-
}
64-
});
6544
}
6645
67-
connectWebSocket();
46+
onMounted(() => {
47+
ws = useWebSocket({ onMessage });
48+
});
49+
50+
onUnmounted(() => {
51+
ws!.close();
52+
});
6853
</script>
6954

7055
<template>
@@ -79,7 +64,7 @@ connectWebSocket();
7964
v-if="message.content[0]?.type === 'text'"
8065
class="p-1 rounded px-2"
8166
:class="{
82-
'bg-gray-200': message.role === 'user',
67+
'bg-gray-200 dark:bg-gray-700': message.role === 'user',
8368
'max-w-[50vw]': message.role === 'user',
8469
}"
8570
>
@@ -97,7 +82,10 @@ connectWebSocket();
9782
<UDivider class="p-4" />
9883

9984
<form @submit.prevent="handleSubmitMessage">
100-
<UTextarea v-model="msg" placeholder="Chat..." />
85+
<UTextarea
86+
v-model="msg"
87+
placeholder="Chat..."
88+
/>
10189
<UButton type="submit">Start!</UButton>
10290
</form>
10391

server/api/message.post.ts

-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,5 @@ export default defineEventHandler(async (event) => {
1616
content: body.message,
1717
});
1818

19-
console.log(message);
2019
return message;
2120
});

server/api/threads.get.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export default defineEventHandler(async (event) => {
88
threadsForUser.map(async (x) => {
99
const t = await openai.beta.threads.retrieve(x.openai_id);
1010
return { ...t, openai_id: t.id, id: (x.id as number).toString() };
11-
})
11+
}),
1212
);
1313
return all;
1414
// await db("threads").insert({ openai_id: emptyThread.id, user_id: 1 });

server/api/threads/[id].get.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ export default defineEventHandler(async (event) => {
1414
{
1515
limit: 5,
1616
order: "desc",
17-
}
17+
},
1818
);
1919

20-
return threadMessages.data;
20+
return threadMessages.data.reverse();
2121
});

server/routes/_ws.ts

+5-37
Original file line numberDiff line numberDiff line change
@@ -6,56 +6,31 @@ const assitantId = "asst_sxL8Gxy8meOwaf0vySOnegmu";
66
async function streamRun(
77
threadId: string,
88
assistantId: string,
9-
send: (chunk: string) => void
9+
send: (chunk: string) => void,
1010
) {
11-
console.log("Streaming");
1211
const stream = openai.beta.threads.runs.stream(threadId, {
1312
assistant_id: assistantId,
1413
});
1514

1615
for await (const chunk of stream) {
17-
// console.log("Event:", chunk.event);
1816
if (chunk.event === "thread.message.delta") {
1917
if (chunk.data.delta.content?.[0]?.type === "text") {
2018
send(chunk.data.delta.content[0].text?.value!);
2119
// process.stdout.write(chunk.data.delta.content[0].text?.value);
2220
}
21+
} else {
22+
console.log(`Used event: ${chunk.event}`);
2323
}
2424
}
2525
}
2626

27-
// .on("textCreated", (text) => process.stdout.write("\nassistant > "))
28-
// .on("textDelta", (textDelta, snapshot) =>
29-
// process.stdout.write(textDelta.value)
30-
// )
31-
// .on("toolCallCreated", (toolCall) =>
32-
// process.stdout.write(`\nassistant > ${toolCall.type}\n\n`)
33-
// )
34-
// .on("toolCallDelta", (toolCallDelta, snapshot) => {
35-
// if (toolCallDelta.type === "code_interpreter") {
36-
// if (toolCallDelta.code_interpreter.input) {
37-
// process.stdout.write(toolCallDelta.code_interpreter.input);
38-
// }
39-
// if (toolCallDelta.code_interpreter.outputs) {
40-
// process.stdout.write("\noutput >\n");
41-
// toolCallDelta.code_interpreter.outputs.forEach((output) => {
42-
// if (output.type === "logs") {
43-
// process.stdout.write(`\n${output.logs}\n`);
44-
// }
45-
// });
46-
// }
47-
// }
48-
// });
49-
5027
export default defineWebSocketHandler({
5128
open(peer) {
5229
console.log("[ws] open", peer);
5330
},
5431

5532
async message(peer, message) {
56-
console.log("[ws] message", peer, message);
5733
const parsed = JSON.parse(message.text());
58-
console.log(parsed);
5934

6035
const dbthread = await db("threads").where({ id: parsed.threadId }).first();
6136
if (!dbthread) {
@@ -67,16 +42,9 @@ export default defineWebSocketHandler({
6742
JSON.stringify({
6843
type: "text-chunk",
6944
text: textChunk,
70-
})
71-
)
45+
}),
46+
),
7247
);
73-
// if (message.text().includes("ping")) {
74-
// peer.send("pong");
75-
// }
76-
77-
// if (message.text().includes("dorun")) {
78-
// streamRun()
79-
// }
8048
},
8149

8250
close(peer, event) {

0 commit comments

Comments
 (0)