@chat-js/cli 0.4.0 → 0.6.0

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 +1163 -959
  2. package/package.json +1 -1
  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 +20 -18
  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 +157 -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chat-js/cli",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "CLI for creating and extending ChatJS apps",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -0,0 +1,37 @@
1
+ import type { Metadata } from "next";
2
+ import { headers } from "next/headers";
3
+ import { redirect } from "next/navigation";
4
+ import { DeviceLoginPage } from "@/components/device-login-page";
5
+ import { auth } from "@/lib/auth";
6
+ import { config } from "@/lib/config";
7
+ import { toSearchParamRecord } from "@/lib/electron-auth";
8
+
9
+ export const metadata: Metadata = {
10
+ title: "Device Login",
11
+ description: "Sign in for the desktop app",
12
+ };
13
+
14
+ export default async function DeviceLoginRoute({
15
+ searchParams,
16
+ }: {
17
+ searchParams: Promise<Record<string, string | string[] | undefined>>;
18
+ }) {
19
+ if (!config.desktopApp.enabled) {
20
+ redirect("/login");
21
+ }
22
+
23
+ const resolvedSearchParams = await searchParams;
24
+ const query = toSearchParamRecord(resolvedSearchParams);
25
+ const isCompletedView = query.done === "1";
26
+ const queryString = new URLSearchParams(query).toString();
27
+ const currentHref = queryString
28
+ ? `/device-login?${queryString}`
29
+ : "/device-login";
30
+ const session = await auth.api.getSession({ headers: await headers() });
31
+
32
+ if (!(session?.user || isCompletedView)) {
33
+ redirect(`/login?returnTo=${encodeURIComponent(currentHref)}`);
34
+ }
35
+
36
+ return <DeviceLoginPage />;
37
+ }
@@ -1,8 +1,16 @@
1
1
  import { ChevronLeft } from "lucide-react";
2
2
  import type { Metadata } from "next";
3
+ import { headers } from "next/headers";
3
4
  import Link from "next/link";
5
+ import { ElectronTransferUser } from "@/components/electron-auth-ui";
4
6
  import { LoginForm } from "@/components/login-form";
5
7
  import { buttonVariants } from "@/components/ui/button";
8
+ import { auth } from "@/lib/auth";
9
+ import { config } from "@/lib/config";
10
+ import {
11
+ ELECTRON_AUTH_CLIENT_ID,
12
+ toSearchParamRecord,
13
+ } from "@/lib/electron-auth";
6
14
  import { cn } from "@/lib/utils";
7
15
 
8
16
  export const metadata: Metadata = {
@@ -10,7 +18,19 @@ export const metadata: Metadata = {
10
18
  description: "Login to your account",
11
19
  };
12
20
 
13
- export default function LoginPage() {
21
+ export default async function LoginPage({
22
+ searchParams,
23
+ }: {
24
+ searchParams: Promise<Record<string, string | string[] | undefined>>;
25
+ }) {
26
+ const resolvedSearchParams = await searchParams;
27
+ const query = toSearchParamRecord(resolvedSearchParams);
28
+ const isElectronTransfer =
29
+ config.desktopApp.enabled && query.client_id === ELECTRON_AUTH_CLIENT_ID;
30
+ const session = isElectronTransfer
31
+ ? await auth.api.getSession({ headers: await headers() })
32
+ : null;
33
+
14
34
  return (
15
35
  <div className="container mx-auto flex h-dvh w-screen flex-col items-center justify-center">
16
36
  <Link
@@ -24,7 +44,11 @@ export default function LoginPage() {
24
44
  Back
25
45
  </Link>
26
46
  <div className="mx-auto flex w-full flex-col items-center justify-center sm:w-[420px]">
27
- <LoginForm className="w-full" />
47
+ {session?.user && isElectronTransfer ? (
48
+ <ElectronTransferUser query={query} session={session} />
49
+ ) : (
50
+ <LoginForm className="w-full" />
51
+ )}
28
52
  </div>
29
53
  </div>
30
54
  );
@@ -1,8 +1,5 @@
1
1
  import type { Metadata } from "next";
2
- import Link from "next/link";
3
2
  import { SignupForm } from "@/components/signup-form";
4
- import { buttonVariants } from "@/components/ui/button";
5
- import { cn } from "@/lib/utils";
6
3
 
7
4
  export const metadata: Metadata = {
8
5
  title: "Create an account",
@@ -12,15 +9,6 @@ export const metadata: Metadata = {
12
9
  export default function RegisterPage() {
13
10
  return (
14
11
  <div className="container m-auto flex h-dvh w-screen flex-col items-center justify-center px-4">
15
- <Link
16
- className={cn(
17
- buttonVariants({ variant: "ghost" }),
18
- "absolute top-4 right-4 md:top-8 md:right-8"
19
- )}
20
- href="/login"
21
- >
22
- Login
23
- </Link>
24
12
  <div className="mx-auto w-full sm:w-[480px]">
25
13
  <SignupForm />
26
14
  </div>
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Note: data-* parts are handled by convertToModelMessages({ convertDataPart: () => undefined })
7
7
  */
8
- export function filterPartsForLLM<T extends { parts: any[] }>(
8
+ export function filterPartsForLLM<T extends { parts: Array<{ type: string }> }>(
9
9
  messages: T[]
10
10
  ): T[] {
11
11
  return messages.map((message) => ({
@@ -25,11 +25,12 @@ import {
25
25
  streamFollowupSuggestions,
26
26
  } from "@/lib/ai/followup-suggestions";
27
27
  import { systemPrompt } from "@/lib/ai/prompts";
28
+ import { getStreamErrorMessage } from "@/lib/ai/stream-errors";
28
29
  import { calculateMessagesTokens } from "@/lib/ai/token-utils";
29
30
  import { allTools } from "@/lib/ai/tools/tools-definitions";
30
31
  import {
31
- getPrimarySelectedModelId,
32
32
  type ChatMessage,
33
+ getPrimarySelectedModelId,
33
34
  type ToolName,
34
35
  } from "@/lib/ai/types";
35
36
  import {
@@ -227,7 +228,9 @@ function resolveSelectedModelId({
227
228
  }
228
229
 
229
230
  if (requestSelectedModelId) {
230
- if (!selectedModel || typeof selectedModel !== "object") return null;
231
+ if (!selectedModel || typeof selectedModel !== "object") {
232
+ return null;
233
+ }
231
234
  const requestedCount = selectedModel[requestSelectedModelId];
232
235
  return requestedCount && requestedCount > 0 ? requestSelectedModelId : null;
233
236
  }
@@ -480,7 +483,10 @@ async function createChatStream({
480
483
  if (!isAnonymous) {
481
484
  after(() =>
482
485
  Promise.resolve(
483
- updateMessageActiveStreamId({ id: messageId, activeStreamId: null })
486
+ updateMessageActiveStreamId({
487
+ id: messageId,
488
+ activeStreamId: null,
489
+ })
484
490
  ).catch((dbError) => {
485
491
  log.error(
486
492
  { error: dbError },
@@ -491,7 +497,7 @@ async function createChatStream({
491
497
  }
492
498
 
493
499
  log.error({ error }, "onError");
494
- return "Oops, an error occured!";
500
+ return getStreamErrorMessage(error);
495
501
  },
496
502
  });
497
503
 
@@ -794,7 +800,9 @@ async function finalizeMessageAndCredits({
794
800
  metadata: {
795
801
  ...assistantMessage.metadata,
796
802
  parallelGroupId:
797
- parallelGroupId ?? assistantMessage.metadata.parallelGroupId ?? null,
803
+ parallelGroupId ??
804
+ assistantMessage.metadata.parallelGroupId ??
805
+ null,
798
806
  parallelIndex:
799
807
  parallelIndex ?? assistantMessage.metadata.parallelIndex ?? null,
800
808
  isPrimaryParallel:
@@ -5,6 +5,7 @@ import { KeyboardShortcuts } from "@/components/keyboard-shortcuts";
5
5
  import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
6
6
  import type { AppModelId } from "@/lib/ai/app-model-id";
7
7
  import { config } from "@/lib/config";
8
+ import { isPlaywrightTestEnvironment } from "@/lib/constants";
8
9
  import { ANONYMOUS_LIMITS } from "@/lib/types/anonymous";
9
10
  import { ChatModelsProvider } from "@/providers/chat-models-provider";
10
11
  import { DefaultModelProvider } from "@/providers/default-model-provider";
@@ -24,7 +25,9 @@ export default async function ChatLayout({
24
25
  headers(),
25
26
  getChatModels(),
26
27
  ]);
27
- const session = await auth.api.getSession({ headers: headersRes });
28
+ const session = isPlaywrightTestEnvironment
29
+ ? null
30
+ : await auth.api.getSession({ headers: headersRes });
28
31
  const isCollapsed = cookieStore.get("sidebar:state")?.value !== "true";
29
32
 
30
33
  const cookieModel = cookieStore.get("chat-model")?.value;
@@ -9,4 +9,5 @@ const handler = (req: Request) =>
9
9
  router: appRouter,
10
10
  createContext: createTRPCContext,
11
11
  });
12
+
12
13
  export { handler as GET, handler as POST };
@@ -245,17 +245,17 @@
245
245
  }
246
246
 
247
247
  :root {
248
- --background: hsl(0 0% 97.0392%);
248
+ --background: hsl(0 0% 100%);
249
249
  --foreground: hsl(0 0% 20%);
250
- --card: hsl(0 0% 100%);
250
+ --card: hsl(0 0% 97.0392%);
251
251
  --card-foreground: hsl(0 0% 20%);
252
- --popover: hsl(0 0% 100%);
252
+ --popover: hsl(0 0% 97.0392%);
253
253
  --popover-foreground: hsl(0 0% 20%);
254
254
  --primary: hsl(217.2193 91.2195% 59.8039%);
255
255
  --primary-foreground: hsl(0 0% 100%);
256
- --secondary: hsl(220 14.2857% 95.8824%);
256
+ --secondary: hsl(0 0% 97.0392%);
257
257
  --secondary-foreground: hsl(215 13.7931% 34.1176%);
258
- --muted: hsl(210 20% 98.0392%);
258
+ --muted: hsl(0 0% 97.0392%);
259
259
  --muted-foreground: hsl(220 8.9362% 46.0784%);
260
260
  --accent: hsl(204 93.75% 93.7255%);
261
261
  --accent-foreground: hsl(224.4444 64.2857% 32.9412%);
@@ -269,7 +269,7 @@
269
269
  --chart-3: hsl(224.2781 76.3265% 48.0392%);
270
270
  --chart-4: hsl(225.931 70.7317% 40.1961%);
271
271
  --chart-5: hsl(224.4444 64.2857% 32.9412%);
272
- --sidebar: hsl(210 20% 98.0392%);
272
+ --sidebar: hsl(0 0% 97.0392%);
273
273
  --sidebar-foreground: hsl(0 0% 20%);
274
274
  --sidebar-primary: hsl(217.2193 91.2195% 59.8039%);
275
275
  --sidebar-primary-foreground: hsl(0 0% 100%);
@@ -383,13 +383,13 @@
383
383
 
384
384
  .cm-editor,
385
385
  .cm-gutters {
386
- @apply bg-background! dark:bg-zinc-800! outline-hidden! selection:bg-zinc-900!;
386
+ @apply bg-card! outline-hidden! selection:bg-zinc-900!;
387
387
  }
388
388
 
389
389
  .ͼo.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground,
390
390
  .ͼo.cm-selectionBackground,
391
391
  .ͼo.cm-content::selection {
392
- @apply bg-zinc-200! dark:bg-zinc-900!;
392
+ @apply bg-accent!;
393
393
  }
394
394
 
395
395
  .cm-activeLine,
@@ -414,7 +414,7 @@
414
414
  }
415
415
 
416
416
  .suggestion-highlight {
417
- @apply bg-blue-200 hover:bg-blue-300 dark:hover:bg-blue-400/50 dark:text-blue-50 dark:bg-blue-500/40;
417
+ @apply bg-accent text-accent-foreground hover:bg-accent/80;
418
418
  }
419
419
 
420
420
  /* Lexical Editor Styles */
@@ -7,6 +7,7 @@ import Script from "next/script";
7
7
  import "./globals.css";
8
8
  import { NuqsAdapter } from "nuqs/adapters/next/app";
9
9
  import { Toaster } from "sonner";
10
+ import { ElectronAuthHandler } from "@/components/electron-auth-handler";
10
11
  import { ThemeProvider } from "@/components/theme-provider";
11
12
  import { config } from "@/lib/config";
12
13
 
@@ -78,14 +79,15 @@ export default async function RootLayout({
78
79
  <Script id="theme-color-script" strategy="beforeInteractive">
79
80
  {THEME_COLOR_SCRIPT}
80
81
  </Script>
81
- {process.env.NODE_ENV !== "production" ? (
82
+ {process.env.NODE_ENV === "production" ? null : (
82
83
  <Script
83
84
  src="https://unpkg.com/react-scan/dist/auto.global.js"
84
85
  strategy="beforeInteractive"
85
86
  />
86
- ) : null}
87
+ )}
87
88
  </head>
88
89
  <body className="antialiased">
90
+ <ElectronAuthHandler />
89
91
  <Script
90
92
  src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"
91
93
  strategy="afterInteractive"
@@ -17,9 +17,9 @@
17
17
  },
18
18
  "linter": {
19
19
  "rules": {
20
- "suspicious": {
21
- /* Needs more work to fix */
22
- "noExplicitAny": "off"
20
+ "style": {
21
+ /* Allow mixing interface and type — type is required for intersection/mapped types */
22
+ "useConsistentTypeDefinitions": "off"
23
23
  }
24
24
  }
25
25
  }
@@ -9,147 +9,150 @@ const isProd = process.env.NODE_ENV === "production";
9
9
  * @see https://chatjs.dev/docs/reference/config
10
10
  */
11
11
  const config = defineConfig({
12
- appPrefix: "chatjs",
13
- appName: "ChatJS",
14
- appTitle: "ChatJS - The prod ready AI chat app",
15
- appDescription:
16
- "Build and deploy AI chat applications in minutes. ChatJS provides authentication, streaming, tool calling, and all the features you need for production-ready AI conversations.",
17
- appUrl: "https://chatjs.dev",
18
- organization: {
19
- name: "ChatJS",
20
- contact: {
21
- privacyEmail: "privacy@chatjs.dev",
22
- legalEmail: "legal@chatjs.dev",
23
- },
24
- },
25
- services: {
26
- hosting: "Vercel",
27
- aiProviders: [
28
- "OpenAI",
29
- "Anthropic",
30
- "xAI",
31
- "Google",
32
- "Meta",
33
- "Mistral",
34
- "Alibaba",
35
- "Amazon",
36
- "Cohere",
37
- "DeepSeek",
38
- "Perplexity",
39
- "Vercel",
40
- "Inception",
41
- "Moonshot",
42
- "Morph",
43
- "ZAI",
44
- ],
45
- paymentProcessors: [],
46
- },
47
- features: {
48
- attachments: true, // Requires BLOB_READ_WRITE_TOKEN
49
- parallelResponses: true,
50
- },
51
- legal: {
52
- minimumAge: 13,
53
- governingLaw: "United States",
54
- refundPolicy: "no-refunds",
55
- },
56
- policies: {
57
- privacy: {
58
- title: "Privacy Policy",
59
- lastUpdated: "July 24, 2025",
60
- },
61
- terms: {
62
- title: "Terms of Service",
63
- lastUpdated: "July 24, 2025",
64
- },
65
- },
66
- authentication: {
67
- google: true, // Requires AUTH_GOOGLE_ID + AUTH_GOOGLE_SECRET
68
- github: true, // Requires AUTH_GITHUB_ID + AUTH_GITHUB_SECRET
69
- vercel: true, // Requires VERCEL_APP_CLIENT_ID + VERCEL_APP_CLIENT_SECRET
70
- },
71
- ai: {
72
- gateway: "vercel",
73
- providerOrder: [
74
- "openai",
75
- "anthropic",
76
- "google",
77
- "xai",
78
- "meta",
79
- "mistral",
80
- "deepseek",
81
- "perplexity",
82
- "cohere",
83
- "alibaba",
84
- "amazon",
85
- "inception",
86
- "moonshot",
87
- "morph",
88
- "zai",
89
- ],
90
- disabledModels: [],
91
- anonymousModels: ["openai/gpt-5-nano"],
92
- workflows: {
93
- chatImageCompatible: "openai/gpt-4o-mini",
94
- },
95
- tools: {
96
- webSearch: {
97
- enabled: true, // Requires TAVILY_API_KEY or FIRECRAWL_API_KEY
98
- },
99
- urlRetrieval: {
100
- enabled: true, // Requires FIRECRAWL_API_KEY
101
- },
102
- codeExecution: {
103
- enabled: true, // Vercel-native, no key needed
104
- },
105
- mcp: {
106
- enabled: true, // Requires MCP_ENCRYPTION_KEY
107
- },
108
- followupSuggestions: {
109
- enabled: true,
110
- },
111
- text: {
112
- polish: "openai/gpt-5-mini",
113
- },
114
- sheet: {
115
- format: "openai/gpt-5-mini",
116
- analyze: "openai/gpt-5-mini",
117
- },
118
- code: {
119
- edits: "openai/gpt-5-mini",
120
- },
121
- image: {
122
- enabled: true, // Requires BLOB_READ_WRITE_TOKEN
123
- default: "google/gemini-3-pro-image",
124
- },
125
- deepResearch: {
126
- enabled: true, // Requires webSearch
127
- defaultModel: "openai/gpt-5-nano",
128
- finalReportModel: "openai/gpt-5-mini",
129
- allowClarification: true,
130
- maxResearcherIterations: 1,
131
- maxConcurrentResearchUnits: 2,
132
- maxSearchQueries: 2,
133
- },
134
- },
135
- },
136
- anonymous: {
137
- credits: isProd ? 10 : 1000,
138
- availableTools: [],
139
- rateLimit: {
140
- requestsPerMinute: isProd ? 5 : 60,
141
- requestsPerMonth: isProd ? 10 : 1000,
142
- },
143
- },
144
- attachments: {
145
- maxBytes: 1024 * 1024, // 1MB
146
- maxDimension: 2048,
147
- acceptedTypes: {
148
- "image/png": [".png"],
149
- "image/jpeg": [".jpg", ".jpeg"],
150
- "application/pdf": [".pdf"],
151
- },
152
- },
12
+ appPrefix: "chatjs",
13
+ appName: "ChatJS",
14
+ appTitle: "ChatJS - The prod ready AI chat app",
15
+ appDescription:
16
+ "Build and deploy AI chat applications in minutes. ChatJS provides authentication, streaming, tool calling, and all the features you need for production-ready AI conversations.",
17
+ appUrl: "https://www.demo.chatjs.dev",
18
+ organization: {
19
+ name: "ChatJS",
20
+ contact: {
21
+ privacyEmail: "privacy@chatjs.dev",
22
+ legalEmail: "legal@chatjs.dev",
23
+ },
24
+ },
25
+ services: {
26
+ hosting: "Vercel",
27
+ aiProviders: [
28
+ "OpenAI",
29
+ "Anthropic",
30
+ "xAI",
31
+ "Google",
32
+ "Meta",
33
+ "Mistral",
34
+ "Alibaba",
35
+ "Amazon",
36
+ "Cohere",
37
+ "DeepSeek",
38
+ "Perplexity",
39
+ "Vercel",
40
+ "Inception",
41
+ "Moonshot",
42
+ "Morph",
43
+ "ZAI",
44
+ ],
45
+ paymentProcessors: [],
46
+ },
47
+ features: {
48
+ attachments: true, // Requires BLOB_READ_WRITE_TOKEN
49
+ parallelResponses: true,
50
+ },
51
+ legal: {
52
+ minimumAge: 13,
53
+ governingLaw: "United States",
54
+ refundPolicy: "no-refunds",
55
+ },
56
+ policies: {
57
+ privacy: {
58
+ title: "Privacy Policy",
59
+ lastUpdated: "July 24, 2025",
60
+ },
61
+ terms: {
62
+ title: "Terms of Service",
63
+ lastUpdated: "July 24, 2025",
64
+ },
65
+ },
66
+ authentication: {
67
+ google: true, // Requires AUTH_GOOGLE_ID + AUTH_GOOGLE_SECRET
68
+ github: true, // Requires AUTH_GITHUB_ID + AUTH_GITHUB_SECRET
69
+ vercel: true, // Requires VERCEL_APP_CLIENT_ID + VERCEL_APP_CLIENT_SECRET
70
+ },
71
+ desktopApp: {
72
+ enabled: true,
73
+ },
74
+ ai: {
75
+ gateway: "vercel",
76
+ providerOrder: [
77
+ "openai",
78
+ "anthropic",
79
+ "google",
80
+ "xai",
81
+ "meta",
82
+ "mistral",
83
+ "deepseek",
84
+ "perplexity",
85
+ "cohere",
86
+ "alibaba",
87
+ "amazon",
88
+ "inception",
89
+ "moonshot",
90
+ "morph",
91
+ "zai",
92
+ ],
93
+ disabledModels: [],
94
+ anonymousModels: ["openai/gpt-5-nano"],
95
+ workflows: {
96
+ chatImageCompatible: "openai/gpt-4o-mini",
97
+ },
98
+ tools: {
99
+ webSearch: {
100
+ enabled: true, // Requires TAVILY_API_KEY or FIRECRAWL_API_KEY
101
+ },
102
+ urlRetrieval: {
103
+ enabled: true, // Requires FIRECRAWL_API_KEY
104
+ },
105
+ codeExecution: {
106
+ enabled: true, // Vercel-native, no key needed
107
+ },
108
+ mcp: {
109
+ enabled: true, // Requires MCP_ENCRYPTION_KEY
110
+ },
111
+ followupSuggestions: {
112
+ enabled: true,
113
+ },
114
+ text: {
115
+ polish: "openai/gpt-5-mini",
116
+ },
117
+ sheet: {
118
+ format: "openai/gpt-5-mini",
119
+ analyze: "openai/gpt-5-mini",
120
+ },
121
+ code: {
122
+ edits: "openai/gpt-5-mini",
123
+ },
124
+ image: {
125
+ enabled: true, // Requires BLOB_READ_WRITE_TOKEN
126
+ default: "google/gemini-3-pro-image",
127
+ },
128
+ deepResearch: {
129
+ enabled: true, // Requires webSearch
130
+ defaultModel: "openai/gpt-5-nano",
131
+ finalReportModel: "openai/gpt-5-mini",
132
+ allowClarification: true,
133
+ maxResearcherIterations: 1,
134
+ maxConcurrentResearchUnits: 2,
135
+ maxSearchQueries: 2,
136
+ },
137
+ },
138
+ },
139
+ anonymous: {
140
+ credits: isProd ? 10 : 1000,
141
+ availableTools: [],
142
+ rateLimit: {
143
+ requestsPerMinute: isProd ? 5 : 60,
144
+ requestsPerMonth: isProd ? 10 : 1000,
145
+ },
146
+ },
147
+ attachments: {
148
+ maxBytes: 1024 * 1024, // 1MB
149
+ maxDimension: 2048,
150
+ acceptedTypes: {
151
+ "image/png": [".png"],
152
+ "image/jpeg": [".jpg", ".jpeg"],
153
+ "application/pdf": [".pdf"],
154
+ },
155
+ },
153
156
  });
154
157
 
155
158
  export default config;
@@ -286,7 +286,7 @@ export function PromptInputAttachment({
286
286
  <HoverCardTrigger asChild>
287
287
  <div
288
288
  className={cn(
289
- "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",
289
+ "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",
290
290
  className
291
291
  )}
292
292
  key={data.id}
@@ -13,13 +13,17 @@ import { useSession } from "@/providers/session-provider";
13
13
  import { useTRPC } from "@/trpc/react";
14
14
 
15
15
  // Schema validation function
16
- function isValidAnonymousSession(obj: any): obj is AnonymousSession {
16
+ function isValidAnonymousSession(obj: unknown): obj is AnonymousSession {
17
+ if (!obj || typeof obj !== "object") {
18
+ return false;
19
+ }
20
+
21
+ const session = obj as Partial<AnonymousSession>;
22
+
17
23
  return (
18
- obj &&
19
- typeof obj === "object" &&
20
- typeof obj.id === "string" &&
21
- typeof obj.remainingCredits === "number" &&
22
- (obj.createdAt instanceof Date || typeof obj.createdAt === "string")
24
+ typeof session.id === "string" &&
25
+ typeof session.remainingCredits === "number" &&
26
+ (session.createdAt instanceof Date || typeof session.createdAt === "string")
23
27
  );
24
28
  }
25
29