Refactor project: remove unused code, clean up logs, streamline error handling, update TypeScript configs, and enhance message streaming.

- Deployed
This commit is contained in:
geoffsee
2025-06-24 16:28:25 -04:00
parent 004ec580d3
commit 9698fc6f3b
19 changed files with 227 additions and 228 deletions

View File

@@ -1,3 +1,4 @@
import { renderPage } from "vike/server";
// This is what makes SSR possible. It is consumed by @open-gsio/server
export default renderPage;

View File

@@ -1,21 +1,83 @@
import { types } from "mobx-state-tree";
import { types, flow } from "mobx-state-tree";
// Utility to pause execution inside a flow
const sleep = (ms: number) => new Promise<void>((res) => setTimeout(res, ms));
// Simple function to generate a unique ID
export const generateId = () => {
return Date.now().toString(36) + Math.random().toString(36).substring(2);
return Date.now().toString(36) + Math.random().toString(36).substring(2);
};
export default types
.model("Message", {
id: types.optional(types.identifier, generateId),
content: types.string,
role: types.enumeration(["user", "assistant"]),
})
.actions((self) => ({
setContent(newContent: string) {
self.content = newContent;
},
append(newContent: string) {
self.content += newContent;
},
}));
// Utility for efficient batched content updates
let batchedContent = "";
let batchUpdateTimeout: NodeJS.Timeout | null = null;
const BATCH_UPDATE_DELAY = 50; // ms
export const batchContentUpdate = (message: any, content: string) => {
if (!content) return;
// Add the content to the batch
batchedContent += content;
// If we already have a timeout scheduled, do nothing
if (batchUpdateTimeout) return;
// Schedule a timeout to apply the batched content
batchUpdateTimeout = setTimeout(() => {
if (message && batchedContent) {
message.append(batchedContent);
batchedContent = "";
}
batchUpdateTimeout = null;
}, BATCH_UPDATE_DELAY);
};
const Message = types
.model("Message", {
id: types.optional(types.identifier, generateId),
content: types.string,
role: types.enumeration(["user", "assistant"]),
})
// Runtimeonly flags that never persist or get serialized
.volatile(() => ({
/** Indicates that characters are still being streamed in */
isStreaming: false,
}))
.actions((self) => {
// Basic mutators ---------------------------------------------------------
const setContent = (newContent: string) => {
self.content = newContent;
};
const append = (newContent: string) => {
self.content += newContent;
};
/**
* Stream content into the message for a smooth “typing” effect.
* @param newContent The full text to stream in.
* @param chunkSize How many characters to reveal per tick (default 3).
* @param delay Delay (ms) between ticks (default 20ms).
*/
const streamContent = flow(function* (
newContent: string,
chunkSize = 3,
delay = 20
) {
self.isStreaming = true;
let pointer = 0;
// Reveal the content chunkbychunk
while (pointer < newContent.length) {
append(newContent.slice(pointer, pointer + chunkSize));
pointer += chunkSize;
yield sleep(delay);
}
self.isStreaming = false; // finished
});
return { setContent, append, streamContent };
});
export default Message;

View File

@@ -11,7 +11,7 @@ export default function IndexPage() {
clientChatStore.setModel(model as string);
} catch (_) {
console.log("using default model");
// Fall back to default model
}
}, []);

View File

@@ -1,7 +1,7 @@
import {types, type Instance} from "mobx-state-tree";
import clientChatStore from "./ClientChatStore";
import UserOptionsStore from "./UserOptionsStore";
import Message from "../models/Message";
import Message, { batchContentUpdate } from "../models/Message";
import {MessagesStore} from "./MessagesStore";
export const MessageEditorStore = types
@@ -78,34 +78,47 @@ export const MessageEditorStore = types
});
if (response.status === 429) {
clientChatStore.updateLast("Too many requests • please slow down.");
clientChatStore.appendLast("\n\nError: Too many requests • please slow down.");
clientChatStore.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
return;
}
if (response.status > 200) {
clientChatStore.updateLast("Error • something went wrong.");
clientChatStore.appendLast("\n\nError: Something went wrong.");
clientChatStore.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
return;
}
const {streamUrl} = await response.json();
// Use the StreamStore's functionality to handle the event source
const eventSource = new EventSource(streamUrl);
eventSource.onmessage = (event) => {
// Set up event handlers using a more efficient approach
const handleMessage = (event) => {
try {
const parsed = JSON.parse(event.data);
if (parsed.type === "error") {
clientChatStore.updateLast(parsed.error);
// Append error message instead of replacing content
clientChatStore.appendLast("\n\nError: " + parsed.error);
clientChatStore.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
eventSource.close();
return;
}
// Get the last message to use its streamContent method
const lastMessage = clientChatStore.items[clientChatStore.items.length - 1];
if (parsed.type === "chat" && parsed.data.choices[0]?.finish_reason === "stop") {
clientChatStore.appendLast(parsed.data.choices[0]?.delta?.content ?? "");
// For the final chunk, append it and close the connection
const content = parsed.data.choices[0]?.delta?.content ?? "";
if (content) {
// Use appendLast for the final chunk to ensure it's added immediately
clientChatStore.appendLast(content);
}
clientChatStore.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
eventSource.close();
@@ -113,22 +126,30 @@ export const MessageEditorStore = types
}
if (parsed.type === "chat") {
clientChatStore.appendLast(parsed.data.choices[0]?.delta?.content ?? "");
// For regular chunks, use the batched content update for a smoother effect
const content = parsed.data.choices[0]?.delta?.content ?? "";
if (content && lastMessage) {
// Use the batching utility for more efficient updates
batchContentUpdate(lastMessage, content);
}
}
} catch (err) {
console.error("stream parse error", err);
}
};
eventSource.onerror = () => {
clientChatStore.updateLast("Error • connection lost.");
const handleError = () => {
clientChatStore.appendLast("\n\nError: Connection lost.");
clientChatStore.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
eventSource.close();
};
eventSource.onmessage = handleMessage;
eventSource.onerror = handleError;
} catch (err) {
console.error("sendMessage", err);
clientChatStore.updateLast("Sorry • network error.");
clientChatStore.appendLast("\n\nError: Sorry • network error.");
clientChatStore.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
}

View File

@@ -1,6 +1,6 @@
import {flow, getParent, type Instance, types} from "mobx-state-tree";
import UserOptionsStore from "./UserOptionsStore";
import Message from "../models/Message";
import Message, { batchContentUpdate } from "../models/Message";
import type {RootDeps} from "./RootDeps.ts";
export const StreamStore = types
@@ -60,13 +60,13 @@ export const StreamStore = types
});
if (response.status === 429) {
root.updateLast("Too many requests • please slow down.");
root.appendLast("\n\nError: Too many requests • please slow down.");
cleanup();
UserOptionsStore.setFollowModeEnabled(false);
return;
}
if (response.status > 200) {
root.updateLast("Error • something went wrong.");
root.appendLast("\n\nError: Something went wrong.");
cleanup();
UserOptionsStore.setFollowModeEnabled(false);
return;
@@ -79,19 +79,29 @@ export const StreamStore = types
const handleMessage = (event: MessageEvent) => {
try {
const parsed = JSON.parse(event.data);
if (parsed.type === "error") {
root.updateLast(parsed.error);
// Append error message instead of replacing content
root.appendLast("\n\nError: " + parsed.error);
root.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
cleanup();
return;
}
// Get the last message
const lastMessage = root.items[root.items.length - 1];
if (
parsed.type === "chat" &&
parsed.data.choices[0]?.finish_reason === "stop"
) {
root.appendLast(parsed.data.choices[0]?.delta?.content ?? "");
// For the final chunk, append it and close the connection
const content = parsed.data.choices[0]?.delta?.content ?? "";
if (content) {
// Use appendLast for the final chunk to ensure it's added immediately
root.appendLast(content);
}
UserOptionsStore.setFollowModeEnabled(false);
root.setIsLoading(false);
cleanup();
@@ -99,7 +109,12 @@ export const StreamStore = types
}
if (parsed.type === "chat") {
root.appendLast(parsed.data.choices[0]?.delta?.content ?? "");
// For regular chunks, use the batched content update for a smoother effect
const content = parsed.data.choices[0]?.delta?.content ?? "";
if (content && lastMessage) {
// Use the batching utility for more efficient updates
batchContentUpdate(lastMessage, content);
}
}
} catch (err) {
console.error("stream parse error", err);
@@ -107,7 +122,7 @@ export const StreamStore = types
};
const handleError = () => {
root.updateLast("Error • connection lost.");
root.appendLast("\n\nError: Connection lost.");
root.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
cleanup();
@@ -117,7 +132,7 @@ export const StreamStore = types
self.eventSource.onerror = handleError;
} catch (err) {
console.error("sendMessage", err);
root.updateLast("Sorry • network error.");
root.appendLast("\n\nError: Sorry • network error.");
root.setIsLoading(false);
UserOptionsStore.setFollowModeEnabled(false);
cleanup();

View File

@@ -1,28 +1,19 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
"outDir": "dist",
"rootDir": "src",
"baseUrl": "src",
"noEmit": true
},
"include": [
"src/**/*.ts",
"src/**/*.tsx"
],
"exclude": [
"node_modules",
"dist"
]
}