Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7e8b73a
Add debug logger and tool call support for OpenAI API compatible tool…
Pablonara Jul 1, 2025
9f6835d
Remove debugging in production
Pablonara Jul 1, 2025
680fd24
Remove reasoning tag rendering for user logic as users will never out…
Pablonara Jul 1, 2025
eac8917
Fallback to assistiant rendering and warn in console instead if inval…
Pablonara Jul 1, 2025
993d2db
Change any[] to check with zod first
Pablonara Jul 1, 2025
2983918
Switch log to devLog (logging wrapper)
Pablonara Jul 1, 2025
69ffaed
Add tempfix for max recursion depth on toolcalls using prompt enginee…
Pablonara Jul 2, 2025
ea434dc
Fix issues with tool_calls blank array making model providers angry a…
Pablonara Jul 2, 2025
e244873
Fix hardcoded parameter assumption when sending search parameters
Pablonara Jul 2, 2025
6d9682a
More efficient DB querying
Pablonara Jul 2, 2025
58c9c21
Fix passing proper depth context to sysprompt
Pablonara Jul 2, 2025
dbf8eb5
Log if tool call fails (plausible if model is smaller)
Pablonara Jul 2, 2025
1637f32
Fix: Actually log an error in production using the correct function
Pablonara Jul 2, 2025
a6bdfb4
Fix tool message rendering \\n and add searchWeb tool!!!
Pablonara Jul 2, 2025
33789b1
fix weird zod shit and clean up
x4132 Jul 2, 2025
23fddc8
reorganize tool functions
x4132 Jul 2, 2025
8c66de9
further reorg, remove test tool, etc
x4132 Jul 2, 2025
1f4efc1
Fix CI
Pablonara Jul 2, 2025
472579a
client-side keys
x4132 Jul 5, 2025
50dbeda
new hooks
x4132 Jul 5, 2025
ab9b74a
Convert showcase videos from .mov to .mp4 format
x4132 Jul 8, 2025
fab11bb
Update docker-compose to healthcheck faster for faster boot times in …
Pablonara Jul 13, 2025
6a00cad
Add getPageContent for better tool calling
Pablonara Jul 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 119 additions & 18 deletions client/src/components/MessageRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,41 @@ export function MessageRenderer({ chatId }: MessageRendererProps) {
if (!messageResponse) {
throw new Error("Failed to fetch messages");
}
let messages = await messageResponse.json();
const messageResponseData = await messageResponse.json();
const messages = z.object({
messages: z.optional(z.array(z.any()))
}).parse(messageResponseData);

if (!activeId && activeMessage.length > 0 && setActiveMessage) {
setActiveMessage([]);
}
return z.object({ messages: z.array(Message) }).parse(messages);

// Add tool_calls or tool_call_id (null) because Zod v4 Mini does not support partial, and API does not return tool_calls per message
// Should fix message rendering with tool_calls support
// edit: now checks type of tool_calls and converts arguments to string if needed, probably make backend API better later

const parsedMessages = messages.messages?.map((msg: any) => ({
...msg,
tool_calls: msg.tool_calls ? msg.tool_calls.map((tc: any) => ({
...tc,
function: {
...tc.function,
arguments: typeof tc.function.arguments === 'string'
? tc.function.arguments
: JSON.stringify(tc.function.arguments)
}
})) : null,
toolCallId: msg.toolCallId || null,
// Default to assistant if role is invalid, but warn in console
role: ["system", "user", "assistant", "tool"].includes(msg.role)
? msg.role
: (() => {
console.warn(`Invalid role: ${msg.role}, defaulting to 'assistant'`);
return 'assistant'
})()
})) || [];

return { messages: z.array(Message).parse(parsedMessages) };
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
} else {
return { messages: [], cursor: 0 };
}
Expand All @@ -93,6 +122,8 @@ export function MessageRenderer({ chatId }: MessageRendererProps) {
message: sendMessageVariables,
reasoning: null,
files: null,
tool_calls: null,
toolCallId: null,
finish_reason: null,
createdAt: new Date(),
});
Expand All @@ -108,6 +139,8 @@ export function MessageRenderer({ chatId }: MessageRendererProps) {
reasoning: activeMessage.reduce((prev, cur) => prev + cur.reasoning, ""),
finish_reason: activeMessage.reduce((prev: string | null, cur) => (prev ? prev : cur.finish_reason), null),
files: null,
tool_calls: null,
toolCallId: null,
createdAt: new Date(),
});
}
Expand Down Expand Up @@ -148,6 +181,8 @@ function RenderedMsg({
setActiveMessage?: (chunks: any[]) => void;
}) {
const [showThink, setShowThink] = React.useState(false);
const [showToolResult, setShowToolResult] = React.useState(false);
const [showFunctionCalls, setShowFunctionCalls] = React.useState(false);
const or_key = useORKey((state) => state.key);
const model = useModel((state) => state.model);
const [editMessage, setEditMessage] = useState("");
Expand Down Expand Up @@ -269,25 +304,91 @@ function RenderedMsg({
</div>
) : (
<div
className={`${message.role === "user" ? "border p-2 rounded-lg ml-auto" : "px-2 py-1"} bg-background mb-1 prose`}
className={`${message.role === "user" ? "border p-2 rounded-lg ml-auto" : "px-2 py-1"} bg-background mb-1`}
>
{message.reasoning ? (
<Collapsible>
<CollapsibleTrigger
className="flex items-center gap-1 transition-all text-foreground/50 hover:text-foreground"
onClick={() => setShowThink(!showThink)}
>
{showThink ? <ChevronDown /> : <ChevronRight />} {showThink ? "Hide Thinking" : "Show Thinking"}
</CollapsibleTrigger>
<CollapsibleContent>
<MarkdownRenderer>{message.reasoning ?? ""}</MarkdownRenderer>
</CollapsibleContent>
</Collapsible>
) : null}
{/* Only wrap in space-y-2 for non-user messages EDIT: changed wrapper to use space-y-3*/}
{message.role === "user" ? (
/* User messages: apply prose only to final content */
<div className="prose">
<MarkdownRenderer>{retryMessage.variables ?? message.message}</MarkdownRenderer>
</div>
) : (
/* Assistant/system/tool messages: apply prose only to final content */
/* Nevermind we use space-y-3 for more consistiency because my code is cooked */
<div className="space-y-3">
{message.reasoning ? (
<Collapsible>
<CollapsibleTrigger
className="flex items-center gap-1 transition-all text-foreground/50 hover:text-foreground"
onClick={() => setShowThink(!showThink)}
>
{showThink ? <ChevronDown /> : <ChevronRight />} {showThink ? "Hide Thinking" : "Show Thinking"}
</CollapsibleTrigger>
<CollapsibleContent>
<div className="prose">
<MarkdownRenderer>{message.reasoning ?? ""}</MarkdownRenderer>
</div>
</CollapsibleContent>
</Collapsible>
) : null}

<MarkdownRenderer>{retryMessage.variables ?? message.message}</MarkdownRenderer>
{/* Tool message rendering */}
{message.role === "tool" ? (
<Collapsible>
<CollapsibleTrigger
className="flex items-center gap-1 transition-all text-foreground/50 hover:text-foreground"
onClick={() => setShowToolResult(!showToolResult)}
>
{showToolResult ? <ChevronDown /> : <ChevronRight />} Tool Result
{message.toolCallId && (
<span className="text-xs text-foreground/40">({message.toolCallId})</span>
)}
</CollapsibleTrigger>
<CollapsibleContent className="mt-2">
<div className="border-l-2 border-foreground/20 pl-3 prose">
<MarkdownRenderer>{retryMessage.variables ?? message.message}</MarkdownRenderer>
</div>
</CollapsibleContent>
</Collapsible>
) : message.tool_calls && message.tool_calls.length > 0 ? (
<>
<Collapsible>
<CollapsibleTrigger
className="flex items-center gap-1 transition-all text-foreground/50 hover:text-foreground"
onClick={() => setShowFunctionCalls(!showFunctionCalls)}
>
{showFunctionCalls ? <ChevronDown /> : <ChevronRight />} Function Calls ({message.tool_calls.length})
</CollapsibleTrigger>
<CollapsibleContent className="mt-2">
<div className="border-l-2 border-foreground/20 pl-3 space-y-2">
{message.tool_calls.map((toolCall, index) => (
<div key={toolCall.id || index} className="text-sm">
<div className="font-mono text-foreground/80 mb-1">
{toolCall.function.name}
</div>
<div className="text-xs text-foreground/60 bg-muted p-2 rounded font-mono overflow-x-auto">
{toolCall.function.arguments}
</div>
</div>
))}
</div>
</CollapsibleContent>
</Collapsible>
{message.message && (
<div className="prose">
<MarkdownRenderer>{retryMessage.variables ?? message.message}</MarkdownRenderer>
</div>
)}
</>
) : (
<div className="prose">
<MarkdownRenderer>{retryMessage.variables ?? message.message}</MarkdownRenderer>
</div>
)}
</div>
)}

{message.finish_reason && message.finish_reason !== "stop" ? (
{message.finish_reason && ["stop", "tool_calls", "tool_calls_response"].includes(message.finish_reason) !== true ? (
<Alert variant="destructive">
<AlertTitle>{message.finish_reason}</AlertTitle>
</Alert>
Expand Down
11 changes: 10 additions & 1 deletion client/src/lib/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,22 @@ export const Chats = z.array(Chat);
export type Chat = z.infer<typeof Chat>;
export const Message = z.object({
id: z.uuidv4(),
role: z.enum(["system", "user", "assistant"]),
role: z.enum(["system", "user", "assistant", "tool"]),
senderId: z.string(),
chatId: z.string(),
files: z.nullable(z.array(z.string())),
reasoning: z.nullable(z.string()),
message: z.string(),
finish_reason: z.nullable(z.string()),
createdAt: z.coerce.date(),
tool_calls: z.nullable(z.array(z.object({
id: z.string(),
type: z.literal("function"),
function: z.object({
name: z.string(),
arguments: z.string(),
}),
}))),
toolCallId: z.nullable(z.string())
});
export type Message = z.infer<typeof Message>;
28 changes: 28 additions & 0 deletions drizzle/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,34 @@
"when": 1750267871981,
"tag": "0000_normal_proteus",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1751319970878,
"tag": "0001_odd_annihilus",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1751323086353,
"tag": "0002_sudden_nick_fury",
"breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1751333885790,
"tag": "0003_sloppy_living_lightning",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1751335818675,
"tag": "0004_mixed_ser_duncan",
"breakpoints": true
}
]
}
9 changes: 8 additions & 1 deletion src/chats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,16 @@ async function getChatMessages(chatId: string): Promise<sync.Messages> {
completions.push({
...msg,
files: files.filter((file) => !!file),
toolCallId: msg.toolCallId ?? undefined,
tool_calls: Array.isArray(msg.tool_calls) ? msg.tool_calls : undefined
});
} else {
completions.push({ ...msg, files: [] });
completions.push({
...msg,
files: [],
toolCallId: msg.toolCallId ?? undefined,
tool_calls: Array.isArray(msg.tool_calls) ? msg.tool_calls : undefined
});
}
}

Expand Down
6 changes: 4 additions & 2 deletions src/db/schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { pgTable, text, timestamp, boolean, index, pgEnum, integer } from "drizzle-orm/pg-core";
import { pgTable, text, timestamp, boolean, index, pgEnum, integer, jsonb } from "drizzle-orm/pg-core";
import { desc } from "drizzle-orm";
import { int } from "drizzle-orm/mysql-core";

Expand Down Expand Up @@ -69,7 +69,7 @@ export const chats = pgTable("chats", {
.notNull(),
});

export const roleEnum = pgEnum("role", ["system", "assistant", "user"]);
export const roleEnum = pgEnum("role", ["system", "assistant", "user", "tool"]);
export const chatMessages = pgTable(
"chat_messages",
{
Expand All @@ -84,6 +84,8 @@ export const chatMessages = pgTable(
message: text("content").notNull(),
reasoning: text("reasoning"),
finish_reason: text("finish_reason"),
tool_calls: jsonb("tool_calls").$defaultFn(() => []), // An array/object of JSON is apparently 'jsonb' format and not type 'array'
toolCallId: text("tool_call_id"),
files: text("files").array(),
createdAt: timestamp("created_at")
.$defaultFn(() => /* @__PURE__ */ new Date())
Expand Down
Loading