Skip to content

Commit 9b80512

Browse files
authored
chore: improve code quality with proper error handling and types (#491)
* chore: improve code quality with proper error handling and types - Add console.warn to empty catch blocks in model.ts and app-sidebar.tsx - Replace any[] with ChatFileAttachment type in pending-message.ts - Replace any[] with ChatFileAttachment type in use-persistent-chat.ts - Add OpenRouterModel interface for API response typing in model.ts - Use proper Convex Id<"chats"> type in app-sidebar.tsx - Remove unsafe 'as unknown as' type casts * fix(deploy): run convex codegen before web build on Railway The Railway build was failing because @server/convex/_generated/* imports couldn't resolve - the Convex generated files don't exist during web-only builds. Fix by running 'convex codegen' in the server directory first to generate the required type files before building the web app.
1 parent 370aba7 commit 9b80512

5 files changed

Lines changed: 57 additions & 16 deletions

File tree

apps/web/railway.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[build]
22
builder = "RAILPACK"
3-
buildCommand = "cd apps/web && bun run build"
4-
watchPatterns = ["apps/web/**"]
3+
buildCommand = "cd apps/server && bunx convex codegen && cd ../web && bun run build"
4+
watchPatterns = ["apps/web/**", "apps/server/convex/**"]
55

66
[deploy]
77
startCommand = "cd apps/web && bun .output/server/index.mjs"

apps/web/src/components/app-sidebar.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ import {
1818
useSidebar,
1919
} from "./ui/sidebar";
2020
import { PlusIcon, ChatIcon, SidebarIcon, ChevronRightIcon } from "@/components/icons";
21+
import type { Id } from "@server/convex/_generated/dataModel";
2122

2223
const CHATS_CACHE_KEY = "openchat-chats-cache";
2324

24-
// Group chats by time periods
2525
interface ChatItem {
26-
_id: string;
26+
_id: Id<"chats">;
2727
title: string;
2828
updatedAt: number;
2929
status?: string;
@@ -130,19 +130,23 @@ export function AppSidebar() {
130130
if (stored && !cachedChatsRef.current) {
131131
cachedChatsRef.current = JSON.parse(stored);
132132
}
133-
} catch {}
133+
} catch (e) {
134+
console.warn("Failed to load chats from localStorage:", e);
135+
}
134136
}, []);
135137

136138
useEffect(() => {
137139
if (chatsResult?.chats && chatsResult.chats.length > 0) {
138-
cachedChatsRef.current = chatsResult.chats as unknown as ChatItem[];
140+
cachedChatsRef.current = chatsResult.chats;
139141
try {
140142
localStorage.setItem(CHATS_CACHE_KEY, JSON.stringify(chatsResult.chats));
141-
} catch {}
143+
} catch (e) {
144+
console.warn("Failed to save chats to localStorage:", e);
145+
}
142146
}
143147
}, [chatsResult?.chats]);
144148

145-
const chats = (chatsResult?.chats ?? cachedChatsRef.current ?? []) as unknown as ChatItem[];
149+
const chats = chatsResult?.chats ?? cachedChatsRef.current ?? [];
146150

147151
const hasCachedChats = chats.length > 0;
148152

apps/web/src/hooks/use-persistent-chat.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ import { useCallback, useEffect, useRef, useState } from "react";
1212
import { useChat } from "@ai-sdk/react";
1313
import { DefaultChatTransport } from "ai";
1414
import type { UIMessage } from "ai";
15+
16+
interface ChatFileAttachment {
17+
type: "file";
18+
mediaType: string;
19+
filename?: string;
20+
url: string;
21+
}
1522
import { useQuery, useMutation } from "convex/react";
1623
import { convexClient } from "@/lib/convex";
1724
import { api } from "@server/convex/_generated/api";
@@ -31,7 +38,7 @@ export interface UsePersistentChatOptions {
3138

3239
export interface UsePersistentChatReturn {
3340
messages: UIMessage[];
34-
sendMessage: (message: { text: string; files?: any[] }) => Promise<void>;
41+
sendMessage: (message: { text: string; files?: ChatFileAttachment[] }) => Promise<void>;
3542
status: "ready" | "submitted" | "streaming" | "error";
3643
error: Error | undefined;
3744
stop: () => void;
@@ -520,7 +527,7 @@ export function usePersistentChat({
520527

521528
// Handle sending messages with new chat creation
522529
const handleSendMessage = useCallback(
523-
async (message: { text: string; files?: any[] }) => {
530+
async (message: { text: string; files?: ChatFileAttachment[] }) => {
524531
if (!convexUserId) return;
525532

526533
if (!message.text.trim() && (!message.files || message.files.length === 0)) {

apps/web/src/stores/model.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,25 @@ import { analytics } from "@/lib/analytics";
1616
// Types
1717
// ============================================================================
1818

19+
/** Raw model data from OpenRouter API */
20+
interface OpenRouterModel {
21+
id: string;
22+
name?: string;
23+
description?: string;
24+
context_length?: number;
25+
pricing?: {
26+
prompt?: string;
27+
completion?: string;
28+
};
29+
top_provider?: {
30+
max_completion_tokens?: number;
31+
};
32+
architecture?: {
33+
modality?: string;
34+
};
35+
supported_parameters?: string[];
36+
}
37+
1938
export interface Model {
2039
id: string;
2140
name: string;
@@ -151,15 +170,19 @@ function loadFromStorage(): { models: Model[] | null; timestamp: number } {
151170
return { models: parsed.models, timestamp: parsed.timestamp };
152171
}
153172
}
154-
} catch {}
173+
} catch (e) {
174+
console.warn("Failed to load models from localStorage:", e);
175+
}
155176
return { models: null, timestamp: 0 };
156177
}
157178

158179
function saveToStorage(models: Model[], timestamp: number) {
159180
if (typeof window === "undefined") return;
160181
try {
161182
localStorage.setItem(STORAGE_KEY, JSON.stringify({ models, timestamp }));
162-
} catch {}
183+
} catch (e) {
184+
console.warn("Failed to save models to localStorage:", e);
185+
}
163186
}
164187

165188
const stored = loadFromStorage();
@@ -242,7 +265,7 @@ function extractFamily(id: string, name: string): string | undefined {
242265
return undefined;
243266
}
244267

245-
function transformModel(raw: any): Model {
268+
function transformModel(raw: OpenRouterModel): Model {
246269
const id = raw.id as string;
247270
const providerSlug = id.split("/")[0] || "unknown";
248271
const info = PROVIDER_INFO[providerSlug] || {
@@ -313,8 +336,8 @@ async function fetchAllModels(): Promise<Model[]> {
313336
const rawModels = data.data || [];
314337

315338
// Transform all models
316-
const models: Model[] = rawModels
317-
.filter((m: any) => m.id && typeof m.id === "string")
339+
const models: Model[] = (rawModels as OpenRouterModel[])
340+
.filter((m): m is OpenRouterModel & { id: string } => !!m.id && typeof m.id === "string")
318341
.map(transformModel)
319342
// Sort: Popular first, then by provider priority, then alphabetically
320343
.sort((a: Model, b: Model) => {

apps/web/src/stores/pending-message.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,17 @@
1212

1313
import { create } from "zustand";
1414

15+
interface ChatFileAttachment {
16+
type: "file";
17+
mediaType: string;
18+
filename?: string;
19+
url: string;
20+
}
21+
1522
interface PendingMessage {
1623
chatId: string;
1724
text: string;
18-
files?: any[];
25+
files?: ChatFileAttachment[];
1926
}
2027

2128
interface PendingMessageStore {

0 commit comments

Comments
 (0)