diff --git a/examples/white-label-minimal/components/Chat.tsx b/examples/white-label-minimal/components/Chat.tsx index b852d17..39977ee 100644 --- a/examples/white-label-minimal/components/Chat.tsx +++ b/examples/white-label-minimal/components/Chat.tsx @@ -69,5 +69,6 @@ export default function Chat({ app, onAppCreated, onBuildingChange }: { // One tool step as a short line, e.g. "✓ write_file src/pages/Home.jsx". function toolLine(tool: ToolCall) { const icon = tool.status === "running" ? "…" : tool.status === "error" ? "✗" : "✓"; - return `${icon} ${tool.name} ${tool.display?.file_paths?.join(", ") ?? ""}`; + const files = "display" in tool && tool.display && "file_paths" in tool.display ? tool.display.file_paths : undefined; + return `${icon} ${tool.name} ${files?.join(", ") ?? ""}`; } diff --git a/examples/white-label-minimal/components/ToolQuestion.tsx b/examples/white-label-minimal/components/ToolQuestion.tsx index d215f0d..9ce5947 100644 --- a/examples/white-label-minimal/components/ToolQuestion.tsx +++ b/examples/white-label-minimal/components/ToolQuestion.tsx @@ -1,6 +1,6 @@ "use client"; import { useState } from "react"; -import type { ToolCall, ToolQuestionArguments, ToolSecretArguments } from "@base44/platform"; +import type { Question, SecretField, ToolCall } from "@base44/platform"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { submitToolCallInput } from "../server/base44"; @@ -12,9 +12,12 @@ export default function ToolQuestion({ appId, messageId, tool }: { appId: string const [picked, setPicked] = useState>({}); const [secrets, setSecrets] = useState>({}); const kind = tool.waiting_on?.kind; - const questions = kind === "choice" ? (tool.arguments as ToolQuestionArguments)?.questions ?? [] : []; + // ToolCall includes OtherToolCall, which has no arguments, so narrow with `in` after the name. + let questions: Question[] = []; + if (kind === "choice" && tool.name === "ask_clarifying_questions" && "arguments" in tool) questions = tool.arguments?.questions ?? []; // Tiny passes these values to Base44 and keeps none. - const fields = kind === "input" ? (tool.arguments as ToolSecretArguments)?.secrets_schema ?? [] : []; + let fields: SecretField[] = []; + if (kind === "input" && tool.name === "set_secrets" && "arguments" in tool) fields = tool.arguments?.secrets_schema ?? []; function answer(approve: boolean, extraUserInput?: object) { setSent(true); @@ -35,7 +38,7 @@ export default function ToolQuestion({ appId, messageId, tool }: { appId: string return (
- {kind === "approval" &&

Allow {tool.name}? {tool.approval?.reason ?? tool.approval?.details?.summary}

} + {kind === "approval" &&

Allow {tool.name}? {approvalText(tool)}

} {questions.map((q, i) => (

{q.question}

@@ -58,3 +61,12 @@ export default function ToolQuestion({ appId, messageId, tool }: { appId: string
); } + +// Why a guard paused the call. Some guards, such as bash_approval, give no reason. +function approvalText(tool: ToolCall) { + const approval = tool.approval; + if (!approval || !("reason" in approval)) return ""; + if (approval.reason) return approval.reason; + if (approval.details && "summary" in approval.details) return approval.details.summary ?? ""; + return ""; +} diff --git a/examples/white-label-minimal/components/useLiveChat.ts b/examples/white-label-minimal/components/useLiveChat.ts index 1cd7022..089e384 100644 --- a/examples/white-label-minimal/components/useLiveChat.ts +++ b/examples/white-label-minimal/components/useLiveChat.ts @@ -1,6 +1,6 @@ "use client"; import { useEffect, useState } from "react"; -import { Base44PlatformClient, type ChatMessage } from "@base44/platform"; +import { createPlatformClient, type Message } from "@base44/platform"; import { createApp, openLiveSession, sendMessage, type App } from "../server/base44"; // The chat's state: the live connection to Base44, message ordering, create-or-send, @@ -10,7 +10,7 @@ export function useLiveChat({ app, onAppCreated }: { onAppCreated: (app: App) => void; }) { const appId = app?.id ?? null; - const [messages, setMessages] = useState([]); + const [messages, setMessages] = useState([]); const [building, setBuilding] = useState(false); const [loading, setLoading] = useState(!!appId); const [creating, setCreating] = useState(false); @@ -20,37 +20,33 @@ export function useLiveChat({ app, onAppCreated }: { useEffect(() => { if (!appId) return; let stopped = false; - let socket: { close(): void } | undefined; - const fail = (e: { code?: string; message?: string }) => setError(e.code || e.message || "Live updates failed"); - - openLiveSession(appId) - .then(({ serverUrl, sessionToken }) => { - if (stopped) return; - const builder = new Base44PlatformClient({ serverUrl, getSessionToken: () => sessionToken }).builder.init({ onError: fail }); - socket = builder; - builder.subscribe(appId, { - onSnapshot: (snapshot) => { - setMessages(snapshot.messages); - setLoading(false); - setBuilding(snapshot.status?.state === "processing"); - }, - onEvent: (event) => { - if (event.type === "message.updated") { - const message = event.data.message; - // Replace the message with the same id in place, or add it at the end. - setMessages((all) => (all.some((m) => m.id === message.id) ? all.map((m) => (m.id === message.id ? message : m)) : [...all, message])); - } - if (event.type === "app.status_changed") setBuilding(event.data.status?.state === "processing"); - }, - onError: fail, - }); - return builder.connect(); - }) - .catch(fail); + // close() rejects a pending connect; that is not an error to show. + const fail = (e: { code?: string; message?: string }) => { + if (!stopped) setError(e.code || e.message || "Live updates failed"); + }; + // The SDK calls getSession on connect, and again when the session expires. + const builder = createPlatformClient().builder.init({ getSession: () => openLiveSession(appId), onError: fail }); + builder.subscribe(appId, { + onSnapshot: (snapshot) => { + setMessages(snapshot.messages); + setLoading(false); + setBuilding(snapshot.status?.state === "processing"); + }, + onEvent: (event) => { + if (event.type === "message.updated") { + const message = event.data.message; + // Replace the message with the same id in place, or add it at the end. + setMessages((all) => (all.some((m) => m.id === message.id) ? all.map((m) => (m.id === message.id ? message : m)) : [...all, message])); + } + if (event.type === "app.status_changed") setBuilding(event.data.status?.state === "processing"); + }, + onError: fail, + }); + builder.connect().catch(fail); return () => { stopped = true; - socket?.close(); + builder.close(); }; }, [appId]); diff --git a/examples/white-label-minimal/package.json b/examples/white-label-minimal/package.json index d3226cd..c7468df 100644 --- a/examples/white-label-minimal/package.json +++ b/examples/white-label-minimal/package.json @@ -9,7 +9,7 @@ "typecheck": "next typegen && tsc --noEmit" }, "dependencies": { - "@base44/platform": "^0.1.0", + "@base44/platform": "^0.2.0", "class-variance-authority": "^0.7.1", "cn": "^0.4.0", "lucide-react": "^0.475.0", diff --git a/examples/white-label-minimal/server/base44.ts b/examples/white-label-minimal/server/base44.ts index f867d42..917e619 100644 --- a/examples/white-label-minimal/server/base44.ts +++ b/examples/white-label-minimal/server/base44.ts @@ -1,4 +1,5 @@ "use server"; +import type { SocketSession } from "@base44/platform"; export type App = { id: string; name: string }; export type ToolCallAnswer = { toolCallId: string; messageId: string; approve: boolean; extraUserInput?: object }; @@ -31,9 +32,10 @@ export async function listApps(): Promise { .map(({ id, name }) => ({ id, name })); } -export async function openLiveSession(appId: string) { +// The browser's socket needs only these three fields; session_id stays on the server. +export async function openLiveSession(appId: string): Promise { const session = await call("/api/service/socket-sessions", { body: { app_ids: [appId] }, headers: serviceKey }); - return { serverUrl: new URL(session.socket_url).origin, sessionToken: session.session_token as string }; + return { session_token: session.session_token, socket_url: session.socket_url, socket_path: session.socket_path }; } export async function sendMessage(appId: string, content: string) { diff --git a/package-lock.json b/package-lock.json index 33b36b2..f4b8069 100644 --- a/package-lock.json +++ b/package-lock.json @@ -84,7 +84,7 @@ "name": "@base44/white-label-minimal", "version": "0.1.0", "dependencies": { - "@base44/platform": "^0.1.0", + "@base44/platform": "^0.2.0", "class-variance-authority": "^0.7.1", "cn": "^0.4.0", "lucide-react": "^0.475.0", @@ -565,9 +565,10 @@ } }, "node_modules/@base44/platform": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@base44/platform/-/platform-0.1.0.tgz", - "integrity": "sha512-6InHb3mM2RPLqU42atFprwkkjR1Osu0CFQpAmloD1sAv7EfDg0h4rGMuUtSB5EZJLJiz4f6DaQ44dP/lR7mjtQ==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@base44/platform/-/platform-0.2.0.tgz", + "integrity": "sha512-fN3JggD6GLAnOzc2CfRr3oWoyss5xRfDfy+ktqp2wJ0DbgWM5SufrZox35RnlIAzvmmxmPV6CntvGTVhhyTZ7g==", + "license": "MIT", "dependencies": { "socket.io-client": "^4.8.3" }