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 && ( - - )} -