Explorar el Código

Add in proper streaming to text.

Lee Morgan hace 3 semanas
padre
commit
76a1e5e497

+ 11 - 1
package-lock.json

@@ -10,7 +10,8 @@
 			"dependencies": {
 				"@stripe/stripe-js": "^9.8.0",
 				"dotenv": "^17.4.2",
-				"express": "^5.2.1"
+				"express": "^5.2.1",
+				"undici": "^8.5.0"
 			},
 			"devDependencies": {
 				"@sveltejs/adapter-auto": "^7.0.1",
@@ -2050,6 +2051,15 @@
 				"url": "https://opencollective.com/express"
 			}
 		},
+		"node_modules/undici": {
+			"version": "8.5.0",
+			"resolved": "https://registry.npmjs.org/undici/-/undici-8.5.0.tgz",
+			"integrity": "sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==",
+			"license": "MIT",
+			"engines": {
+				"node": ">=22.19.0"
+			}
+		},
 		"node_modules/unpipe": {
 			"version": "1.0.0",
 			"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",

+ 2 - 1
package.json

@@ -19,6 +19,7 @@
 	"dependencies": {
 		"@stripe/stripe-js": "^9.8.0",
 		"dotenv": "^17.4.2",
-		"express": "^5.2.1"
+		"express": "^5.2.1",
+		"undici": "^8.5.0"
 	}
 }

+ 21 - 16
src/routes/(app)/game/[game_id]/+page.svelte

@@ -1,6 +1,7 @@
 <script>
     import {page} from "$app/state";
     import {PUBLIC_API_URL} from "$env/static/public";
+    import {onMount} from "svelte";
 
     import DashboardNav from "../../dashboard/DashboardNav.svelte";
     import Message from "./Message.svelte";
@@ -37,14 +38,16 @@
 
         const response = await fetch(`${PUBLIC_API_URL}/game/${game.id}/session/${sessions[0].id}/turn`, {
             method: "POST",
-            headers: {"Content-Type": "application/json"},
+            headers: {
+                "Content-Type": "application/json",
+                "Accept-Encoding": "identity"
+            },
             credentials: "include",
             body: JSON.stringify({content: text})
         });
 
         const reader = response.body.getReader();
         const decoder = new TextDecoder();
-        let streamingIndex = turns.length - 1;
         let buffer = "";
 
         while(true){
@@ -52,27 +55,29 @@
             if(done) break;
 
             buffer += decoder.decode(value, {stream: true});
-
-            const lines = buffer.split("\n\n");
+            const lines = buffer.split("\n");
             buffer = lines.pop();
 
             for(let i = 0; i < lines.length; i++){
-                if(!lines[i].startsWith("data: ")) continue;
-                const payload = JSON.parse(lines[i].slice(6));
-
-                if(payload.type === "chunk"){
-                    turns[streamingIndex].llm_text += payload.text;
-                }else if(payload.type === "done"){
-                    console.log(payload);
-                    streaming = false;
-                }
+                if(!lines[i].startsWith("data:")) continue;
+                const data = lines[i].slice(5).trim();
+                if(data === "[DONE]") continue;
+                try{
+                    const json = JSON.parse(data);
+                    const chunk = json.choices?.[0]?.delta?.content;
+                    if(chunk) turns[turns.length-1].llm_text += chunk;
+                }catch{}
             }
         }
-    }
 
-    if(turns.length === 0){
-        sendMessage("");
+        streaming = false;
     }
+
+    onMount(()=>{
+        if(turns.length === 0){
+            sendMessage("");
+        }
+    });
 </script>
 
 <div class="page">

+ 37 - 0
src/routes/api/game/[game_id]/session/[session_id]/turn/+server.js

@@ -0,0 +1,37 @@
+import {PRIVATE_API_URL} from "$env/static/private";
+import {fetch} from "undici";
+
+export async function POST({params, request, cookies}){
+    const cookie = cookies.get("user");
+    const body = await request.json();
+
+    const res = await fetch(`${PRIVATE_API_URL}/game/${params.game_id}/session/${params.session_id}/turn`, {
+        method: "POST",
+        headers: {
+            "Content-Type": "application/json",
+            "Cookie": `user=${cookie}`,
+            "Accept-Encoding": "identity"
+        },
+        body: JSON.stringify(body),
+        duplex: "half"
+    });
+
+    const reader = res.body.getReader();
+    const stream = new ReadableStream({
+        async start(controller) {
+            while (true) {
+                const { done, value } = await reader.read();
+                if (done) break;
+                controller.enqueue(value);
+            }
+            controller.close();
+        }
+    });
+
+    return new Response(stream, {
+        headers: {
+            "Content-Type": "text/event-stream",
+            "Cache-Control": "no-cache"
+        }
+    });
+}

+ 6 - 1
vite.config.js

@@ -17,7 +17,12 @@ export default defineConfig({
             "/api": {
                 target: "https://chatrpgapi.leemorgan.dev",
                 changeOrigin: true,
-                rewrite: (path) => path.replace(/^\/api/, "")
+                rewrite: (path) => path.replace(/^\/api/, ""),
+                bypass: (req) => {
+                    if (req.url.match(/\/api\/game\/[^/]+\/session\/[^/]+\/turn/)) {
+                        return req.url;
+                    }
+                }
             }
         }
     }