chore: import upstream snapshot with attribution
Changesets / Create Version PR (push) Has been cancelled
Deploy Shadcn Registry / Deploy Production (push) Has been cancelled
Template Metrics / LOC + Bundle Size (push) Has been cancelled
Code Quality / Oxlint + Oxfmt (push) Has been cancelled
Code Quality / Template Sync (push) Has been cancelled
Code Quality / Build Changed Packages (push) Has been cancelled
Code Quality / Test Changed Packages (push) Has been cancelled
Deploy Expo Example / Deploy Production (push) Has been cancelled
Deploy Ink Example / Deploy Production (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-stream, 3.12) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.10) (push) Has been cancelled
Python Tests / pytest (assistant-ui-sync-server-api, 3.12) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:40:13 +08:00
commit e30e75b5d4
3893 changed files with 533074 additions and 0 deletions
@@ -0,0 +1,104 @@
import { useState, type ReactNode } from "react";
import {
useExternalStoreRuntime,
type ThreadMessageLike,
type AppendMessage,
AssistantRuntimeProvider,
} from "@assistant-ui/react";
import { chatStream } from "@/server/chat";
type MyMessage = {
id: string;
role: "user" | "assistant";
content: string;
};
const generateId = () => Math.random().toString(36).substring(2, 9);
const convertMessage = (message: MyMessage): ThreadMessageLike => {
return {
id: message.id,
role: message.role,
content: [{ type: "text", text: message.content }],
};
};
export function MyRuntimeProvider({
children,
}: Readonly<{
children: ReactNode;
}>) {
const [isRunning, setIsRunning] = useState(false);
const [messages, setMessages] = useState<MyMessage[]>([]);
const onNew = async (message: AppendMessage) => {
if (message.content[0]?.type !== "text")
throw new Error("Only text messages are supported");
const input = message.content[0].text;
const userMessage: MyMessage = {
id: generateId(),
role: "user",
content: input,
};
// Add user message
const updatedMessages = [...messages, userMessage];
setMessages(updatedMessages);
// Create placeholder for assistant message
setIsRunning(true);
const assistantId = generateId();
const assistantMessage: MyMessage = {
id: assistantId,
role: "assistant",
content: "",
};
setMessages((prev) => [...prev, assistantMessage]);
try {
// Stream response using async generator
const stream = await chatStream({
data: {
messages: updatedMessages.map((m) => ({
role: m.role,
content: m.content,
})),
},
});
// Handle streaming chunks
for await (const chunk of stream) {
setMessages((prev) =>
prev.map((m) =>
m.id === assistantId ? { ...m, content: m.content + chunk } : m,
),
);
}
} catch (error) {
console.error("Chat error:", error);
setMessages((prev) =>
prev.map((m) =>
m.id === assistantId
? { ...m, content: "Sorry, an error occurred. Please try again." }
: m,
),
);
} finally {
setIsRunning(false);
}
};
const runtime = useExternalStoreRuntime({
isRunning,
messages,
convertMessage,
onNew,
});
return (
<AssistantRuntimeProvider runtime={runtime}>
{children}
</AssistantRuntimeProvider>
);
}
@@ -0,0 +1,68 @@
/* eslint-disable */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root'
import { Route as IndexRouteImport } from './routes/index'
const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
getParentRoute: () => rootRouteImport,
} as any)
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths: '/'
fileRoutesByTo: FileRoutesByTo
to: '/'
id: '__root__' | '/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
}
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
'/': {
id: '/'
path: '/'
fullPath: '/'
preLoaderRoute: typeof IndexRouteImport
parentRoute: typeof rootRouteImport
}
}
}
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>()
import type { getRouter } from './router.tsx'
import type { createStart } from '@tanstack/react-start'
declare module '@tanstack/react-start' {
interface Register {
ssr: true
router: Awaited<ReturnType<typeof getRouter>>
}
}
+15
View File
@@ -0,0 +1,15 @@
import { createRouter } from "@tanstack/react-router";
// Import the generated route tree
import { routeTree } from "./routeTree.gen";
// Create a new router instance
export const getRouter = () => {
const router = createRouter({
routeTree,
scrollRestoration: true,
defaultPreloadStaleTime: 0,
});
return router;
};
@@ -0,0 +1,42 @@
import { HeadContent, Scripts, createRootRoute } from "@tanstack/react-router";
import appCss from "../styles.css?url";
export const Route = createRootRoute({
head: () => ({
meta: [
{
charSet: "utf-8",
},
{
name: "viewport",
content: "width=device-width, initial-scale=1",
},
{
title: "assistant-ui + TanStack Start",
},
],
links: [
{
rel: "stylesheet",
href: appCss,
},
],
}),
shellComponent: RootDocument,
});
function RootDocument({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<HeadContent />
</head>
<body className="bg-background text-foreground">
{children}
<Scripts />
</body>
</html>
);
}
@@ -0,0 +1,38 @@
import { createFileRoute } from "@tanstack/react-router";
import { Thread } from "@/components/assistant-ui/thread";
import { useAui, AuiProvider, Suggestions } from "@assistant-ui/react";
import { MyRuntimeProvider } from "@/components/MyRuntimeProvider";
export const Route = createFileRoute("/")({ component: App });
function ThreadWithSuggestions() {
const aui = useAui({
suggestions: Suggestions([
{
title: "Hello!",
label: "start a conversation",
prompt: "Hello! What can you help me with?",
},
{
title: "What can you do?",
label: "tell me your capabilities",
prompt: "What kinds of things can you help me with?",
},
]),
});
return (
<AuiProvider value={aui}>
<Thread />
</AuiProvider>
);
}
function App() {
return (
<MyRuntimeProvider>
<main className="h-dvh">
<ThreadWithSuggestions />
</main>
</MyRuntimeProvider>
);
}
+32
View File
@@ -0,0 +1,32 @@
import { createServerFn } from "@tanstack/react-start";
import OpenAI from "openai";
type ChatMessage = {
role: "user" | "assistant" | "system";
content: string;
};
type ChatInput = {
messages: ChatMessage[];
};
export const chatStream = createServerFn({ method: "POST" })
.inputValidator((data: ChatInput) => data)
.handler(async function* ({ data }) {
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
const stream = await openai.chat.completions.create({
model: "gpt-5.4-nano",
messages: data.messages,
stream: true,
});
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content;
if (content) {
yield content;
}
}
});
+253
View File
@@ -0,0 +1,253 @@
@import "tailwindcss";
@source "../../../packages/ui/src";
@custom-variant dark (&:is(.dark *));
@theme inline {
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
}
:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.141 0.005 285.823);
--card: oklch(1 0 0);
--card-foreground: oklch(0.141 0.005 285.823);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.141 0.005 285.823);
--primary: oklch(0.21 0.006 285.885);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.967 0.001 286.375);
--muted-foreground: oklch(0.552 0.016 285.938);
--accent: oklch(0.967 0.001 286.375);
--accent-foreground: oklch(0.21 0.006 285.885);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.92 0.004 286.32);
--input: oklch(0.92 0.004 286.32);
--ring: oklch(0.705 0.015 286.067);
}
.dark {
--background: oklch(0.141 0.005 285.823);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.006 285.885);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.006 285.885);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.92 0.004 286.32);
--primary-foreground: oklch(0.21 0.006 285.885);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.274 0.006 286.033);
--muted-foreground: oklch(0.705 0.015 286.067);
--accent: oklch(0.274 0.006 286.033);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.552 0.016 285.938);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground m-0;
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family:
source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
}
}
/* Animation utilities for assistant-ui */
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slide-in-from-bottom-1 {
from {
transform: translateY(0.25rem);
}
to {
transform: translateY(0);
}
}
@keyframes slide-in-from-bottom-2 {
from {
transform: translateY(0.5rem);
}
to {
transform: translateY(0);
}
}
@keyframes slide-in-from-bottom-4 {
from {
transform: translateY(1rem);
}
to {
transform: translateY(0);
}
}
@keyframes zoom-in-95 {
from {
transform: scale(0.95);
}
to {
transform: scale(1);
}
}
@keyframes zoom-out-95 {
to {
transform: scale(0.95);
}
}
@keyframes fade-out {
to {
opacity: 0;
}
}
@keyframes slide-in-from-top-2 {
from {
transform: translateY(-0.5rem);
}
to {
transform: translateY(0);
}
}
@keyframes slide-in-from-left-2 {
from {
transform: translateX(-0.5rem);
}
to {
transform: translateX(0);
}
}
@keyframes slide-in-from-right-2 {
from {
transform: translateX(0.5rem);
}
to {
transform: translateX(0);
}
}
.animate-in {
animation-duration: 150ms;
animation-timing-function: ease-out;
animation-fill-mode: both;
}
.fade-in {
animation-name: fade-in;
}
.fade-in-0 {
animation-name: fade-in;
}
.zoom-in-95 {
animation-name: zoom-in-95;
}
.slide-in-from-bottom-1 {
animation-name: fade-in, slide-in-from-bottom-1;
}
.slide-in-from-bottom-2 {
animation-name: fade-in, slide-in-from-bottom-2;
}
.slide-in-from-bottom-4 {
animation-name: fade-in, slide-in-from-bottom-4;
}
.slide-in-from-top-2 {
animation-name: fade-in, slide-in-from-top-2;
}
.slide-in-from-left-2 {
animation-name: fade-in, slide-in-from-left-2;
}
.slide-in-from-right-2 {
animation-name: fade-in, slide-in-from-right-2;
}
[data-state="closed"].animate-out {
animation-duration: 150ms;
animation-timing-function: ease-in;
animation-fill-mode: both;
}
[data-state="closed"].fade-out-0 {
animation-name: fade-out;
}
[data-state="closed"].zoom-out-95 {
animation-name: zoom-out-95;
}
.delay-100 {
animation-delay: 100ms;
}
.fill-mode-both {
animation-fill-mode: both;
}
.duration-150 {
animation-duration: 150ms;
}
.duration-300 {
animation-duration: 300ms;
}
.ease-out {
animation-timing-function: ease-out;
}