From dff219161182f8fca99e8df6dbd7c27ee9dd6118 Mon Sep 17 00:00:00 2001
From: josh <144584931+dancer@users.noreply.github.com>
Date: Mon, 1 Sep 2025 11:07:07 +0100
Subject: [PATCH] integrate ai gateway and improve ui components (#1145)
---
.env.example | 6 +-
.github/workflows/lint.yml | 29 +++---
README.md | 10 ++-
app/(chat)/api/chat/route.ts | 3 +
artifacts/image/server.ts | 45 ----------
components/artifact-actions.tsx | 6 +-
components/artifact-messages.tsx | 3 +-
components/artifact.tsx | 15 ++--
components/chat-header.tsx | 25 ++----
components/chat.tsx | 5 +-
components/code-editor.tsx | 2 +-
components/document-skeleton.tsx | 2 +-
components/elements/code-block.tsx | 6 ++
components/elements/tool.tsx | 10 +--
components/icons.tsx | 34 +++----
components/message.tsx | 139 +++++++++++++----------------
components/messages.tsx | 5 +-
components/multimodal-input.tsx | 58 ++++++++++++
components/preview-attachment.tsx | 2 +-
components/sidebar-history.tsx | 10 +--
components/ui/sidebar.tsx | 2 +-
components/visibility-selector.tsx | 2 +-
hooks/use-artifact.ts | 2 +-
lib/ai/models.ts | 8 +-
lib/ai/providers.ts | 13 ++-
lib/artifacts/server.ts | 4 +-
package.json | 1 +
pnpm-lock.yaml | 3 +
tests/pages/artifact.ts | 2 +-
29 files changed, 235 insertions(+), 217 deletions(-)
delete mode 100644 artifacts/image/server.ts
diff --git a/.env.example b/.env.example
index 6a40fdd..b25ecc4 100644
--- a/.env.example
+++ b/.env.example
@@ -4,8 +4,10 @@ AUTH_SECRET=****
# The following keys below are automatically created and
# added to your environment when you deploy on vercel
-# Get your xAI API Key here for chat and image models: https://console.x.ai/
-XAI_API_KEY=****
+# AI Gateway API Key (required for non-Vercel deployments)
+# For Vercel deployments, OIDC tokens are used automatically
+AI_GATEWAY_API_KEY=****
+
# Instructions to create a Vercel Blob Store here: https://vercel.com/docs/storage/vercel-blob
BLOB_READ_WRITE_TOKEN=****
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index deda7f5..e24fc77 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -9,17 +9,18 @@ jobs:
matrix:
node-version: [20]
steps:
- - uses: actions/checkout@v4
- - name: Install pnpm
- uses: pnpm/action-setup@v4
- with:
- version: 9.12.3
- - name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v4
- with:
- node-version: ${{ matrix.node-version }}
- cache: 'pnpm'
- - name: Install dependencies
- run: pnpm install
- - name: Run lint
- run: pnpm lint
\ No newline at end of file
+ - uses: actions/checkout@v4
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: 9.12.3
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: "pnpm"
+ - name: Install dependencies
+ run: pnpm install
+ - name: Run lint
+ run: pnpm lint
+1
\ No newline at end of file
diff --git a/README.md b/README.md
index 44a8cb2..8202559 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,15 @@
## Model Providers
-This template ships with [xAI](https://x.ai) `grok-2-1212` as the default chat model. However, with the [AI SDK](https://sdk.vercel.ai/docs), you can switch LLM providers to [OpenAI](https://openai.com), [Anthropic](https://anthropic.com), [Cohere](https://cohere.com/), and [many more](https://sdk.vercel.ai/providers/ai-sdk-providers) with just a few lines of code.
+This template uses the [Vercel AI Gateway](https://vercel.com/docs/ai-gateway) to access multiple AI models through a unified interface. The default configuration includes [xAI](https://x.ai) models (`grok-2-vision-1212`, `grok-3-mini-beta`) routed through the gateway.
+
+### AI Gateway Authentication
+
+**For Vercel deployments**: Authentication is handled automatically via OIDC tokens.
+
+**For non-Vercel deployments**: You need to provide an AI Gateway API key by setting the `AI_GATEWAY_API_KEY` environment variable in your `.env.local` file.
+
+With the [AI SDK](https://ai-sdk.dev/docs/introduction), you can also switch to direct LLM providers like [OpenAI](https://openai.com), [Anthropic](https://anthropic.com), [Cohere](https://cohere.com/), and [many more](https://ai-sdk.dev/providers/ai-sdk-providers) with just a few lines of code.
## Deploy Your Own
diff --git a/app/(chat)/api/chat/route.ts b/app/(chat)/api/chat/route.ts
index b7ea099..8e16929 100644
--- a/app/(chat)/api/chat/route.ts
+++ b/app/(chat)/api/chat/route.ts
@@ -222,6 +222,9 @@ export async function POST(request: Request) {
if (error instanceof ChatSDKError) {
return error.toResponse();
}
+
+ console.error('Unhandled error in chat API:', error);
+ return new ChatSDKError('offline:chat').toResponse();
}
}
diff --git a/artifacts/image/server.ts b/artifacts/image/server.ts
deleted file mode 100644
index 14a994c..0000000
--- a/artifacts/image/server.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import { myProvider } from '@/lib/ai/providers';
-import { createDocumentHandler } from '@/lib/artifacts/server';
-import { experimental_generateImage } from 'ai';
-
-export const imageDocumentHandler = createDocumentHandler<'image'>({
- kind: 'image',
- onCreateDocument: async ({ title, dataStream }) => {
- let draftContent = '';
-
- const { image } = await experimental_generateImage({
- model: myProvider.imageModel('small-model'),
- prompt: title,
- n: 1,
- });
-
- draftContent = image.base64;
-
- dataStream.write({
- type: 'data-imageDelta',
- data: image.base64,
- transient: true,
- });
-
- return draftContent;
- },
- onUpdateDocument: async ({ description, dataStream }) => {
- let draftContent = '';
-
- const { image } = await experimental_generateImage({
- model: myProvider.imageModel('small-model'),
- prompt: description,
- n: 1,
- });
-
- draftContent = image.base64;
-
- dataStream.write({
- type: 'data-imageDelta',
- data: image.base64,
- transient: true,
- });
-
- return draftContent;
- },
-});
diff --git a/components/artifact-actions.tsx b/components/artifact-actions.tsx
index 009cbed..a5689d3 100644
--- a/components/artifact-actions.tsx
+++ b/components/artifact-actions.tsx
@@ -1,8 +1,8 @@
import { Button } from './ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
-import { artifactDefinitions, UIArtifact } from './artifact';
-import { Dispatch, memo, SetStateAction, useState } from 'react';
-import { ArtifactActionContext } from './create-artifact';
+import { artifactDefinitions, type UIArtifact } from './artifact';
+import { type Dispatch, memo, type SetStateAction, useState } from 'react';
+import type { ArtifactActionContext } from './create-artifact';
import { cn } from '@/lib/utils';
import { toast } from 'sonner';
diff --git a/components/artifact-messages.tsx b/components/artifact-messages.tsx
index 4d71435..5cb6f93 100644
--- a/components/artifact-messages.tsx
+++ b/components/artifact-messages.tsx
@@ -42,7 +42,7 @@ function PureArtifactMessages({
return (
{messages.map((message, index) => (
))}
diff --git a/components/artifact.tsx b/components/artifact.tsx
index 6b266d9..9f5d6d7 100644
--- a/components/artifact.tsx
+++ b/components/artifact.tsx
@@ -67,6 +67,7 @@ function PureArtifact({
votes,
isReadonly,
selectedVisibilityType,
+ selectedModelId,
}: {
chatId: string;
input: string;
@@ -82,6 +83,7 @@ function PureArtifact({
regenerate: UseChatHelpers
['regenerate'];
isReadonly: boolean;
selectedVisibilityType: VisibilityType;
+ selectedModelId: string;
}) {
const { artifact, setArtifact, metadata, setMetadata } = useArtifact();
@@ -286,9 +288,9 @@ function PureArtifact({
x: 0,
scale: 1,
transition: {
- delay: 0.2,
+ delay: 0.1,
type: 'spring',
- stiffness: 200,
+ stiffness: 300,
damping: 30,
},
}}
@@ -336,6 +338,7 @@ function PureArtifact({
className="bg-background dark:bg-muted"
setMessages={setMessages}
selectedVisibilityType={selectedVisibilityType}
+ selectedModelId={selectedModelId}
/>
@@ -375,9 +378,9 @@ function PureArtifact({
transition: {
delay: 0,
type: 'spring',
- stiffness: 200,
+ stiffness: 300,
damping: 30,
- duration: 5000,
+ duration: 0.8,
},
}
: {
@@ -392,9 +395,9 @@ function PureArtifact({
transition: {
delay: 0,
type: 'spring',
- stiffness: 200,
+ stiffness: 300,
damping: 30,
- duration: 5000,
+ duration: 0.8,
},
}
}
diff --git a/components/chat-header.tsx b/components/chat-header.tsx
index f21f151..8370906 100644
--- a/components/chat-header.tsx
+++ b/components/chat-header.tsx
@@ -4,7 +4,6 @@ import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { useWindowSize } from 'usehooks-ts';
-import { ModelSelector } from '@/components/model-selector';
import { SidebarToggle } from '@/components/sidebar-toggle';
import { Button } from '@/components/ui/button';
import { PlusIcon, VercelIcon } from './icons';
@@ -16,13 +15,11 @@ import type { Session } from 'next-auth';
function PureChatHeader({
chatId,
- selectedModelId,
selectedVisibilityType,
isReadonly,
session,
}: {
chatId: string;
- selectedModelId: string;
selectedVisibilityType: VisibilityType;
isReadonly: boolean;
session: Session;
@@ -56,23 +53,15 @@ function PureChatHeader({
)}
{!isReadonly && (
-
)}
- {!isReadonly && (
-
- )}
-