@chat-js/cli 0.4.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/index.js +1548 -969
  2. package/package.json +4 -3
  3. package/templates/chat-app/app/(auth)/device-login/page.tsx +37 -0
  4. package/templates/chat-app/app/(auth)/login/page.tsx +26 -2
  5. package/templates/chat-app/app/(auth)/register/page.tsx +0 -12
  6. package/templates/chat-app/app/(chat)/api/chat/filter-reasoning-parts.ts +1 -1
  7. package/templates/chat-app/app/(chat)/api/chat/route.ts +13 -5
  8. package/templates/chat-app/app/(chat)/layout.tsx +4 -1
  9. package/templates/chat-app/app/api/trpc/[trpc]/route.ts +1 -0
  10. package/templates/chat-app/app/globals.css +9 -9
  11. package/templates/chat-app/app/layout.tsx +4 -2
  12. package/templates/chat-app/biome.jsonc +3 -3
  13. package/templates/chat-app/chat.config.ts +144 -141
  14. package/templates/chat-app/components/ai-elements/prompt-input.tsx +1 -1
  15. package/templates/chat-app/components/anonymous-session-init.tsx +10 -6
  16. package/templates/chat-app/components/artifact-actions.tsx +81 -18
  17. package/templates/chat-app/components/artifact-panel.tsx +142 -41
  18. package/templates/chat-app/components/attachment-list.tsx +1 -1
  19. package/templates/chat-app/components/{social-auth-providers.tsx → auth-providers.tsx} +49 -4
  20. package/templates/chat-app/components/chat/chat-welcome.tsx +3 -3
  21. package/templates/chat-app/components/chat-menu-items.tsx +1 -1
  22. package/templates/chat-app/components/chat-sync.tsx +3 -8
  23. package/templates/chat-app/components/console.tsx +9 -9
  24. package/templates/chat-app/components/context-usage.tsx +2 -2
  25. package/templates/chat-app/components/create-artifact.tsx +15 -5
  26. package/templates/chat-app/components/data-stream-handler.tsx +57 -16
  27. package/templates/chat-app/components/device-login-page.tsx +191 -0
  28. package/templates/chat-app/components/diffview.tsx +8 -2
  29. package/templates/chat-app/components/electron-auth-handler.tsx +184 -0
  30. package/templates/chat-app/components/electron-auth-ui.tsx +121 -0
  31. package/templates/chat-app/components/favicon-group.tsx +1 -1
  32. package/templates/chat-app/components/feedback-actions.tsx +1 -1
  33. package/templates/chat-app/components/greeting.tsx +1 -1
  34. package/templates/chat-app/components/interactive-chart-impl.tsx +3 -4
  35. package/templates/chat-app/components/interactive-charts.tsx +1 -1
  36. package/templates/chat-app/components/login-form.tsx +52 -10
  37. package/templates/chat-app/components/message-editor.tsx +4 -5
  38. package/templates/chat-app/components/model-selector.tsx +661 -655
  39. package/templates/chat-app/components/multimodal-input.tsx +13 -10
  40. package/templates/chat-app/components/parallel-response-cards.tsx +53 -35
  41. package/templates/chat-app/components/part/code-execution.tsx +8 -2
  42. package/templates/chat-app/components/part/document-common.tsx +1 -1
  43. package/templates/chat-app/components/part/document-preview.tsx +5 -5
  44. package/templates/chat-app/components/part/retrieve-url.tsx +12 -12
  45. package/templates/chat-app/components/part/text-message-part.tsx +13 -9
  46. package/templates/chat-app/components/project-chat-item.tsx +1 -1
  47. package/templates/chat-app/components/project-menu-items.tsx +1 -1
  48. package/templates/chat-app/components/research-task.tsx +1 -1
  49. package/templates/chat-app/components/research-tasks.tsx +1 -1
  50. package/templates/chat-app/components/retry-button.tsx +1 -1
  51. package/templates/chat-app/components/sandbox.tsx +1 -1
  52. package/templates/chat-app/components/sheet-editor.tsx +7 -7
  53. package/templates/chat-app/components/sidebar-chats-list.tsx +1 -1
  54. package/templates/chat-app/components/sidebar-toggle.tsx +15 -2
  55. package/templates/chat-app/components/sidebar-top-row.tsx +27 -12
  56. package/templates/chat-app/components/sidebar-user-nav.tsx +10 -1
  57. package/templates/chat-app/components/signup-form.tsx +49 -10
  58. package/templates/chat-app/components/sources.tsx +4 -4
  59. package/templates/chat-app/components/text-editor.tsx +5 -2
  60. package/templates/chat-app/components/toolbar.tsx +3 -3
  61. package/templates/chat-app/components/ui/sidebar.tsx +0 -1
  62. package/templates/chat-app/components/upgrade-cta/limit-display.tsx +1 -1
  63. package/templates/chat-app/components/user-message.tsx +135 -134
  64. package/templates/chat-app/electron.d.ts +41 -0
  65. package/templates/chat-app/evals/my-eval.eval.ts +3 -1
  66. package/templates/chat-app/hooks/use-artifact.tsx +13 -13
  67. package/templates/chat-app/lib/ai/gateways/provider-types.ts +19 -10
  68. package/templates/chat-app/lib/ai/stream-errors.test.ts +72 -0
  69. package/templates/chat-app/lib/ai/stream-errors.ts +94 -0
  70. package/templates/chat-app/lib/ai/tools/code-execution.javascript.ts +171 -0
  71. package/templates/chat-app/lib/ai/tools/code-execution.python.ts +336 -0
  72. package/templates/chat-app/lib/ai/tools/code-execution.shared.test.ts +71 -0
  73. package/templates/chat-app/lib/ai/tools/code-execution.shared.ts +59 -0
  74. package/templates/chat-app/lib/ai/tools/code-execution.ts +62 -391
  75. package/templates/chat-app/lib/ai/tools/code-execution.types.ts +24 -0
  76. package/templates/chat-app/lib/ai/tools/steps/multi-query-web-search.ts +3 -2
  77. package/templates/chat-app/lib/anonymous-session-client.ts +0 -3
  78. package/templates/chat-app/lib/artifacts/code/client.tsx +35 -5
  79. package/templates/chat-app/lib/artifacts/sheet/client.tsx +11 -3
  80. package/templates/chat-app/lib/auth-client.ts +23 -1
  81. package/templates/chat-app/lib/auth.ts +18 -1
  82. package/templates/chat-app/lib/blob.ts +1 -1
  83. package/templates/chat-app/lib/clone-messages.ts +1 -1
  84. package/templates/chat-app/lib/config-schema.ts +13 -1
  85. package/templates/chat-app/lib/constants.ts +3 -4
  86. package/templates/chat-app/lib/db/migrations/meta/0044_snapshot.json +42 -129
  87. package/templates/chat-app/lib/db/migrations/meta/_journal.json +1 -1
  88. package/templates/chat-app/lib/editor/config.ts +4 -4
  89. package/templates/chat-app/lib/electron-auth.ts +96 -0
  90. package/templates/chat-app/lib/env-schema.ts +33 -4
  91. package/templates/chat-app/lib/message-conversion.ts +1 -1
  92. package/templates/chat-app/lib/playwright-test-environment.ts +18 -0
  93. package/templates/chat-app/lib/social-auth.ts +5 -0
  94. package/templates/chat-app/lib/stores/hooks-threads.ts +2 -1
  95. package/templates/chat-app/lib/stores/with-threads.test.ts +1 -1
  96. package/templates/chat-app/lib/stores/with-threads.ts +5 -6
  97. package/templates/chat-app/lib/stores/with-tracing.ts +1 -1
  98. package/templates/chat-app/lib/thread-utils.ts +19 -21
  99. package/templates/chat-app/lib/utils/download-assets.ts +6 -7
  100. package/templates/chat-app/lib/utils/rate-limit.ts +9 -3
  101. package/templates/chat-app/package.json +22 -19
  102. package/templates/chat-app/playwright.config.ts +0 -19
  103. package/templates/chat-app/providers/chat-input-provider.tsx +1 -1
  104. package/templates/chat-app/proxy.ts +28 -3
  105. package/templates/chat-app/scripts/check-env.ts +10 -0
  106. package/templates/chat-app/trpc/server.tsx +7 -2
  107. package/templates/chat-app/tsconfig.json +2 -1
  108. package/templates/chat-app/vercel.json +0 -10
  109. package/templates/electron/CHANGELOG.md +7 -0
  110. package/templates/electron/README.md +54 -0
  111. package/templates/electron/entitlements.mac.plist +10 -0
  112. package/templates/electron/forge.config.ts +152 -0
  113. package/templates/electron/icon.png +0 -0
  114. package/templates/electron/package.json +53 -0
  115. package/templates/electron/scripts/generate-icons.test.js +37 -0
  116. package/templates/electron/scripts/generate-icons.ts +29 -0
  117. package/templates/electron/scripts/run-forge.cjs +28 -0
  118. package/templates/electron/scripts/write-branding.ts +18 -0
  119. package/templates/electron/src/config.ts +16 -0
  120. package/templates/electron/src/lib/auth-client.ts +64 -0
  121. package/templates/electron/src/main.ts +670 -0
  122. package/templates/electron/src/preload.d.ts +27 -0
  123. package/templates/electron/src/preload.ts +25 -0
  124. package/templates/electron/tsconfig.json +18 -0
@@ -1,8 +1,21 @@
1
1
  import { type Dispatch, memo, type SetStateAction, useState } from "react";
2
2
  import { toast } from "sonner";
3
+ import type {
4
+ Artifact,
5
+ ArtifactActionContext,
6
+ ArtifactMetadata,
7
+ } from "@/components/create-artifact";
8
+ import {
9
+ codeArtifact,
10
+ getCodeArtifactMetadata,
11
+ } from "@/lib/artifacts/code/client";
12
+ import {
13
+ getSheetArtifactMetadata,
14
+ sheetArtifact,
15
+ } from "@/lib/artifacts/sheet/client";
16
+ import { textArtifact } from "@/lib/artifacts/text/client";
3
17
  import { cn } from "@/lib/utils";
4
- import { artifactDefinitions, type UIArtifact } from "./artifact-panel";
5
- import type { ArtifactActionContext } from "./create-artifact";
18
+ import type { UIArtifact } from "./artifact-panel";
6
19
  import { Button } from "./ui/button";
7
20
  import { Toggle } from "./ui/toggle";
8
21
  import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip";
@@ -13,13 +26,33 @@ interface ArtifactActionsProps {
13
26
  handleVersionChange: (type: "next" | "prev" | "toggle" | "latest") => void;
14
27
  isCurrentVersion: boolean;
15
28
  isReadonly: boolean;
16
- metadata: any;
29
+ metadata: ArtifactMetadata;
17
30
  mode: "edit" | "diff";
18
- setMetadata: Dispatch<SetStateAction<any>>;
31
+ setMetadata: Dispatch<SetStateAction<ArtifactMetadata>>;
19
32
  }
20
33
 
21
- function PureArtifactActions({
34
+ function createTypedMetadataSetter<M extends ArtifactMetadata>(
35
+ setMetadata: Dispatch<SetStateAction<ArtifactMetadata>>,
36
+ coerce: (metadata: ArtifactMetadata) => M
37
+ ): Dispatch<SetStateAction<M>> {
38
+ return (value) => {
39
+ setMetadata((current) => {
40
+ const typedCurrent = coerce(current);
41
+ return typeof value === "function" ? value(typedCurrent) : value;
42
+ });
43
+ };
44
+ }
45
+
46
+ interface TypedArtifactActionsProps<M extends ArtifactMetadata>
47
+ extends Omit<ArtifactActionsProps, "metadata" | "setMetadata"> {
48
+ artifactDefinition: Artifact<string, M>;
49
+ metadata: M;
50
+ setMetadata: Dispatch<SetStateAction<M>>;
51
+ }
52
+
53
+ function TypedArtifactActions<M extends ArtifactMetadata>({
22
54
  artifact,
55
+ artifactDefinition,
23
56
  handleVersionChange,
24
57
  currentVersionIndex,
25
58
  isCurrentVersion,
@@ -27,18 +60,10 @@ function PureArtifactActions({
27
60
  metadata,
28
61
  setMetadata,
29
62
  isReadonly,
30
- }: ArtifactActionsProps) {
63
+ }: TypedArtifactActionsProps<M>) {
31
64
  const [isLoading, setIsLoading] = useState(false);
32
65
 
33
- const artifactDefinition = artifactDefinitions.find(
34
- (definition) => definition.kind === artifact.kind
35
- );
36
-
37
- if (!artifactDefinition) {
38
- throw new Error("Artifact definition not found!");
39
- }
40
-
41
- const actionContext: ArtifactActionContext = {
66
+ const actionContext: ArtifactActionContext<M> = {
42
67
  content: artifact.content,
43
68
  handleVersionChange,
44
69
  currentVersionIndex,
@@ -50,7 +75,7 @@ function PureArtifactActions({
50
75
  };
51
76
 
52
77
  function isActionDisabled(action: {
53
- isDisabled?: (context: ArtifactActionContext) => boolean;
78
+ isDisabled?: (context: ArtifactActionContext<M>) => boolean;
54
79
  }): boolean {
55
80
  if (isLoading || artifact.status === "streaming") {
56
81
  return true;
@@ -106,7 +131,7 @@ function PureArtifactActions({
106
131
  </div>
107
132
  ) : (
108
133
  <Button
109
- className={cn("h-fit dark:hover:bg-zinc-700", {
134
+ className={cn("h-fit hover:bg-accent", {
110
135
  "p-2": !action.label,
111
136
  "px-2 py-1.5": action.label,
112
137
  })}
@@ -137,7 +162,45 @@ function PureArtifactActions({
137
162
  }
138
163
 
139
164
  export const ArtifactActions = memo(
140
- PureArtifactActions,
165
+ function ArtifactActions(props: ArtifactActionsProps) {
166
+ switch (props.artifact.kind) {
167
+ case "code":
168
+ return (
169
+ <TypedArtifactActions
170
+ {...props}
171
+ artifactDefinition={codeArtifact}
172
+ metadata={getCodeArtifactMetadata(props.metadata)}
173
+ setMetadata={createTypedMetadataSetter(
174
+ props.setMetadata,
175
+ getCodeArtifactMetadata
176
+ )}
177
+ />
178
+ );
179
+ case "sheet":
180
+ return (
181
+ <TypedArtifactActions
182
+ {...props}
183
+ artifactDefinition={sheetArtifact}
184
+ metadata={getSheetArtifactMetadata(props.metadata)}
185
+ setMetadata={createTypedMetadataSetter(
186
+ props.setMetadata,
187
+ getSheetArtifactMetadata
188
+ )}
189
+ />
190
+ );
191
+ case "text":
192
+ return (
193
+ <TypedArtifactActions
194
+ {...props}
195
+ artifactDefinition={textArtifact}
196
+ metadata={props.metadata}
197
+ setMetadata={props.setMetadata}
198
+ />
199
+ );
200
+ default:
201
+ throw new Error("Artifact definition not found!");
202
+ }
203
+ },
141
204
  (prevProps, nextProps) => {
142
205
  if (prevProps.artifact.status !== nextProps.artifact.status) {
143
206
  return false;
@@ -5,14 +5,21 @@ import {
5
5
  } from "@ai-sdk-tools/store";
6
6
  import { useQueryClient } from "@tanstack/react-query";
7
7
  import { formatDistance } from "date-fns";
8
+ import type { Dispatch, SetStateAction } from "react";
8
9
  import { memo, useCallback, useEffect, useRef, useState } from "react";
9
10
  import { useDebounceCallback } from "usehooks-ts";
10
11
  import { useDocuments, useSaveDocument } from "@/hooks/chat-sync-hooks";
11
12
  import { useArtifact } from "@/hooks/use-artifact";
12
13
  import type { ChatMessage } from "@/lib/ai/types";
13
14
  import type { ArtifactKind } from "@/lib/artifacts/artifact-kind";
14
- import { codeArtifact } from "@/lib/artifacts/code/client";
15
- import { sheetArtifact } from "@/lib/artifacts/sheet/client";
15
+ import {
16
+ codeArtifact,
17
+ getCodeArtifactMetadata,
18
+ } from "@/lib/artifacts/code/client";
19
+ import {
20
+ getSheetArtifactMetadata,
21
+ sheetArtifact,
22
+ } from "@/lib/artifacts/sheet/client";
16
23
  import { textArtifact } from "@/lib/artifacts/text/client";
17
24
  import type { Document } from "@/lib/db/schema";
18
25
  import { cn } from "@/lib/utils";
@@ -27,6 +34,7 @@ import {
27
34
  ArtifactTitle,
28
35
  } from "./ai-elements/artifact";
29
36
  import { ArtifactActions as ArtifactPanelActions } from "./artifact-actions";
37
+ import type { ArtifactMetadata } from "./create-artifact";
30
38
  //
31
39
  import { Toolbar } from "./toolbar";
32
40
  import { ScrollArea } from "./ui/scroll-area";
@@ -45,6 +53,18 @@ export interface UIArtifact {
45
53
  title: string;
46
54
  }
47
55
 
56
+ function createTypedMetadataSetter<M extends ArtifactMetadata>(
57
+ setMetadata: Dispatch<SetStateAction<ArtifactMetadata>>,
58
+ coerce: (metadata: ArtifactMetadata) => M
59
+ ): Dispatch<SetStateAction<M>> {
60
+ return (value) => {
61
+ setMetadata((current) => {
62
+ const typedCurrent = coerce(current);
63
+ return typeof value === "function" ? value(typedCurrent) : value;
64
+ });
65
+ };
66
+ }
67
+
48
68
  function PureArtifactPanel({
49
69
  isReadonly,
50
70
  isAuthenticated,
@@ -77,15 +97,7 @@ function PureArtifactPanel({
77
97
  (doc) => doc.messageId === artifact.messageId
78
98
  );
79
99
 
80
- if (mostRecentDocumentIndex !== -1) {
81
- const mostRecentDocument = documents[mostRecentDocumentIndex];
82
- setDocument(mostRecentDocument);
83
- setCurrentVersionIndex(mostRecentDocumentIndex);
84
- setArtifact((currentArtifact) => ({
85
- ...currentArtifact,
86
- content: mostRecentDocument.content ?? "",
87
- }));
88
- } else {
100
+ if (mostRecentDocumentIndex === -1) {
89
101
  // Fallback to the most recent document
90
102
  const latestDocument = documents.at(-1);
91
103
  if (latestDocument) {
@@ -96,6 +108,14 @@ function PureArtifactPanel({
96
108
  content: latestDocument.content ?? "",
97
109
  }));
98
110
  }
111
+ } else {
112
+ const mostRecentDocument = documents[mostRecentDocumentIndex];
113
+ setDocument(mostRecentDocument);
114
+ setCurrentVersionIndex(mostRecentDocumentIndex);
115
+ setArtifact((currentArtifact) => ({
116
+ ...currentArtifact,
117
+ content: mostRecentDocument.content ?? "",
118
+ }));
99
119
  }
100
120
  }
101
121
  }, [documents, setArtifact, artifact.messageId]);
@@ -112,17 +132,6 @@ function PureArtifactPanel({
112
132
  }
113
133
  );
114
134
 
115
- const artifactDefinition = artifactDefinitions.find(
116
- (definition) => definition.kind === artifact.kind
117
- );
118
-
119
- if (!artifactDefinition) {
120
- throw new Error("Artifact definition not found!");
121
- }
122
-
123
- const ArtifactContentComponent = artifactDefinition.content;
124
- const ArtifactFooterComponent = artifactDefinition.footer;
125
-
126
135
  const handleContentChange = useCallback(
127
136
  (updatedContent: string) => {
128
137
  if (!documents) {
@@ -225,22 +234,48 @@ function PureArtifactPanel({
225
234
  : true;
226
235
 
227
236
  useEffect(() => {
228
- if (
229
- artifact.documentId !== "init" &&
230
- artifact.status !== "streaming" &&
231
- artifactDefinition.initialize
232
- ) {
233
- artifactDefinition.initialize({
234
- documentId: artifact.documentId,
235
- setMetadata,
236
- trpc,
237
- queryClient,
238
- isAuthenticated,
239
- });
237
+ if (artifact.documentId !== "init" && artifact.status !== "streaming") {
238
+ switch (artifact.kind) {
239
+ case "code":
240
+ codeArtifact.initialize?.({
241
+ documentId: artifact.documentId,
242
+ setMetadata: createTypedMetadataSetter(
243
+ setMetadata,
244
+ getCodeArtifactMetadata
245
+ ),
246
+ trpc,
247
+ queryClient,
248
+ isAuthenticated,
249
+ });
250
+ break;
251
+ case "sheet":
252
+ sheetArtifact.initialize?.({
253
+ documentId: artifact.documentId,
254
+ setMetadata: createTypedMetadataSetter(
255
+ setMetadata,
256
+ getSheetArtifactMetadata
257
+ ),
258
+ trpc,
259
+ queryClient,
260
+ isAuthenticated,
261
+ });
262
+ break;
263
+ case "text":
264
+ textArtifact.initialize?.({
265
+ documentId: artifact.documentId,
266
+ setMetadata,
267
+ trpc,
268
+ queryClient,
269
+ isAuthenticated,
270
+ });
271
+ break;
272
+ default:
273
+ break;
274
+ }
240
275
  }
241
276
  }, [
242
277
  artifact.documentId,
243
- artifactDefinition,
278
+ artifact.kind,
244
279
  setMetadata,
245
280
  trpc,
246
281
  queryClient,
@@ -272,6 +307,76 @@ function PureArtifactPanel({
272
307
  title: artifact.title,
273
308
  };
274
309
 
310
+ const renderArtifactContent = () => {
311
+ switch (artifact.kind) {
312
+ case "code":
313
+ return (
314
+ <>
315
+ <codeArtifact.content
316
+ {...sharedArtifactProps}
317
+ metadata={getCodeArtifactMetadata(metadata)}
318
+ setMetadata={createTypedMetadataSetter(
319
+ setMetadata,
320
+ getCodeArtifactMetadata
321
+ )}
322
+ />
323
+ {codeArtifact.footer ? (
324
+ <codeArtifact.footer
325
+ {...sharedArtifactProps}
326
+ metadata={getCodeArtifactMetadata(metadata)}
327
+ setMetadata={createTypedMetadataSetter(
328
+ setMetadata,
329
+ getCodeArtifactMetadata
330
+ )}
331
+ />
332
+ ) : null}
333
+ </>
334
+ );
335
+ case "sheet":
336
+ return (
337
+ <>
338
+ <sheetArtifact.content
339
+ {...sharedArtifactProps}
340
+ metadata={getSheetArtifactMetadata(metadata)}
341
+ setMetadata={createTypedMetadataSetter(
342
+ setMetadata,
343
+ getSheetArtifactMetadata
344
+ )}
345
+ />
346
+ {sheetArtifact.footer ? (
347
+ <sheetArtifact.footer
348
+ {...sharedArtifactProps}
349
+ metadata={getSheetArtifactMetadata(metadata)}
350
+ setMetadata={createTypedMetadataSetter(
351
+ setMetadata,
352
+ getSheetArtifactMetadata
353
+ )}
354
+ />
355
+ ) : null}
356
+ </>
357
+ );
358
+ case "text":
359
+ return (
360
+ <>
361
+ <textArtifact.content
362
+ {...sharedArtifactProps}
363
+ metadata={metadata}
364
+ setMetadata={setMetadata}
365
+ />
366
+ {textArtifact.footer ? (
367
+ <textArtifact.footer
368
+ {...sharedArtifactProps}
369
+ metadata={metadata}
370
+ setMetadata={setMetadata}
371
+ />
372
+ ) : null}
373
+ </>
374
+ );
375
+ default:
376
+ return null;
377
+ }
378
+ };
379
+
275
380
  return (
276
381
  <ArtifactCard
277
382
  className={cn(
@@ -283,7 +388,7 @@ function PureArtifactPanel({
283
388
  <ArtifactHeader className="items-start bg-background/80 p-2">
284
389
  <div className="flex flex-row items-start gap-4">
285
390
  <ArtifactClose
286
- className="h-fit p-2 dark:hover:bg-zinc-700"
391
+ className="h-fit p-2 hover:bg-accent"
287
392
  data-testid="artifact-close-button"
288
393
  onClick={closeArtifact}
289
394
  variant="outline"
@@ -336,7 +441,7 @@ function PureArtifactPanel({
336
441
  <ArtifactContent className="flex h-full flex-col p-0">
337
442
  <ScrollArea className="h-full max-w-full!">
338
443
  <div className="flex flex-col items-center bg-background/80">
339
- <ArtifactContentComponent {...sharedArtifactProps} />
444
+ {renderArtifactContent()}
340
445
 
341
446
  {isCurrentVersion && !isReadonly && (
342
447
  <Toolbar
@@ -351,10 +456,6 @@ function PureArtifactPanel({
351
456
  </div>
352
457
  </ScrollArea>
353
458
 
354
- {ArtifactFooterComponent ? (
355
- <ArtifactFooterComponent {...sharedArtifactProps} />
356
- ) : null}
357
-
358
459
  {!(isCurrentVersion || isReadonly) && (
359
460
  <VersionFooter
360
461
  currentVersionIndex={currentVersionIndex}
@@ -66,7 +66,7 @@ function AttachmentPill({
66
66
  return (
67
67
  <div
68
68
  className={cn(
69
- "group relative flex h-8 cursor-default select-none items-center gap-1.5 rounded-md border border-border px-1.5 font-medium text-sm transition-all hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
69
+ "group relative flex h-8 cursor-default select-none items-center gap-1.5 rounded-md border border-border px-1.5 font-medium text-sm transition-all hover:bg-accent hover:text-accent-foreground",
70
70
  isUploading && "opacity-60"
71
71
  )}
72
72
  data-testid="input-attachment-preview"
@@ -1,9 +1,12 @@
1
1
  "use client";
2
2
 
3
3
  import { Github } from "lucide-react";
4
+ import { toast } from "sonner";
5
+ import { ElectronBrowserSignIn } from "@/components/electron-auth-ui";
4
6
  import { Button } from "@/components/ui/button";
5
7
  import authClient from "@/lib/auth-client";
6
8
  import { config } from "@/lib/config";
9
+ import type { SocialAuthSignInOptions } from "@/lib/social-auth";
7
10
 
8
11
  function GoogleIcon({ className }: { className?: string }) {
9
12
  return (
@@ -38,13 +41,55 @@ function VercelIcon({ className }: { className?: string }) {
38
41
  );
39
42
  }
40
43
 
41
- export function SocialAuthProviders() {
44
+ export function SocialAuthProviders({
45
+ callbackURL,
46
+ electronBrowserLabel,
47
+ isElectron = false,
48
+ onRedirectToUrl,
49
+ query = {},
50
+ signInOptions,
51
+ }: {
52
+ callbackURL?: string;
53
+ electronBrowserLabel?: string;
54
+ isElectron?: boolean;
55
+ onRedirectToUrl?: (url: string) => void;
56
+ query?: Record<string, string>;
57
+ signInOptions?: SocialAuthSignInOptions;
58
+ } = {}) {
59
+ // In the Electron app, use the @better-auth/electron bridges exposed by
60
+ // setupRenderer() in the preload script. requestAuth() opens the sign-in
61
+ // URL in the user's default browser with the proper PKCE params.
62
+ if (config.desktopApp.enabled && isElectron) {
63
+ return <ElectronBrowserSignIn buttonLabel={electronBrowserLabel} />;
64
+ }
65
+
66
+ async function signIn(provider: "google" | "github" | "vercel") {
67
+ try {
68
+ const result = await authClient.signIn.social({
69
+ provider,
70
+ callbackURL,
71
+ ...signInOptions,
72
+ fetchOptions: {
73
+ query,
74
+ },
75
+ });
76
+
77
+ const redirectUrl = result.data?.url;
78
+ if (redirectUrl) {
79
+ onRedirectToUrl?.(redirectUrl);
80
+ }
81
+ } catch (error) {
82
+ console.error(`Failed to start ${provider} sign-in`, error);
83
+ toast.error("Couldn't start sign-in. Please try again.");
84
+ }
85
+ }
86
+
42
87
  return (
43
88
  <div className="space-y-2">
44
89
  {config.authentication.google ? (
45
90
  <Button
46
91
  className="w-full"
47
- onClick={() => authClient.signIn.social({ provider: "google" })}
92
+ onClick={() => signIn("google")}
48
93
  type="button"
49
94
  variant="outline"
50
95
  >
@@ -55,7 +100,7 @@ export function SocialAuthProviders() {
55
100
  {config.authentication.github ? (
56
101
  <Button
57
102
  className="w-full"
58
- onClick={() => authClient.signIn.social({ provider: "github" })}
103
+ onClick={() => signIn("github")}
59
104
  type="button"
60
105
  variant="outline"
61
106
  >
@@ -66,7 +111,7 @@ export function SocialAuthProviders() {
66
111
  {config.authentication.vercel ? (
67
112
  <Button
68
113
  className="w-full"
69
- onClick={() => authClient.signIn.social({ provider: "vercel" })}
114
+ onClick={() => signIn("vercel")}
70
115
  type="button"
71
116
  variant="outline"
72
117
  >
@@ -34,12 +34,12 @@ function PureChatWelcome({
34
34
  return (
35
35
  <div
36
36
  className={cn(
37
- "flex min-h-0 flex-1 flex-col items-center justify-center",
37
+ "flex min-h-0 flex-1 flex-col justify-end md:justify-center",
38
38
  className
39
39
  )}
40
40
  >
41
- <div className="mx-auto w-full p-2 @[500px]:px-4 md:max-w-3xl">
42
- <div className="mb-6">
41
+ <div className="mx-auto w-full p-2 @[500px]:px-4 @[500px]:pb-6 pb-4 md:max-w-3xl">
42
+ <div className="mb-4 md:mb-6">
43
43
  <WelcomeMessage />
44
44
  </div>
45
45
  <MultimodalInput
@@ -36,7 +36,7 @@ export function ChatMenuItems({
36
36
  {showShare && onShare && <ShareMenuItem onShare={onShare} />}
37
37
 
38
38
  <DropdownMenuItem
39
- className="cursor-pointer text-destructive focus:bg-destructive/15 focus:text-destructive dark:text-red-500"
39
+ className="cursor-pointer text-destructive focus:bg-destructive/15 focus:text-destructive"
40
40
  onSelect={onDelete}
41
41
  >
42
42
  <Trash2 size={16} />
@@ -8,6 +8,7 @@ import { useDataStream } from "@/components/data-stream-provider";
8
8
  import { useSaveMessageMutation } from "@/hooks/chat-sync-hooks";
9
9
  import { useCompleteDataPart } from "@/hooks/use-complete-data-part";
10
10
  import { ChatSDKError } from "@/lib/ai/errors";
11
+ import { getStreamErrorToastContent } from "@/lib/ai/stream-errors";
11
12
  import type { ChatMessage } from "@/lib/ai/types";
12
13
  import {
13
14
  useAddMessageToTree,
@@ -103,14 +104,8 @@ export function ChatSync({
103
104
  }
104
105
 
105
106
  console.error(error);
106
- const cause = error.cause;
107
- if (cause && typeof cause === "string") {
108
- toast.error(error.message ?? "An error occured, please try again!", {
109
- description: cause,
110
- });
111
- } else {
112
- toast.error(error.message ?? "An error occured, please try again!");
113
- }
107
+ const { message, description } = getStreamErrorToastContent(error);
108
+ toast.error(message, description ? { description } : undefined);
114
109
  },
115
110
  });
116
111
 
@@ -54,16 +54,16 @@ export function Console({
54
54
 
55
55
  return consoleOutputs.length > 0 ? (
56
56
  <div className={cn("flex w-full flex-col overflow-hidden", className)}>
57
- <div className="flex h-full w-full flex-col overflow-x-hidden overflow-y-scroll border-zinc-200 border-t bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900">
58
- <div className="sticky top-0 z-50 flex h-fit w-full flex-row items-center justify-between border-zinc-200 border-b bg-muted px-2 py-1 dark:border-zinc-700">
59
- <div className="flex flex-row items-center gap-3 pl-2 text-sm text-zinc-800 dark:text-zinc-50">
57
+ <div className="flex h-full w-full flex-col overflow-x-hidden overflow-y-scroll border-border border-t bg-muted">
58
+ <div className="sticky top-0 z-50 flex h-fit w-full flex-row items-center justify-between border-border border-b bg-muted px-2 py-1">
59
+ <div className="flex flex-row items-center gap-3 pl-2 text-foreground text-sm">
60
60
  <div className="text-muted-foreground">
61
61
  <Terminal size={16} />
62
62
  </div>
63
63
  <div>Console</div>
64
64
  </div>
65
65
  <Button
66
- className="size-fit p-1 hover:bg-zinc-200 dark:hover:bg-zinc-700"
66
+ className="size-fit p-1 hover:bg-accent"
67
67
  onClick={() => setConsoleOutputs([])}
68
68
  size="icon"
69
69
  variant="ghost"
@@ -75,7 +75,7 @@ export function Console({
75
75
  <div>
76
76
  {consoleOutputs.map((consoleOutput, index) => (
77
77
  <div
78
- className="flex flex-row border-zinc-200 border-b bg-zinc-50 px-4 py-2 font-mono text-sm dark:border-zinc-700 dark:bg-zinc-900"
78
+ className="flex flex-row border-border border-b bg-muted px-4 py-2 font-mono text-sm"
79
79
  key={consoleOutput.id}
80
80
  >
81
81
  <div
@@ -102,10 +102,10 @@ export function Console({
102
102
  </div>
103
103
  </div>
104
104
  ) : (
105
- <div className="flex w-full flex-col gap-2 overflow-x-scroll text-zinc-900 dark:text-zinc-50">
106
- {consoleOutput.contents.map((content, contentIndex) =>
105
+ <div className="flex w-full flex-col gap-2 overflow-x-scroll text-foreground">
106
+ {consoleOutput.contents.map((content) =>
107
107
  content.type === "image" ? (
108
- <picture key={`${consoleOutput.id}-${contentIndex}`}>
108
+ <picture key={`${consoleOutput.id}-${content.value}`}>
109
109
  <img
110
110
  alt="output"
111
111
  className="w-full max-w-(--breakpoint-toast-mobile) rounded-md"
@@ -117,7 +117,7 @@ export function Console({
117
117
  ) : (
118
118
  <div
119
119
  className="break-word-wrap w-full whitespace-pre-line"
120
- key={`${consoleOutput.id}-${contentIndex}`}
120
+ key={`${consoleOutput.id}-${content.type}-${content.value}`}
121
121
  >
122
122
  {content.value}
123
123
  </div>
@@ -94,8 +94,8 @@ function ContextUsage({
94
94
  if (!usage) {
95
95
  return 0;
96
96
  }
97
- const input = (usage as any).inputTokens ?? 0;
98
- const cached = (usage as any).cachedInputTokens ?? 0;
97
+ const input = usage.inputTokens ?? 0;
98
+ const cached = usage.cachedInputTokens ?? 0;
99
99
  return input + cached;
100
100
  }, [usage]);
101
101
 
@@ -7,7 +7,11 @@ import type { ChatMessage, CustomUIDataTypes } from "@/lib/ai/types";
7
7
  import type { useTRPC } from "@/trpc/react";
8
8
  import type { UIArtifact } from "./artifact-panel";
9
9
 
10
- export interface ArtifactActionContext<M = any> {
10
+ export type ArtifactMetadata = object | null;
11
+
12
+ export interface ArtifactActionContext<
13
+ M extends ArtifactMetadata = ArtifactMetadata,
14
+ > {
11
15
  content: string;
12
16
  currentVersionIndex: number;
13
17
  handleVersionChange: (type: "next" | "prev" | "toggle" | "latest") => void;
@@ -18,7 +22,7 @@ export interface ArtifactActionContext<M = any> {
18
22
  setMetadata: Dispatch<SetStateAction<M>>;
19
23
  }
20
24
 
21
- interface ArtifactAction<M = any> {
25
+ interface ArtifactAction<M extends ArtifactMetadata = ArtifactMetadata> {
22
26
  description: string;
23
27
  icon: ReactNode;
24
28
  isDisabled?: (context: ArtifactActionContext<M>) => boolean;
@@ -37,7 +41,7 @@ export interface ArtifactToolbarItem {
37
41
  onClick: (context: ArtifactToolbarContext) => void;
38
42
  }
39
43
 
40
- interface ArtifactContent<M = any> {
44
+ interface ArtifactContent<M extends ArtifactMetadata = ArtifactMetadata> {
41
45
  content: string;
42
46
  currentVersionIndex: number;
43
47
  getDocumentContentById: (index: number) => string;
@@ -53,7 +57,10 @@ interface ArtifactContent<M = any> {
53
57
  title: string;
54
58
  }
55
59
 
56
- interface ArtifactConfig<T extends string, M = any> {
60
+ interface ArtifactConfig<
61
+ T extends string,
62
+ M extends ArtifactMetadata = ArtifactMetadata,
63
+ > {
57
64
  actions: ArtifactAction<M>[];
58
65
  content: ComponentType<ArtifactContent<M>>;
59
66
  description: string;
@@ -80,7 +87,10 @@ interface ArtifactConfig<T extends string, M = any> {
80
87
  toolbar: ArtifactToolbarItem[];
81
88
  }
82
89
 
83
- export class Artifact<T extends string, M = any> {
90
+ export class Artifact<
91
+ T extends string,
92
+ M extends ArtifactMetadata = ArtifactMetadata,
93
+ > {
84
94
  readonly kind: T;
85
95
  readonly description: string;
86
96
  readonly content: ComponentType<ArtifactContent<M>>;