@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,7 +1,9 @@
1
1
  "use client";
2
2
 
3
3
  import Link from "next/link";
4
- import { SocialAuthProviders } from "@/components/social-auth-providers";
4
+ import { useSearchParams } from "next/navigation";
5
+ import { Suspense, useEffect, useState } from "react";
6
+ import { SocialAuthProviders } from "@/components/auth-providers";
5
7
  import {
6
8
  Card,
7
9
  CardContent,
@@ -9,27 +11,64 @@ import {
9
11
  CardHeader,
10
12
  CardTitle,
11
13
  } from "@/components/ui/card";
14
+ import {
15
+ buildSocialAuthRequest,
16
+ isElectronRenderer,
17
+ } from "@/lib/electron-auth";
12
18
 
13
19
  export function SignupForm({
14
20
  className,
15
21
  ...props
16
22
  }: React.ComponentProps<typeof Card>) {
23
+ const searchParams = useSearchParams();
24
+ const query = Object.fromEntries(searchParams.entries());
25
+ const [isElectron, setIsElectron] = useState(false);
26
+ const { callbackURL, onRedirectToUrl, signInOptions } =
27
+ buildSocialAuthRequest(query, globalThis.location?.origin);
28
+ const loginHref = { pathname: "/login" as const, query };
29
+
30
+ useEffect(() => {
31
+ setIsElectron(isElectronRenderer());
32
+ }, []);
33
+
17
34
  return (
18
35
  <div className="flex flex-col gap-6" {...props}>
19
36
  <Card {...props}>
20
37
  <CardHeader className="text-center">
21
- <CardTitle className="text-xl">Create an account</CardTitle>
22
- <CardDescription>Continue with a social provider</CardDescription>
38
+ <CardTitle className="text-xl">
39
+ {isElectron ? "Continue in browser" : "Create an account"}
40
+ </CardTitle>
41
+ <CardDescription>
42
+ {isElectron
43
+ ? "Use your browser to sign in or create an account."
44
+ : "Get started in seconds"}
45
+ </CardDescription>
23
46
  </CardHeader>
24
47
  <CardContent>
25
48
  <div className="grid gap-6">
26
- <SocialAuthProviders />
27
- <div className="text-center text-sm">
28
- Already have an account?{" "}
29
- <a className="underline underline-offset-4" href="/login">
30
- Sign in
31
- </a>
32
- </div>
49
+ <Suspense>
50
+ <SocialAuthProviders
51
+ callbackURL={callbackURL}
52
+ electronBrowserLabel="Continue in browser"
53
+ isElectron={isElectron}
54
+ onRedirectToUrl={onRedirectToUrl}
55
+ query={query}
56
+ signInOptions={signInOptions}
57
+ />
58
+ </Suspense>
59
+ {isElectron ? (
60
+ <div className="text-center text-muted-foreground text-sm">
61
+ New and existing accounts both continue through the browser
62
+ flow.
63
+ </div>
64
+ ) : (
65
+ <div className="text-center text-sm">
66
+ Already have an account?{" "}
67
+ <Link className="underline underline-offset-4" href={loginHref}>
68
+ Sign in
69
+ </Link>
70
+ </div>
71
+ )}
33
72
  </div>
34
73
  </CardContent>
35
74
  </Card>
@@ -28,7 +28,7 @@ const SourcesList = ({
28
28
  <div className="space-y-3">
29
29
  {sources?.map((source: SearchResultItem) => (
30
30
  <a
31
- className="block rounded-lg bg-neutral-50 p-4 transition-colors hover:bg-neutral-100 dark:bg-neutral-800/50 dark:hover:bg-neutral-800"
31
+ className="block rounded-lg bg-secondary p-4 transition-colors hover:bg-accent"
32
32
  href={source.url}
33
33
  key={source.url}
34
34
  rel="noopener noreferrer"
@@ -113,7 +113,7 @@ function ShowSourcesButton({
113
113
  }) {
114
114
  return (
115
115
  <button
116
- className="group flex items-center justify-center gap-2 rounded-lg border border-neutral-200 p-2.5 transition-colors hover:bg-neutral-50 dark:border-neutral-800 dark:hover:bg-neutral-800/50"
116
+ className="group flex items-center justify-center gap-2 rounded-lg border border-border p-2.5 transition-colors hover:bg-accent"
117
117
  onClick={() => document.getElementById(dialogId)?.click()}
118
118
  type="button"
119
119
  >
@@ -125,10 +125,10 @@ function ShowSourcesButton({
125
125
  title: s.title,
126
126
  }))}
127
127
  />
128
- <span className="text-neutral-600 text-xs group-hover:text-neutral-700 dark:text-neutral-400 dark:group-hover:text-neutral-300">
128
+ <span className="text-muted-foreground text-xs group-hover:text-foreground">
129
129
  {sources.length} Sources
130
130
  </span>
131
- <ArrowRight className="h-3.5 w-3.5 text-neutral-400 transition-colors group-hover:text-neutral-500" />
131
+ <ArrowRight className="h-3.5 w-3.5 text-muted-foreground transition-colors group-hover:text-foreground" />
132
132
  </button>
133
133
  );
134
134
  }
@@ -14,6 +14,7 @@ import { ListPlugin } from "@lexical/react/LexicalListPlugin";
14
14
  import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin";
15
15
  import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";
16
16
  import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
17
+ import type { EditorState } from "lexical";
17
18
  import { $getRoot } from "lexical";
18
19
  import { memo, useEffect, useRef } from "react";
19
20
 
@@ -95,7 +96,7 @@ function ContentUpdatePlugin({
95
96
  }
96
97
  }, [content, status, editor]);
97
98
 
98
- const handleChange = (editorState: any) => {
99
+ const handleChange = (editorState: EditorState) => {
99
100
  if (!(isReadonly || isProgrammaticUpdate.current)) {
100
101
  handleEditorChange({
101
102
  editorState,
@@ -129,7 +130,9 @@ function PureEditor({
129
130
  <ContentEditable className="lexical-editor text-left outline-hidden" />
130
131
  }
131
132
  ErrorBoundary={LexicalErrorBoundary}
132
- placeholder={<div className="text-gray-400">Start typing...</div>}
133
+ placeholder={
134
+ <div className="text-muted-foreground">Start typing...</div>
135
+ }
133
136
  />
134
137
  <HistoryPlugin />
135
138
  <ListPlugin />
@@ -89,11 +89,11 @@ function Tool({
89
89
  return;
90
90
  }
91
91
 
92
- if (selectedTool !== description) {
93
- setSelectedTool(description);
94
- } else {
92
+ if (selectedTool === description) {
95
93
  setSelectedTool(null);
96
94
  onClick({ sendMessage, storeApi });
95
+ } else {
96
+ setSelectedTool(description);
97
97
  }
98
98
  };
99
99
 
@@ -84,7 +84,6 @@ function SidebarProvider({
84
84
  // This sets the cookie to keep the sidebar state.
85
85
  // Prefer Cookie Store API when available
86
86
  if ("cookieStore" in window) {
87
- // @ts-expect-error cookieStore is not yet in TS lib.dom
88
87
  void window.cookieStore.set({
89
88
  name: SIDEBAR_COOKIE_NAME,
90
89
  value: String(openState),
@@ -40,7 +40,7 @@ const VARIANT_CONFIG: Record<
40
40
  <span>
41
41
  You only have{" "}
42
42
  <strong>
43
- {remaining} credit{remaining !== 1 ? "s" : ""}
43
+ {remaining} credit{remaining === 1 ? "" : "s"}
44
44
  </strong>{" "}
45
45
  left.{" "}
46
46
  <Link
@@ -11,150 +11,151 @@ import { MessageEditor } from "./message-editor";
11
11
  import { ParallelResponseCards } from "./parallel-response-cards";
12
12
 
13
13
  export interface BaseMessageProps {
14
- isLoading: boolean;
15
- isReadonly: boolean;
16
- messageId: string;
17
- parentMessageId: string | null;
14
+ isLoading: boolean;
15
+ isReadonly: boolean;
16
+ messageId: string;
17
+ parentMessageId: string | null;
18
18
  }
19
19
 
20
20
  const PureUserMessage = ({
21
- messageId,
22
- isLoading,
23
- isReadonly,
24
- parentMessageId,
21
+ messageId,
22
+ isLoading,
23
+ isReadonly,
24
+ parentMessageId,
25
25
  }: BaseMessageProps) => {
26
- const chatId = useChatId();
27
- const message = useMessageById<ChatMessage>(messageId);
28
- const [mode, setMode] = useState<"view" | "edit">("view");
29
- const [imageModal, setImageModal] = useState<{
30
- isOpen: boolean;
31
- imageUrl: string;
32
- imageName?: string;
33
- }>({ isOpen: false, imageUrl: "" });
26
+ const chatId = useChatId();
27
+ const message = useMessageById<ChatMessage>(messageId);
28
+ const [mode, setMode] = useState<"view" | "edit">("view");
29
+ const [imageModal, setImageModal] = useState<{
30
+ isOpen: boolean;
31
+ imageUrl: string;
32
+ imageName?: string;
33
+ }>({ isOpen: false, imageUrl: "" });
34
34
 
35
- const handleImageClick = (imageUrl: string, imageName?: string) => {
36
- setImageModal({ isOpen: true, imageUrl, imageName });
37
- };
35
+ const handleImageClick = (imageUrl: string, imageName?: string) => {
36
+ setImageModal({ isOpen: true, imageUrl, imageName });
37
+ };
38
38
 
39
- if (!message) {
40
- return null;
41
- }
42
- const textPart = message.parts.find((part) => part.type === "text");
43
- if (!(textPart && chatId)) {
44
- return null;
45
- }
39
+ if (!message) {
40
+ return null;
41
+ }
42
+ const textPart = message.parts.find((part) => part.type === "text");
43
+ if (!(textPart && chatId)) {
44
+ return null;
45
+ }
46
46
 
47
- return (
48
- <>
49
- <Message
50
- className={cn(
51
- // TODO: Consider not using this max-w class override when editing is cohesive with displaying the message
52
- mode === "edit" ? "max-w-full [&>div]:max-w-full" : undefined,
53
- "py-1",
54
- )}
55
- from="user"
56
- >
57
- <div
58
- className={cn(
59
- "flex w-full flex-col gap-2",
60
- message.role === "user" && mode !== "edit" && "items-end",
61
- )}
62
- >
63
- {mode === "view" && <ParallelResponseCards messageId={message.id} />}
47
+ return (
48
+ <>
49
+ <Message
50
+ className={cn(
51
+ // TODO: Consider not using this max-w class override when editing is cohesive with displaying the message
52
+ mode === "edit" ? "max-w-full [&>div]:max-w-full" : undefined,
53
+ "py-1"
54
+ )}
55
+ from="user"
56
+ >
57
+ <div
58
+ className={cn(
59
+ "flex w-full flex-col gap-2",
60
+ message.role === "user" && mode !== "edit" && "items-end"
61
+ )}
62
+ >
63
+ {mode === "view" && <ParallelResponseCards messageId={message.id} />}
64
64
 
65
- {mode === "view" && isReadonly && (
66
- <MessageContent
67
- className="text-left group-[.is-user]:bg-card"
68
- data-testid="message-content"
69
- >
70
- <AttachmentList
71
- attachments={getAttachmentsFromMessage(message)}
72
- onImageClick={handleImageClick}
73
- testId="message-attachments"
74
- />
75
- <pre className="whitespace-pre-wrap font-sans">
76
- {textPart.text}
77
- </pre>
78
- </MessageContent>
79
- )}
80
- {mode === "view" && !isReadonly && (
81
- <button
82
- className="block cursor-pointer select-text text-left transition-opacity hover:opacity-80"
83
- data-testid="message-content"
84
- onClick={(e) => {
85
- const selection = window.getSelection();
86
- if (
87
- selection?.toString() &&
88
- e.currentTarget.contains(selection.anchorNode)
89
- )
90
- return;
91
- setMode("edit");
92
- }}
93
- type="button"
94
- >
95
- <MessageContent
96
- className="text-left group-[.is-user]:max-w-none group-[.is-user]:bg-card"
97
- data-testid="message-content"
98
- >
99
- <AttachmentList
100
- attachments={getAttachmentsFromMessage(message)}
101
- onImageClick={handleImageClick}
102
- testId="message-attachments"
103
- />
104
- <pre className="whitespace-pre-wrap font-sans">
105
- {textPart.text}
106
- </pre>
107
- </MessageContent>
108
- </button>
109
- )}
110
- {mode !== "view" && (
111
- <div className="flex flex-row items-start gap-2">
112
- <MessageEditor
113
- chatId={chatId}
114
- key={message.id}
115
- message={message}
116
- parentMessageId={parentMessageId}
117
- setMode={setMode}
118
- />
119
- </div>
120
- )}
65
+ {mode === "view" && isReadonly && (
66
+ <MessageContent
67
+ className="text-left group-[.is-user]:bg-card"
68
+ data-testid="message-content"
69
+ >
70
+ <AttachmentList
71
+ attachments={getAttachmentsFromMessage(message)}
72
+ onImageClick={handleImageClick}
73
+ testId="message-attachments"
74
+ />
75
+ <pre className="whitespace-pre-wrap font-sans">
76
+ {textPart.text}
77
+ </pre>
78
+ </MessageContent>
79
+ )}
80
+ {mode === "view" && !isReadonly && (
81
+ <button
82
+ className="block cursor-pointer select-text text-left transition-opacity hover:opacity-80"
83
+ data-testid="message-content"
84
+ onClick={(e) => {
85
+ const selection = window.getSelection();
86
+ if (
87
+ selection?.toString() &&
88
+ e.currentTarget.contains(selection.anchorNode)
89
+ ) {
90
+ return;
91
+ }
92
+ setMode("edit");
93
+ }}
94
+ type="button"
95
+ >
96
+ <MessageContent
97
+ className="text-left group-[.is-user]:max-w-none group-[.is-user]:bg-card"
98
+ data-testid="message-content"
99
+ >
100
+ <AttachmentList
101
+ attachments={getAttachmentsFromMessage(message)}
102
+ onImageClick={handleImageClick}
103
+ testId="message-attachments"
104
+ />
105
+ <pre className="whitespace-pre-wrap font-sans">
106
+ {textPart.text}
107
+ </pre>
108
+ </MessageContent>
109
+ </button>
110
+ )}
111
+ {mode !== "view" && (
112
+ <div className="flex flex-row items-start gap-2">
113
+ <MessageEditor
114
+ chatId={chatId}
115
+ key={message.id}
116
+ message={message}
117
+ parentMessageId={parentMessageId}
118
+ setMode={setMode}
119
+ />
120
+ </div>
121
+ )}
121
122
 
122
- <div className="self-end">
123
- <MessageActions
124
- chatId={chatId}
125
- isEditing={mode === "edit"}
126
- isLoading={isLoading}
127
- isReadOnly={isReadonly}
128
- key={`action-${message.id}`}
129
- messageId={message.id}
130
- onCancelEdit={() => setMode("view")}
131
- onStartEdit={() => setMode("edit")}
132
- />
133
- </div>
134
- </div>
135
- </Message>
136
- <ImageModal
137
- imageName={imageModal.imageName}
138
- imageUrl={imageModal.imageUrl}
139
- isOpen={imageModal.isOpen}
140
- onClose={() => setImageModal({ isOpen: false, imageUrl: "" })}
141
- />
142
- </>
143
- );
123
+ <div className="self-end">
124
+ <MessageActions
125
+ chatId={chatId}
126
+ isEditing={mode === "edit"}
127
+ isLoading={isLoading}
128
+ isReadOnly={isReadonly}
129
+ key={`action-${message.id}`}
130
+ messageId={message.id}
131
+ onCancelEdit={() => setMode("view")}
132
+ onStartEdit={() => setMode("edit")}
133
+ />
134
+ </div>
135
+ </div>
136
+ </Message>
137
+ <ImageModal
138
+ imageName={imageModal.imageName}
139
+ imageUrl={imageModal.imageUrl}
140
+ isOpen={imageModal.isOpen}
141
+ onClose={() => setImageModal({ isOpen: false, imageUrl: "" })}
142
+ />
143
+ </>
144
+ );
144
145
  };
145
146
 
146
147
  export const UserMessage = memo(PureUserMessage, (prevProps, nextProps) => {
147
- if (prevProps.messageId !== nextProps.messageId) {
148
- return false;
149
- }
150
- if (prevProps.isReadonly !== nextProps.isReadonly) {
151
- return false;
152
- }
153
- if (prevProps.parentMessageId !== nextProps.parentMessageId) {
154
- return false;
155
- }
156
- if (prevProps.isLoading !== nextProps.isLoading) {
157
- return false;
158
- }
159
- return true;
148
+ if (prevProps.messageId !== nextProps.messageId) {
149
+ return false;
150
+ }
151
+ if (prevProps.isReadonly !== nextProps.isReadonly) {
152
+ return false;
153
+ }
154
+ if (prevProps.parentMessageId !== nextProps.parentMessageId) {
155
+ return false;
156
+ }
157
+ if (prevProps.isLoading !== nextProps.isLoading) {
158
+ return false;
159
+ }
160
+ return true;
160
161
  });
@@ -0,0 +1,41 @@
1
+ declare global {
2
+ interface ElectronAuthErrorContext {
3
+ message?: string;
4
+ path?: string;
5
+ status?: number;
6
+ statusText?: string;
7
+ }
8
+
9
+ type ElectronRendererAuthState =
10
+ | {
11
+ status: "idle";
12
+ message: null;
13
+ }
14
+ | {
15
+ status: "awaiting-browser" | "finishing" | "timed-out" | "error";
16
+ message: string;
17
+ detail?: string | null;
18
+ };
19
+
20
+ interface Window {
21
+ electronAPI?: {
22
+ cancelAuthFlow?: () => Promise<void>;
23
+ getAuthState?: () => Promise<ElectronRendererAuthState>;
24
+ isElectron: boolean;
25
+ onAuthStateChanged?: (
26
+ callback: (state: ElectronRendererAuthState) => void
27
+ ) => () => void;
28
+ platform: string;
29
+ syncAuthSession?: () => Promise<void>;
30
+ };
31
+ onAuthError?: (
32
+ callback: (context: ElectronAuthErrorContext) => void
33
+ ) => () => void;
34
+ onAuthenticated?: (callback: (user: unknown) => void) => () => void;
35
+ onUserUpdated?: (callback: (user: unknown) => void) => () => void;
36
+ requestAuth?: (options?: { provider?: string }) => Promise<void> | void;
37
+ signOut?: () => Promise<void>;
38
+ }
39
+ }
40
+
41
+ export {};
@@ -16,7 +16,9 @@ evalite("Test Capitals", {
16
16
  ],
17
17
  task: async (input) => {
18
18
  const result = streamText({
19
- model: wrapAISDKModel(openai("gpt-4o-mini") as any),
19
+ model: wrapAISDKModel(
20
+ openai("gpt-4o-mini") as unknown as Parameters<typeof wrapAISDKModel>[0]
21
+ ),
20
22
  system: "Answer the question concisely.",
21
23
  prompt: input,
22
24
  });
@@ -9,6 +9,7 @@ import {
9
9
  useState,
10
10
  } from "react";
11
11
  import type { UIArtifact } from "@/components/artifact-panel";
12
+ import type { ArtifactMetadata } from "@/components/create-artifact";
12
13
 
13
14
  const initialArtifactData: UIArtifact = {
14
15
  documentId: "init",
@@ -23,18 +24,19 @@ const initialArtifactData: UIArtifact = {
23
24
 
24
25
  type Selector<T> = (state: UIArtifact) => T;
25
26
 
27
+ type MetadataUpdater = (current: ArtifactMetadata) => ArtifactMetadata;
28
+
29
+ type MetadataStore = Record<string, ArtifactMetadata>;
30
+
26
31
  interface ArtifactContextType {
27
32
  artifact: UIArtifact;
28
- metadata: Record<string, any> | null;
33
+ metadata: MetadataStore;
29
34
  setArtifact: (
30
35
  updaterFn: UIArtifact | ((currentArtifact: UIArtifact) => UIArtifact)
31
36
  ) => void;
32
37
  setMetadata: (
33
38
  documentId: string,
34
- metadata:
35
- | Record<string, any>
36
- | null
37
- | ((current: Record<string, any> | null) => Record<string, any> | null)
39
+ metadata: ArtifactMetadata | MetadataUpdater
38
40
  ) => void;
39
41
  }
40
42
 
@@ -45,10 +47,7 @@ const ArtifactContext = createContext<ArtifactContextType | undefined>(
45
47
  export function ArtifactProvider({ children }: { children: ReactNode }) {
46
48
  const [artifact, setArtifactState] =
47
49
  useState<UIArtifact>(initialArtifactData);
48
- const [metadataStore, setMetadataStore] = useState<Record<
49
- string,
50
- any
51
- > | null>(initialArtifactData);
50
+ const [metadataStore, setMetadataStore] = useState<MetadataStore>({});
52
51
 
53
52
  const setArtifact = useCallback(
54
53
  (updaterFn: UIArtifact | ((currentArtifact: UIArtifact) => UIArtifact)) => {
@@ -63,12 +62,12 @@ export function ArtifactProvider({ children }: { children: ReactNode }) {
63
62
  );
64
63
 
65
64
  const setMetadata = useCallback(
66
- (documentId: string, metadata: any | ((current: any) => any)) => {
65
+ (documentId: string, metadata: ArtifactMetadata | MetadataUpdater) => {
67
66
  setMetadataStore((current) => ({
68
67
  ...current,
69
68
  [documentId]:
70
69
  typeof metadata === "function"
71
- ? metadata(current ? current[documentId] : null)
70
+ ? metadata(current[documentId] ?? null)
72
71
  : metadata,
73
72
  }));
74
73
  },
@@ -117,12 +116,13 @@ export function useArtifact() {
117
116
  } = useArtifactContext();
118
117
 
119
118
  const metadata = useMemo(
120
- () => (artifact.documentId ? metadataStore?.[artifact.documentId] : null),
119
+ () =>
120
+ artifact.documentId ? (metadataStore[artifact.documentId] ?? null) : null,
121
121
  [metadataStore, artifact.documentId]
122
122
  );
123
123
 
124
124
  const setMetadata = useCallback(
125
- (metadataArg: any | ((current: any) => any)) => {
125
+ (metadataArg: ArtifactMetadata | MetadataUpdater) => {
126
126
  if (artifact.documentId) {
127
127
  setMetadataStore(artifact.documentId, metadataArg);
128
128
  }
@@ -4,14 +4,23 @@ export type StrictLiterals<T> = T extends string
4
4
  : T
5
5
  : T;
6
6
 
7
- export type ExtractModelIdFromProvider<
8
- ProviderFactory extends (...args: any) => {
9
- languageModel: (modelId: any) => any;
10
- },
11
- > = Parameters<ReturnType<ProviderFactory>["languageModel"]>[0];
7
+ export type ExtractModelIdFromProvider<ProviderFactory> =
8
+ ProviderFactory extends (...args: infer _Args) => infer Provider
9
+ ? Provider extends {
10
+ languageModel: (
11
+ modelId: infer ModelId,
12
+ ...args: infer _Rest
13
+ ) => unknown;
14
+ }
15
+ ? ModelId
16
+ : never
17
+ : never;
12
18
 
13
- export type ExtractImageModelIdFromProvider<
14
- ProviderFactory extends (...args: any) => {
15
- image: (modelId: any) => any;
16
- },
17
- > = Parameters<ReturnType<ProviderFactory>["image"]>[0];
19
+ export type ExtractImageModelIdFromProvider<ProviderFactory> =
20
+ ProviderFactory extends (...args: infer _Args) => infer Provider
21
+ ? Provider extends {
22
+ image: (modelId: infer ModelId, ...args: infer _Rest) => unknown;
23
+ }
24
+ ? ModelId
25
+ : never
26
+ : never;