Skip to content

Commit bc447dc

Browse files
committed
feat: upgrade to ai sdk 5
1 parent 52cf2ce commit bc447dc

File tree

18 files changed

+2902
-519
lines changed

18 files changed

+2902
-519
lines changed

.changeset/remove-astro-docs.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
---
3+
4+
Remove accidentally committed Astro docs build artifacts

client/dashboard/package.json

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,36 @@
1212
},
1313
"dependencies": {
1414
"@ai-sdk/openai": "catalog:",
15-
"@ai-sdk/react": "^1.2.12",
15+
"@ai-sdk/react": "catalog:",
16+
"@ai-sdk/ui-utils": "catalog:",
1617
"@datadog/browser-rum": "^6.20.0",
1718
"@dnd-kit/core": "^6.3.1",
1819
"@dnd-kit/modifiers": "^9.0.0",
1920
"@dnd-kit/sortable": "^10.0.0",
2021
"@dnd-kit/utilities": "^3.2.2",
2122
"@gram/client": "workspace:*",
22-
"@openrouter/ai-sdk-provider": "0.7.5",
23+
"@openrouter/ai-sdk-provider": "^1.2.0",
2324
"@polar-sh/checkout": "^0.1.11",
2425
"@radix-ui/react-accordion": "^1.2.12",
2526
"@radix-ui/react-avatar": "^1.1.10",
2627
"@radix-ui/react-checkbox": "^1.3.3",
28+
"@radix-ui/react-collapsible": "^1.1.12",
2729
"@radix-ui/react-dialog": "^1.1.15",
2830
"@radix-ui/react-dropdown-menu": "^2.1.16",
2931
"@radix-ui/react-hover-card": "^1.1.15",
3032
"@radix-ui/react-label": "^2.1.7",
3133
"@radix-ui/react-popover": "^1.1.15",
34+
"@radix-ui/react-progress": "^1.1.7",
3235
"@radix-ui/react-radio-group": "^1.3.8",
36+
"@radix-ui/react-scroll-area": "^1.2.10",
3337
"@radix-ui/react-select": "^2.2.6",
3438
"@radix-ui/react-separator": "^1.1.7",
3539
"@radix-ui/react-slot": "^1.2.3",
3640
"@radix-ui/react-tabs": "^1.1.13",
3741
"@radix-ui/react-toggle": "^1.1.10",
3842
"@radix-ui/react-toggle-group": "^1.1.11",
3943
"@radix-ui/react-tooltip": "^1.2.8",
44+
"@radix-ui/react-use-controllable-state": "^1.2.2",
4045
"@react-three/drei": "^10.0.7",
4146
"@react-three/fiber": "^9.1.2",
4247
"@react-three/postprocessing": "^3.0.4",
@@ -45,12 +50,14 @@
4550
"@tailwindcss/vite": "^4.1.13",
4651
"@tanstack/react-query": "catalog:",
4752
"@tanstack/react-table": "^8.21.3",
53+
"@xyflow/react": "^12.8.6",
4854
"ai": "catalog:",
4955
"class-variance-authority": "^0.7.1",
5056
"clsx": "^2.1.1",
5157
"cmdk": "^1.1.1",
5258
"date-fns": "^4.1.0",
5359
"embla-carousel-react": "^8.6.0",
60+
"hast": "^1.0.0",
5461
"lucide-react": "^0.544.0",
5562
"motion": "^12.23.14",
5663
"motion-plus": "^1.5.1",
@@ -63,22 +70,29 @@
6370
"react-error-boundary": "^6.0.0",
6471
"react-merge-refs": "^3.0.2",
6572
"react-router": "^7.9.1",
73+
"react-syntax-highlighter": "^15.6.6",
74+
"shiki": "^3.14.0",
6675
"sonner": "^2.0.7",
76+
"streamdown": "^1.3.0",
6777
"tailwind-merge": "^3.3.1",
6878
"tailwindcss": "^4.1.13",
6979
"three": "^0.176.0",
80+
"tokenlens": "^1.3.1",
7081
"tunnel-rat": "^0.1.2",
7182
"tw-animate-css": "^1.3.8",
83+
"use-stick-to-bottom": "^1.1.1",
7284
"uuid": "^13.0.0",
7385
"vaul": "^1.1.2",
7486
"zod": "^3.20.0",
7587
"zustand": "^5.0.4"
7688
},
7789
"devDependencies": {
7890
"@eslint/js": "^9.35.0",
91+
"@types/hast": "^3.0.4",
7992
"@types/node": "^24.5.2",
8093
"@types/react": "catalog:",
8194
"@types/react-dom": "catalog:",
95+
"@types/react-syntax-highlighter": "^15.5.13",
8296
"@types/three": "^0.180.0",
8397
"@vitejs/plugin-react": "^5.0.3",
8498
"eslint": "^9.35.0",
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
"use client";
2+
3+
import { Button } from "@/components/ui/button";
4+
import { cn } from "@/lib/utils";
5+
import type { Element } from "hast";
6+
import { CheckIcon, CopyIcon } from "lucide-react";
7+
import {
8+
type ComponentProps,
9+
createContext,
10+
type HTMLAttributes,
11+
useContext,
12+
useEffect,
13+
useRef,
14+
useState,
15+
} from "react";
16+
import { type BundledLanguage, codeToHtml, type ShikiTransformer } from "shiki";
17+
18+
type CodeBlockProps = HTMLAttributes<HTMLDivElement> & {
19+
code: string;
20+
language: BundledLanguage;
21+
showLineNumbers?: boolean;
22+
};
23+
24+
type CodeBlockContextType = {
25+
code: string;
26+
};
27+
28+
const CodeBlockContext = createContext<CodeBlockContextType>({
29+
code: "",
30+
});
31+
32+
const lineNumberTransformer: ShikiTransformer = {
33+
name: "line-numbers",
34+
line(node: Element, line: number) {
35+
node.children.unshift({
36+
type: "element",
37+
tagName: "span",
38+
properties: {
39+
className: [
40+
"inline-block",
41+
"min-w-10",
42+
"mr-4",
43+
"text-right",
44+
"select-none",
45+
"text-muted-foreground",
46+
],
47+
},
48+
children: [{ type: "text", value: String(line) }],
49+
});
50+
},
51+
};
52+
53+
export async function highlightCode(
54+
code: string,
55+
language: BundledLanguage,
56+
showLineNumbers = false,
57+
) {
58+
const transformers: ShikiTransformer[] = showLineNumbers
59+
? [lineNumberTransformer]
60+
: [];
61+
62+
return await Promise.all([
63+
codeToHtml(code, {
64+
lang: language,
65+
theme: "one-light",
66+
transformers,
67+
}),
68+
codeToHtml(code, {
69+
lang: language,
70+
theme: "one-dark-pro",
71+
transformers,
72+
}),
73+
]);
74+
}
75+
76+
export const CodeBlock = ({
77+
code,
78+
language,
79+
showLineNumbers = false,
80+
className,
81+
children,
82+
...props
83+
}: CodeBlockProps) => {
84+
const [html, setHtml] = useState<string>("");
85+
const [darkHtml, setDarkHtml] = useState<string>("");
86+
const mounted = useRef(false);
87+
88+
useEffect(() => {
89+
highlightCode(code, language, showLineNumbers).then(([light, dark]) => {
90+
if (!mounted.current) {
91+
setHtml(light);
92+
setDarkHtml(dark);
93+
mounted.current = true;
94+
}
95+
});
96+
97+
return () => {
98+
mounted.current = false;
99+
};
100+
}, [code, language, showLineNumbers]);
101+
102+
return (
103+
<CodeBlockContext.Provider value={{ code }}>
104+
<div
105+
className={cn(
106+
"group relative w-full overflow-hidden rounded-md border bg-background text-foreground",
107+
className,
108+
)}
109+
{...props}
110+
>
111+
<div className="relative">
112+
<div
113+
className="overflow-hidden dark:hidden [&>pre]:m-0 [&>pre]:bg-background! [&>pre]:p-4 [&>pre]:text-foreground! [&>pre]:text-sm [&_code]:font-mono [&_code]:text-sm"
114+
// biome-ignore lint/security/noDangerouslySetInnerHtml: "this is needed."
115+
dangerouslySetInnerHTML={{ __html: html }}
116+
/>
117+
<div
118+
className="hidden overflow-hidden dark:block [&>pre]:m-0 [&>pre]:bg-background! [&>pre]:p-4 [&>pre]:text-foreground! [&>pre]:text-sm [&_code]:font-mono [&_code]:text-sm"
119+
// biome-ignore lint/security/noDangerouslySetInnerHtml: "this is needed."
120+
dangerouslySetInnerHTML={{ __html: darkHtml }}
121+
/>
122+
{children && (
123+
<div className="absolute top-2 right-2 flex items-center gap-2">
124+
{children}
125+
</div>
126+
)}
127+
</div>
128+
</div>
129+
</CodeBlockContext.Provider>
130+
);
131+
};
132+
133+
export type CodeBlockCopyButtonProps = ComponentProps<typeof Button> & {
134+
onCopy?: () => void;
135+
onError?: (error: Error) => void;
136+
timeout?: number;
137+
};
138+
139+
export const CodeBlockCopyButton = ({
140+
onCopy,
141+
onError,
142+
timeout = 2000,
143+
children,
144+
className,
145+
...props
146+
}: CodeBlockCopyButtonProps) => {
147+
const [isCopied, setIsCopied] = useState(false);
148+
const { code } = useContext(CodeBlockContext);
149+
150+
const copyToClipboard = async () => {
151+
if (typeof window === "undefined" || !navigator?.clipboard?.writeText) {
152+
onError?.(new Error("Clipboard API not available"));
153+
return;
154+
}
155+
156+
try {
157+
await navigator.clipboard.writeText(code);
158+
setIsCopied(true);
159+
onCopy?.();
160+
setTimeout(() => setIsCopied(false), timeout);
161+
} catch (error) {
162+
onError?.(error as Error);
163+
}
164+
};
165+
166+
const Icon = isCopied ? CheckIcon : CopyIcon;
167+
168+
return (
169+
<Button
170+
className={cn("shrink-0", className)}
171+
onClick={copyToClipboard}
172+
size="icon"
173+
variant="ghost"
174+
{...props}
175+
>
176+
{children ?? <Icon size={14} />}
177+
</Button>
178+
);
179+
};

0 commit comments

Comments
 (0)