feat: new chat working stateless. Added citation logic.

This commit is contained in:
DESKTOP-RTLN3BA\$punk 2025-12-20 23:15:49 -08:00
parent 24f438a39e
commit 947087452f
10 changed files with 441 additions and 160 deletions

View file

@ -87,10 +87,11 @@ def create_chat_litellm_from_config(llm_config: dict) -> ChatLiteLLM | None:
provider_prefix = provider_map.get(provider, provider.lower())
model_string = f"{provider_prefix}/{llm_config['model_name']}"
# Create ChatLiteLLM instance
# Create ChatLiteLLM instance with streaming enabled
litellm_kwargs = {
"model": model_string,
"api_key": llm_config.get("api_key"),
"streaming": True, # Enable streaming for real-time token streaming
}
# Add optional parameters

View file

@ -12,10 +12,9 @@ from langchain_core.messages import HumanMessage
from sqlalchemy.ext.asyncio import AsyncSession
from app.agents.new_chat.chat_deepagent import (
create_chat_litellm_from_config,
create_surfsense_deep_agent,
load_llm_config_from_yaml,
)
from app.agents.new_chat.llm_config import create_chat_litellm_from_config, load_llm_config_from_yaml
from app.services.connector_service import ConnectorService
from app.services.new_streaming_service import VercelStreamingService

View file

@ -1,74 +0,0 @@
import type { UIMessage } from "ai";
export const maxDuration = 30;
export async function POST(req: Request) {
try {
const body = await req.json();
const {
messages,
chat_id,
search_space_id,
}: {
messages: UIMessage[];
chat_id?: number;
search_space_id?: number;
} = body;
// Get auth token from headers
const authHeader = req.headers.get("authorization");
if (!authHeader) {
return new Response("Unauthorized", { status: 401 });
}
// Get the last user message
const lastUserMessage = messages.filter((m) => m.role === "user").pop();
if (!lastUserMessage) {
return new Response("No user message found", { status: 400 });
}
// Extract text content from the message
const userQuery =
typeof lastUserMessage.content === "string"
? lastUserMessage.content
: lastUserMessage.content
.filter((c: any) => c.type === "text")
.map((c: any) => c.text)
.join(" ");
// Call the DeepAgent backend
const backendUrl = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000";
const response = await fetch(`${backendUrl}/api/v1/new_chat`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: authHeader,
},
body: JSON.stringify({
chat_id: chat_id || 0,
user_query: userQuery,
search_space_id: search_space_id || 0,
}),
});
if (!response.ok) {
return new Response(`Backend error: ${response.statusText}`, {
status: response.status,
});
}
// The backend returns SSE stream with Vercel AI SDK Data Stream Protocol
// We need to forward this stream to the client
return new Response(response.body, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
},
});
} catch (error) {
console.error("Error in deep-agent-chat route:", error);
return new Response("Internal Server Error", { status: 500 });
}
}

View file

@ -1,17 +1,44 @@
"use client";
import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime } from "@assistant-ui/react-ai-sdk";
import { AssistantRuntimeProvider, useLocalRuntime } from "@assistant-ui/react";
import { useParams } from "next/navigation";
import { useMemo } from "react";
import { Thread } from "@/components/assistant-ui/thread";
import { createNewChatAdapter } from "@/lib/chat/new-chat-transport";
export default function NewChatPage() {
// Using the official assistant-ui pattern - useChatRuntime with NO parameters
// It defaults to /api/chat endpoint
const runtime = useChatRuntime();
const params = useParams();
// Extract search_space_id and chat_id from URL params
const searchSpaceId = useMemo(() => {
const id = params.search_space_id;
const parsed = typeof id === "string" ? Number.parseInt(id, 10) : 0;
return Number.isNaN(parsed) ? 0 : parsed;
}, [params.search_space_id]);
const chatId = useMemo(() => {
const id = params.chat_id;
let parsed = 0;
if (Array.isArray(id) && id.length > 0) {
parsed = Number.parseInt(id[0], 10);
} else if (typeof id === "string") {
parsed = Number.parseInt(id, 10);
}
return Number.isNaN(parsed) ? 0 : parsed;
}, [params.chat_id]);
// Create the adapter with the extracted params
const adapter = useMemo(
() => createNewChatAdapter({ searchSpaceId, chatId }),
[searchSpaceId, chatId]
);
// Use LocalRuntime with our custom adapter
const runtime = useLocalRuntime(adapter);
return (
<AssistantRuntimeProvider runtime={runtime}>
<div className="h-full">
<div className="h-[calc(100vh-64px)] max-h-[calc(100vh-64px)] overflow-hidden">
<Thread />
</div>
</AssistantRuntimeProvider>

View file

@ -0,0 +1,47 @@
"use client";
import type { FC } from "react";
import { useState } from "react";
import { SheetTrigger } from "@/components/ui/sheet";
import { SourceDetailSheet } from "@/components/chat/SourceDetailSheet";
interface InlineCitationProps {
chunkId: number;
}
/**
* Inline citation component for the new chat.
* Renders a clickable badge that opens the SourceDetailSheet with document chunk details.
*/
export const InlineCitation: FC<InlineCitationProps> = ({ chunkId }) => {
const [isOpen, setIsOpen] = useState(false);
return (
<SourceDetailSheet
open={isOpen}
onOpenChange={setIsOpen}
chunkId={chunkId}
sourceType=""
title="Source"
description=""
url=""
>
<SheetTrigger asChild>
<span
className="text-[10px] font-bold bg-primary/80 hover:bg-primary text-primary-foreground rounded-full w-4 h-4 inline-flex items-center justify-center align-super cursor-pointer transition-colors ml-0.5"
title={`View source (chunk ${chunkId})`}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
className="w-2.5 h-2.5"
>
<path d="M6.22 8.72a.75.75 0 0 0 1.06 1.06l5.22-5.22v1.69a.75.75 0 0 0 1.5 0v-3.5a.75.75 0 0 0-.75-.75h-3.5a.75.75 0 0 0 0 1.5h1.69L6.22 8.72Z" />
<path d="M3.5 6.75c0-.69.56-1.25 1.25-1.25H7A.75.75 0 0 0 7 4H4.75A2.75 2.75 0 0 0 2 6.75v4.5A2.75 2.75 0 0 0 4.75 14h4.5A2.75 2.75 0 0 0 12 11.25V9a.75.75 0 0 0-1.5 0v2.25c0 .69-.56 1.25-1.25 1.25h-4.5c-.69 0-1.25-.56-1.25-1.25v-4.5Z" />
</svg>
</span>
</SheetTrigger>
</SourceDetailSheet>
);
};

View file

@ -9,12 +9,52 @@ import {
useIsMarkdownCodeBlock,
} from "@assistant-ui/react-markdown";
import { CheckIcon, CopyIcon } from "lucide-react";
import { type FC, memo, useState } from "react";
import { type FC, type ReactNode, memo, useState } from "react";
import remarkGfm from "remark-gfm";
import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button";
import { InlineCitation } from "@/components/assistant-ui/inline-citation";
import { cn } from "@/lib/utils";
// Citation pattern: [citation:CHUNK_ID]
const CITATION_REGEX = /\[citation:(\d+)\]/g;
/**
* Parses text and replaces [citation:XXX] patterns with InlineCitation components
*/
function parseTextWithCitations(text: string): ReactNode[] {
const parts: ReactNode[] = [];
let lastIndex = 0;
let match: RegExpExecArray | null;
let citationIndex = 0;
// Reset regex state
CITATION_REGEX.lastIndex = 0;
while ((match = CITATION_REGEX.exec(text)) !== null) {
// Add text before the citation
if (match.index > lastIndex) {
parts.push(text.substring(lastIndex, match.index));
}
// Add the citation component
const chunkId = Number.parseInt(match[1], 10);
parts.push(
<InlineCitation key={`citation-${chunkId}-${citationIndex}`} chunkId={chunkId} />
);
lastIndex = match.index + match[0].length;
citationIndex++;
}
// Add any remaining text after the last citation
if (lastIndex < text.length) {
parts.push(text.substring(lastIndex));
}
return parts.length > 0 ? parts : [text];
}
const MarkdownTextImpl = () => {
return (
<MarkdownTextPrimitive
@ -60,63 +100,116 @@ const useCopyToClipboard = ({ copiedDuration = 3000 }: { copiedDuration?: number
return { isCopied, copyToClipboard };
};
/**
* Helper to process children and replace citation patterns with components
*/
function processChildrenWithCitations(children: ReactNode): ReactNode {
if (typeof children === "string") {
const parsed = parseTextWithCitations(children);
return parsed.length === 1 && typeof parsed[0] === "string" ? children : <>{parsed}</>;
}
if (Array.isArray(children)) {
return children.map((child, index) => {
if (typeof child === "string") {
const parsed = parseTextWithCitations(child);
return parsed.length === 1 && typeof parsed[0] === "string" ? (
child
) : (
<span key={index}>{parsed}</span>
);
}
return child;
});
}
return children;
}
const defaultComponents = memoizeMarkdownComponents({
h1: ({ className, ...props }) => (
h1: ({ className, children, ...props }) => (
<h1
className={cn(
"aui-md-h1 mb-8 scroll-m-20 font-extrabold text-4xl tracking-tight last:mb-0",
className
)}
{...props}
/>
>
{processChildrenWithCitations(children)}
</h1>
),
h2: ({ className, ...props }) => (
h2: ({ className, children, ...props }) => (
<h2
className={cn(
"aui-md-h2 mt-8 mb-4 scroll-m-20 font-semibold text-3xl tracking-tight first:mt-0 last:mb-0",
className
)}
{...props}
/>
>
{processChildrenWithCitations(children)}
</h2>
),
h3: ({ className, ...props }) => (
h3: ({ className, children, ...props }) => (
<h3
className={cn(
"aui-md-h3 mt-6 mb-4 scroll-m-20 font-semibold text-2xl tracking-tight first:mt-0 last:mb-0",
className
)}
{...props}
/>
>
{processChildrenWithCitations(children)}
</h3>
),
h4: ({ className, ...props }) => (
h4: ({ className, children, ...props }) => (
<h4
className={cn(
"aui-md-h4 mt-6 mb-4 scroll-m-20 font-semibold text-xl tracking-tight first:mt-0 last:mb-0",
className
)}
{...props}
/>
>
{processChildrenWithCitations(children)}
</h4>
),
h5: ({ className, ...props }) => (
h5: ({ className, children, ...props }) => (
<h5
className={cn("aui-md-h5 my-4 font-semibold text-lg first:mt-0 last:mb-0", className)}
{...props}
/>
>
{processChildrenWithCitations(children)}
</h5>
),
h6: ({ className, ...props }) => (
<h6 className={cn("aui-md-h6 my-4 font-semibold first:mt-0 last:mb-0", className)} {...props} />
h6: ({ className, children, ...props }) => (
<h6
className={cn("aui-md-h6 my-4 font-semibold first:mt-0 last:mb-0", className)}
{...props}
>
{processChildrenWithCitations(children)}
</h6>
),
p: ({ className, ...props }) => (
<p className={cn("aui-md-p mt-5 mb-5 leading-7 first:mt-0 last:mb-0", className)} {...props} />
p: ({ className, children, ...props }) => (
<p
className={cn("aui-md-p mt-5 mb-5 leading-7 first:mt-0 last:mb-0", className)}
{...props}
>
{processChildrenWithCitations(children)}
</p>
),
a: ({ className, ...props }) => (
a: ({ className, children, ...props }) => (
<a
className={cn("aui-md-a font-medium text-primary underline underline-offset-4", className)}
{...props}
/>
>
{processChildrenWithCitations(children)}
</a>
),
blockquote: ({ className, ...props }) => (
<blockquote className={cn("aui-md-blockquote border-l-2 pl-6 italic", className)} {...props} />
blockquote: ({ className, children, ...props }) => (
<blockquote
className={cn("aui-md-blockquote border-l-2 pl-6 italic", className)}
{...props}
>
{processChildrenWithCitations(children)}
</blockquote>
),
ul: ({ className, ...props }) => (
<ul className={cn("aui-md-ul my-5 ml-6 list-disc [&>li]:mt-2", className)} {...props} />
@ -124,6 +217,11 @@ const defaultComponents = memoizeMarkdownComponents({
ol: ({ className, ...props }) => (
<ol className={cn("aui-md-ol my-5 ml-6 list-decimal [&>li]:mt-2", className)} {...props} />
),
li: ({ className, children, ...props }) => (
<li className={cn("aui-md-li", className)} {...props}>
{processChildrenWithCitations(children)}
</li>
),
hr: ({ className, ...props }) => (
<hr className={cn("aui-md-hr my-5 border-b", className)} {...props} />
),
@ -136,23 +234,27 @@ const defaultComponents = memoizeMarkdownComponents({
{...props}
/>
),
th: ({ className, ...props }) => (
th: ({ className, children, ...props }) => (
<th
className={cn(
"aui-md-th bg-muted px-4 py-2 text-left font-bold first:rounded-tl-lg last:rounded-tr-lg [[align=center]]:text-center [[align=right]]:text-right",
className
)}
{...props}
/>
>
{processChildrenWithCitations(children)}
</th>
),
td: ({ className, ...props }) => (
td: ({ className, children, ...props }) => (
<td
className={cn(
"aui-md-td border-b border-l px-4 py-2 text-left last:border-r [[align=center]]:text-center [[align=right]]:text-right",
className
)}
{...props}
/>
>
{processChildrenWithCitations(children)}
</td>
),
tr: ({ className, ...props }) => (
<tr
@ -187,5 +289,15 @@ const defaultComponents = memoizeMarkdownComponents({
/>
);
},
strong: ({ className, children, ...props }) => (
<strong className={cn("aui-md-strong font-semibold", className)} {...props}>
{processChildrenWithCitations(children)}
</strong>
),
em: ({ className, children, ...props }) => (
<em className={cn("aui-md-em", className)} {...props}>
{processChildrenWithCitations(children)}
</em>
),
CodeHeader,
});

View file

@ -118,7 +118,7 @@ const ThreadSuggestions: FC = () => {
className="aui-thread-welcome-suggestion-display fade-in slide-in-from-bottom-2 @md:nth-[n+3]:block nth-[n+3]:hidden animate-in fill-mode-both duration-200"
style={{ animationDelay: `${100 + index * 50}ms` }}
>
<ThreadPrimitive.Suggestion prompt={suggestion.prompt} send asChild>
<ThreadPrimitive.Suggestion prompt={suggestion.prompt} autoSend asChild>
<Button
variant="ghost"
className="aui-thread-welcome-suggestion h-auto w-full @md:flex-col flex-wrap items-start justify-start gap-1 rounded-2xl border px-4 py-3 text-left text-sm transition-colors hover:bg-muted"

View file

@ -0,0 +1,167 @@
/**
* Custom ChatModelAdapter for the new-chat feature using LocalRuntime.
* Connects directly to the FastAPI backend using the Vercel AI SDK Data Stream Protocol.
*/
import type { ChatModelAdapter, ChatModelRunOptions } from "@assistant-ui/react";
import { getBearerToken } from "@/lib/auth-utils";
interface NewChatAdapterConfig {
searchSpaceId: number;
chatId: number;
}
/**
* Creates a ChatModelAdapter that connects to the FastAPI new_chat endpoint.
*
* The backend expects:
* - POST /api/v1/new_chat
* - Body: { chat_id: number, user_query: string, search_space_id: number }
* - Returns: SSE stream with Vercel AI SDK Data Stream Protocol
*/
export function createNewChatAdapter(config: NewChatAdapterConfig): ChatModelAdapter {
const backendUrl = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL || "http://localhost:8000";
return {
async *run({ messages, abortSignal }: ChatModelRunOptions) {
// Get the last user message
const lastUserMessage = messages.filter((m) => m.role === "user").pop();
if (!lastUserMessage) {
throw new Error("No user message found");
}
// Extract text content from the message
let userQuery = "";
for (const part of lastUserMessage.content) {
if (part.type === "text") {
userQuery += part.text;
}
}
if (!userQuery.trim()) {
throw new Error("User query cannot be empty");
}
const token = getBearerToken();
if (!token) {
throw new Error("Not authenticated. Please log in again.");
}
const response = await fetch(`${backendUrl}/api/v1/new_chat`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
chat_id: config.chatId,
user_query: userQuery.trim(),
search_space_id: config.searchSpaceId,
}),
signal: abortSignal,
});
if (!response.ok) {
const errorText = await response.text().catch(() => "Unknown error");
throw new Error(`Backend error (${response.status}): ${errorText}`);
}
if (!response.body) {
throw new Error("No response body");
}
// Parse the SSE stream (Vercel AI SDK Data Stream Protocol)
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let accumulatedText = "";
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
const chunk = decoder.decode(value, { stream: true });
buffer += chunk;
// Split on double newlines (handle both \n\n and \r\n\r\n)
const events = buffer.split(/\r?\n\r?\n/);
buffer = events.pop() || "";
for (const event of events) {
// Each event can have multiple lines, find the data line
const lines = event.split(/\r?\n/);
for (const line of lines) {
if (!line.startsWith("data: ")) continue;
const data = line.slice(6).trim(); // Remove "data: " prefix
// Handle [DONE] marker
if (data === "[DONE]") {
continue;
}
if (!data) continue;
try {
const parsed = JSON.parse(data);
// Handle different message types from the Data Stream Protocol
switch (parsed.type) {
case "text-delta":
accumulatedText += parsed.delta;
yield {
content: [{ type: "text" as const, text: accumulatedText }],
};
break;
case "error":
throw new Error(parsed.errorText || "Unknown error from server");
// Other types like text-start, text-end, tool-*, etc.
// are handled implicitly - we just accumulate text deltas
default:
break;
}
} catch (e) {
// Skip non-JSON lines
if (e instanceof SyntaxError) {
continue;
}
throw e;
}
}
}
}
// Handle any remaining buffer
if (buffer.trim()) {
const lines = buffer.split(/\r?\n/);
for (const line of lines) {
if (line.startsWith("data: ")) {
const data = line.slice(6).trim();
if (data && data !== "[DONE]") {
try {
const parsed = JSON.parse(data);
if (parsed.type === "text-delta") {
accumulatedText += parsed.delta;
yield {
content: [{ type: "text" as const, text: accumulatedText }],
};
}
} catch {
// Ignore parse errors
}
}
}
}
}
} finally {
reader.releaseLock();
}
},
};
}

View file

@ -22,9 +22,9 @@
},
"dependencies": {
"@ai-sdk/react": "^1.2.12",
"@assistant-ui/react": "^0.11.52",
"@assistant-ui/react-ai-sdk": "^1.1.19",
"@assistant-ui/react-markdown": "^0.11.8",
"@assistant-ui/react": "^0.11.53",
"@assistant-ui/react-ai-sdk": "^1.1.20",
"@assistant-ui/react-markdown": "^0.11.9",
"@blocknote/core": "^0.45.0",
"@blocknote/mantine": "^0.45.0",
"@blocknote/react": "^0.45.0",
@ -100,7 +100,9 @@
"sonner": "^2.0.6",
"tailwind-merge": "^3.3.1",
"tailwindcss-animate": "^1.0.7",
"zod": "^4.2.1"
"unist-util-visit": "^5.0.0",
"zod": "^4.2.1",
"zustand": "^5.0.9"
},
"devDependencies": {
"@biomejs/biome": "2.1.2",

View file

@ -12,14 +12,14 @@ importers:
specifier: ^1.2.12
version: 1.2.12(react@19.2.3)(zod@4.2.1)
'@assistant-ui/react':
specifier: ^0.11.52
version: 0.11.52(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3))
specifier: ^0.11.53
version: 0.11.53(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3))
'@assistant-ui/react-ai-sdk':
specifier: ^1.1.19
version: 1.1.19(@assistant-ui/react@0.11.52(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)))(@types/react@19.2.7)(assistant-cloud@0.1.11)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3))
specifier: ^1.1.20
version: 1.1.20(@assistant-ui/react@0.11.53(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)))(@types/react@19.2.7)(assistant-cloud@0.1.12)(react@19.2.3)
'@assistant-ui/react-markdown':
specifier: ^0.11.8
version: 0.11.8(@assistant-ui/react@0.11.52(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)))(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
specifier: ^0.11.9
version: 0.11.9(@assistant-ui/react@0.11.53(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)))(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@blocknote/core':
specifier: ^0.45.0
version: 0.45.0(@tiptap/extensions@3.14.0(@tiptap/core@3.14.0(@tiptap/pm@3.14.0))(@tiptap/pm@3.14.0))(@types/hast@3.0.4)(highlight.js@11.11.1)
@ -245,9 +245,15 @@ importers:
tailwindcss-animate:
specifier: ^1.0.7
version: 1.0.7(tailwindcss@4.1.18)
unist-util-visit:
specifier: ^5.0.0
version: 5.0.0
zod:
specifier: ^4.2.1
version: 4.2.1
zustand:
specifier: ^5.0.9
version: 5.0.9(@types/react@19.2.7)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3))
devDependencies:
'@biomejs/biome':
specifier: 2.1.2
@ -359,47 +365,50 @@ packages:
'@asamuzakjp/css-color@3.2.0':
resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
'@assistant-ui/react-ai-sdk@1.1.19':
resolution: {integrity: sha512-Xlwsnm9Uaq/SlBKtQcGJgNALMr0uHvqt9r54QdrvM+/PfZV7NQkDZubHzx4WwS0uNBh3Gh62zvPchlfZnBdiaA==}
'@assistant-ui/react-ai-sdk@1.1.20':
resolution: {integrity: sha512-1t+TBUIeNwq7ukb3rLMeSnPeQHrCj5LdwOuvqkYvx5d7dspNMUd2Zh954Gxdie0/iLHGn3ltpjscZeJWSrjSxg==}
peerDependencies:
'@assistant-ui/react': ^0.11.50
'@assistant-ui/react': ^0.11.53
'@types/react': '*'
assistant-cloud: '*'
react: ^18 || ^19 || ^19.0.0-rc
react: ^18 || ^19
peerDependenciesMeta:
'@types/react':
optional: true
assistant-cloud:
optional: true
'@assistant-ui/react-markdown@0.11.8':
resolution: {integrity: sha512-Us7yD9xUGozmmmDuWn+lCp4bylA2sINWyGE6RXr0onmAlwXcOUaw1ZgofKscJXkw6DmC0jrLj7Bdsw8O4IUJhw==}
'@assistant-ui/react-markdown@0.11.9':
resolution: {integrity: sha512-zR0Ty4ID5htJgm4g1TVAbTsyfJZ8XHccDQ0sMODsq/PWAM75l7EmAbxdSKPbvCqny1A/FxvAB4dz1LA17ZgoWg==}
peerDependencies:
'@assistant-ui/react': ^0.11.50
'@assistant-ui/react': ^0.11.53
'@types/react': '*'
react: ^18 || ^19 || ^19.0.0-rc
react: ^18 || ^19
peerDependenciesMeta:
'@types/react':
optional: true
'@assistant-ui/react@0.11.52':
resolution: {integrity: sha512-7lM6IfU9o82wqpOj1wZYI71NQ3jt5OkCh893pvc5utpeJUCkxGKvdx7bRGdMN1XsC14//y7Z4T9bQ16sHtoTjw==}
'@assistant-ui/react@0.11.53':
resolution: {integrity: sha512-G5VB752Somw2Xv4JkGqnloZTxXRu2laHufOROs2H9yOE9Pu+o9aCjW/rn9p8FIev4gWh/ltDouX8T+z9Fh8dJw==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^18 || ^19 || ^19.0.0-rc
react-dom: ^18 || ^19 || ^19.0.0-rc
react: ^18 || ^19
react-dom: ^18 || ^19
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@assistant-ui/tap@0.3.4':
resolution: {integrity: sha512-62Fz1y4EJA7Hk+aStjNS9VTciZ/TUf9SKdv5RfGF65A1Y/REOBzxBDL4ZCeGHCX2dxmwESg5/Kehky+57og3EA==}
'@assistant-ui/tap@0.3.5':
resolution: {integrity: sha512-aI7lOKglkVYy17GrS9EdjSrOmEBmofWPBZ4F5wb96yqEynXflXY3qUAFCgmUwaP/TVkog72+o1ePyvsGphSmJQ==}
peerDependencies:
react: '*'
'@types/react': '*'
react: ^18 || ^19
peerDependenciesMeta:
'@types/react':
optional: true
react:
optional: true
@ -3344,11 +3353,11 @@ packages:
asap@2.0.6:
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
assistant-cloud@0.1.11:
resolution: {integrity: sha512-1ZS7ccwiuy1NxNzIbHKubdCEcqe914YXVYxAR9N3M4csiLdrtUC1/U7KgLXy5Sig2Jp951L/wnN2fLzl302+sA==}
assistant-cloud@0.1.12:
resolution: {integrity: sha512-A2tY6QIdP9+RkE8Mmpm4kAoO0NyKsKpJKYebbYFZ3bAnQKyB15Bw/PS9AovpdeziGU9At97TyiMrT36pDjCD7A==}
assistant-stream@0.2.45:
resolution: {integrity: sha512-gfCwkPcGpfCpwWDzKrgzR5TvKP/ppc+MqotFHPCufbYK8nMft1+GyWGhQp6XXzQR2dC7T/IrqmtEGlGSs8uobA==}
assistant-stream@0.2.46:
resolution: {integrity: sha512-smcC4sqOcTrUO01YpiHPgdG3Wc57kmQlCIEdMXSNuWMgcDvo60hnRY3rPDhZQBJHZOXQ9Q1wLR8ugKDjxi72GQ==}
ast-types-flow@0.0.8:
resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
@ -6790,31 +6799,22 @@ snapshots:
'@csstools/css-tokenizer': 3.0.4
lru-cache: 10.4.3
'@assistant-ui/react-ai-sdk@1.1.19(@assistant-ui/react@0.11.52(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)))(@types/react@19.2.7)(assistant-cloud@0.1.11)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3))':
'@assistant-ui/react-ai-sdk@1.1.20(@assistant-ui/react@0.11.53(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)))(@types/react@19.2.7)(assistant-cloud@0.1.12)(react@19.2.3)':
dependencies:
'@ai-sdk/provider': 2.0.0
'@ai-sdk/react': 2.0.118(react@19.2.3)(zod@4.2.1)
'@assistant-ui/react': 0.11.52(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3))
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.3)
'@types/json-schema': 7.0.15
'@assistant-ui/react': 0.11.53(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3))
ai: 5.0.116(zod@4.2.1)
assistant-stream: 0.2.45
react: 19.2.3
zod: 4.2.1
zustand: 5.0.9(@types/react@19.2.7)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3))
optionalDependencies:
'@types/react': 19.2.7
assistant-cloud: 0.1.11
transitivePeerDependencies:
- immer
- use-sync-external-store
assistant-cloud: 0.1.12
'@assistant-ui/react-markdown@0.11.8(@assistant-ui/react@0.11.52(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)))(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
'@assistant-ui/react-markdown@0.11.9(@assistant-ui/react@0.11.53(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)))(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
'@assistant-ui/react': 0.11.52(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3))
'@assistant-ui/react': 0.11.53(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3))
'@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.3)
'@types/hast': 3.0.4
classnames: 2.5.1
react: 19.2.3
react-markdown: 10.1.0(@types/react@19.2.7)(react@19.2.3)
@ -6825,9 +6825,9 @@ snapshots:
- react-dom
- supports-color
'@assistant-ui/react@0.11.52(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3))':
'@assistant-ui/react@0.11.53(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3))':
dependencies:
'@assistant-ui/tap': 0.3.4(react@19.2.3)
'@assistant-ui/tap': 0.3.5(@types/react@19.2.7)(react@19.2.3)
'@radix-ui/primitive': 1.1.3
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.7)(react@19.2.3)
'@radix-ui/react-context': 1.1.3(@types/react@19.2.7)(react@19.2.3)
@ -6836,9 +6836,8 @@ snapshots:
'@radix-ui/react-slot': 1.2.4(@types/react@19.2.7)(react@19.2.3)
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.7)(react@19.2.3)
'@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.7)(react@19.2.3)
'@standard-schema/spec': 1.1.0
assistant-cloud: 0.1.11
assistant-stream: 0.2.45
assistant-cloud: 0.1.12
assistant-stream: 0.2.46
nanoid: 5.1.6
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
@ -6852,8 +6851,9 @@ snapshots:
- immer
- use-sync-external-store
'@assistant-ui/tap@0.3.4(react@19.2.3)':
'@assistant-ui/tap@0.3.5(@types/react@19.2.7)(react@19.2.3)':
optionalDependencies:
'@types/react': 19.2.7
react: 19.2.3
'@babel/runtime@7.28.4': {}
@ -10119,13 +10119,13 @@ snapshots:
asap@2.0.6: {}
assistant-cloud@0.1.11:
assistant-cloud@0.1.12:
dependencies:
assistant-stream: 0.2.45
assistant-stream: 0.2.46
assistant-stream@0.2.45:
assistant-stream@0.2.46:
dependencies:
'@types/json-schema': 7.0.15
'@standard-schema/spec': 1.1.0
nanoid: 5.1.6
secure-json-parse: 4.1.0