Compare commits

..

34 Commits

Author SHA1 Message Date
dayuan.jiang
476a632d1b fix: update @ai-sdk/amazon-bedrock to 3.0.62 for tool streaming support 2025-11-30 16:32:40 +09:00
dayuan.jiang
0cba721d33 Merge branch 'main' into feat/tool-streaming 2025-11-30 16:17:40 +09:00
dayuan.jiang
de910bdf6c fix: correct anthropic beta header config for fine-grained tool streaming
- Use bedrock.anthropicBeta for Bedrock provider (not additionalModelRequestFields)
- Use top-level headers for direct Anthropic API
- Update @ai-sdk/amazon-bedrock to 3.0.62
- Add headers support to ModelConfig interface
2025-11-30 16:12:12 +09:00
Ming long Hu
6e6de1eba6 feat: add copy button to user messages in chat sidebar (#21)
* Initial plan

* Initial plan for adding copy button to user messages

Co-authored-by: huminglong <63436986+huminglong@users.noreply.github.com>

* Add copy button to user messages in chat sidebar

Co-authored-by: huminglong <63436986+huminglong@users.noreply.github.com>

* Refactor: extract userMessageText to avoid duplicate function calls

Co-authored-by: huminglong <63436986+huminglong@users.noreply.github.com>

* feat(ui): 增加消息复制功能支持非 HTTPS 环境

优化复制消息到剪贴板的逻辑,增加对非 HTTPS 环境的降级处理,提升用户体验。

* fix(package): 添加 peer 属性以支持依赖关系

* chore(.gitignore): 添加 next 和 next-ai-draw-io@0.2.0 相关条目

* fix: improve copy button implementation

- Fix copy button alignment (place left of user message)
- Remove unused React import
- Move getMessageTextContent outside component
- Add error state with X icon for copy failures
- Translate Chinese comments to English
- Simplify copy function
- Add missing semicolon
- Remove accidental .gitignore entries

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: dayuan.jiang <jdy.toh@gmail.com>
2025-11-29 13:39:35 +09:00
dayuan.jiang
00af87edbe fix: prevent duplicate PR review comments by tracking existing file:line 2025-11-29 12:55:53 +09:00
dayuan.jiang
468d6c0276 fix: require suggested fixes in PR review comments 2025-11-29 12:46:24 +09:00
dayuan.jiang
14f74b076f fix: remove gh pr comment from allowed tools to force inline comments only 2025-11-29 12:38:35 +09:00
dayuan.jiang
78d9229ca3 fix: remove gh pr comment from allowed tools to force inline comments only 2025-11-29 12:36:35 +09:00
dayuan.jiang
d8e0a1daad fix: enforce inline comments for all issues found
- Explicitly require mcp inline comment tool for each issue
- Clarify gh pr comment is only for final summary
- Forbid dismissing issues as minor/harmless
2025-11-29 12:31:46 +09:00
Dayuan Jiang
32e75ab556 feat: expand PR review scope to catch more issues (#25)
* feat: use pull_request_target to support fork PR reviews

* feat: expand PR review scope to catch more issues

- Add categories for scope creep, suspicious .gitignore additions, UI inconsistencies
- Change from 'be very selective' to 'report ALL issues found'
- Simplify DO NOT comment list to allow more actionable feedback
2025-11-29 12:22:21 +09:00
Dayuan Jiang
b87e3a2de9 feat: use pull_request_target to support fork PR reviews (#24) 2025-11-29 11:14:49 +09:00
dayuan.jiang
b758f63d7f feat: use pull_request_target to support fork PR reviews 2025-11-29 11:09:51 +09:00
Dayuan Jiang
b32d42d962 Merge pull request #23 from DayuanJiang/feature/claude-code-actions
Feature/claude code actions
2025-11-29 01:50:27 +09:00
dayuan.jiang
603865fdb0 fix: add reopened trigger for pr-review workflow 2025-11-29 01:45:44 +09:00
dayuan.jiang
4cb4e187b3 Trigger PR review 2025-11-29 01:40:13 +09:00
Dayuan Jiang
b4d0c6c18b Merge pull request #22 from DayuanJiang/feature/claude-code-actions
Add Claude Code GitHub Actions with Bedrock and auto PR review
2025-11-29 01:32:06 +09:00
dayuan.jiang
c03c41d320 Run PR review on both opened and synchronize events 2025-11-29 01:29:25 +09:00
dayuan.jiang
9d248e25ad Resolve/collapse comment threads when issues are fixed 2025-11-29 01:26:00 +09:00
dayuan.jiang
4f4aae0e39 Check previous comments and mark resolved if fixed 2025-11-29 01:25:15 +09:00
dayuan.jiang
b00579b257 Remove automatic comment cleanup - GitHub handles outdated comments 2025-11-29 01:23:01 +09:00
dayuan.jiang
caf7ffe56c Clean up outdated review comments before new review 2025-11-29 01:21:44 +09:00
dayuan.jiang
50d16cbe47 Allow Claude to fetch AI SDK docs during PR review 2025-11-29 01:20:20 +09:00
dayuan.jiang
56167d363c Add specific AI SDK patterns to PR review prompt 2025-11-29 01:18:25 +09:00
dayuan.jiang
3e2dbbb541 Improve PR review prompt: add project context, narrow scope to real bugs only 2025-11-29 01:14:36 +09:00
dayuan.jiang
efebcea3ba Fix PR review: use correct params and enable inline comments 2025-11-29 00:58:02 +09:00
dayuan.jiang
68824bc951 Enable inline code review comments like Vercel bot 2025-11-29 00:52:57 +09:00
dayuan.jiang
794826550d Use stable v1 instead of beta for claude-code-action 2025-11-29 00:44:58 +09:00
dayuan.jiang
de98cf60ae Add Claude Code GitHub Actions with Bedrock and auto PR review 2025-11-29 00:29:56 +09:00
Dayuan Jiang
b3fc624e13 Merge pull request #16 from ylxmf2005/main
feat: support OpenAI compatible llm
2025-11-22 09:52:30 +09:00
ylxmf
d2dd501f3f feat: support OpenAI compatible llm 2025-11-21 17:03:47 +08:00
Dayuan Jiang
5964deeff7 Merge pull request #15 from DayuanJiang/docs/update-examples
Docs/update examples
2025-11-17 20:47:00 +09:00
Dayuan Jiang
8d36e0dfb0 Merge pull request #14 from DayuanJiang/docs/update-examples
docs: Update examples with new prompts and demo images
2025-11-17 19:03:29 +09:00
Dayuan Jiang
60f4694752 Merge pull request #13 from DayuanJiang/fix/resize
fix: Preserve state when resizing window to mobile size
2025-11-17 18:59:07 +09:00
dayuan.jiang
aa330f74b3 fix: Preserve state when resizing window to mobile size
Change mobile warning from unmounting components to showing an overlay.
This prevents losing diagram and chat state when window is resized.
2025-11-17 15:18:29 +09:00
10 changed files with 4981 additions and 109 deletions

6
.eslintrc.json Normal file
View File

@@ -0,0 +1,6 @@
{
"extends": [
"next/core-web-vitals",
"next/typescript"
]
}

118
.github/workflows/claude-code.yml vendored Normal file
View File

@@ -0,0 +1,118 @@
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
pull_request_target:
types: [opened, synchronize, reopened]
jobs:
claude:
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && contains(github.event.issue.body, '@claude'))
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: us-east-1
- name: Run Claude Code
uses: anthropics/claude-code-action@v1
with:
use_bedrock: "true"
github_token: ${{ secrets.GITHUB_TOKEN }}
pr-review:
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: us-east-1
- name: Run Claude Code PR Review
uses: anthropics/claude-code-action@v1
with:
use_bedrock: "true"
github_token: ${{ secrets.GITHUB_TOKEN }}
allowed_non_write_users: "*"
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}
This is a personal project - an AI-powered draw.io diagram generator built with:
- Next.js 15 with React 19
- Vercel AI SDK (streamText, useChat, tool calling)
- Multiple AI providers: Bedrock, Anthropic, OpenAI, Google, Azure, OpenRouter, Ollama
STEP 1: Check existing comments to avoid duplicates.
Run: `gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/comments`
Build a list of files and line numbers that already have comments. For each existing comment:
- If the issue is FIXED in current code, resolve the thread using:
`gh api graphql -f query='mutation { resolveReviewThread(input: {threadId: "THREAD_ID"}) { thread { isResolved } } }'`
- If the issue still exists, remember this file:line - DO NOT create a new comment for it
STEP 2: Review the diff for issues, but SKIP any file:line that already has a comment.
Review this PR for these issues (report ALL that apply):
1. Bugs that would cause runtime errors or broken functionality
2. Security issues (exposed secrets, API key leaks)
3. AI SDK misuse - specifically check for:
- Client-side: Should use useChat/useCompletion/useObject hooks, NOT raw fetch()
- Server-side: Should use streamText/generateText/streamObject/generateObject
- Message handling: Access message.parts array, not legacy content property
- Tool definitions: Must use Zod schemas for inputSchema
- Status handling: Check status (submitted/streaming/ready/error) before actions
- Stream cleanup: Call stop() when aborting streams
4. Unrelated changes that should be in separate PRs (scope creep)
5. Suspicious .gitignore additions or accidentally committed files
6. UI/UX inconsistencies (e.g., alignment issues)
When reviewing AI SDK usage, fetch https://ai-sdk.dev/docs/ to verify correct patterns.
Key doc pages: /docs/ai-sdk-ui/chatbot, /docs/ai-sdk-core/generating-text, /docs/ai-sdk-core/tools-and-tool-calling
DO NOT comment on:
- Minor performance optimizations
- Code style preferences (unless clearly wrong)
- Type annotations that don't affect functionality
IMPORTANT:
- NEVER create a comment on a file:line that already has a comment - this causes duplicates
- For each NEW issue, use `mcp__github_inline_comment__create_inline_comment` to comment on the specific line
- ALWAYS include a suggested fix using GitHub's suggestion syntax: ```suggestion\n<fixed code>\n```
- Only say "LGTM" if there are truly ZERO new issues to report
claude_args: |
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh api:*),WebFetch(domain:ai-sdk.dev)"

View File

@@ -68,7 +68,7 @@ Diagrams are represented as XML that can be rendered in draw.io. The AI processe
## Multi-Provider Support ## Multi-Provider Support
- AWS Bedrock (default) - AWS Bedrock (default)
- OpenAI - OpenAI / OpenAI-compatible APIs (via `OPENAI_BASE_URL`)
- Anthropic - Anthropic
- Google AI - Google AI
- Azure OpenAI - Azure OpenAI

View File

@@ -120,13 +120,14 @@ ${lastMessageText}
console.log("Enhanced messages:", enhancedMessages); console.log("Enhanced messages:", enhancedMessages);
// Get AI model from environment configuration // Get AI model from environment configuration
const { model, providerOptions } = getAIModel(); const { model, providerOptions, headers } = getAIModel();
const result = streamText({ const result = streamText({
model, model,
system: systemMessage, system: systemMessage,
messages: enhancedMessages, messages: enhancedMessages,
...(providerOptions && { providerOptions }), ...(providerOptions && { providerOptions }),
...(headers && { headers }),
tools: { tools: {
// Client-side tool that will be executed on the client // Client-side tool that will be executed on the client
display_diagram: { display_diagram: {

View File

@@ -40,20 +40,19 @@ export default function Home() {
}; };
}, []); }, []);
if (isMobile) {
return (
<div className="flex items-center justify-center h-screen bg-gray-100">
<div className="text-center p-8">
<h1 className="text-2xl font-semibold text-gray-800">
Please open this application on a desktop or laptop
</h1>
</div>
</div>
);
}
return ( return (
<div className="flex h-screen bg-gray-100"> <div className="flex h-screen bg-gray-100 relative">
{/* Mobile warning overlay - keeps components mounted */}
{isMobile && (
<div className="absolute inset-0 z-50 flex items-center justify-center bg-gray-100">
<div className="text-center p-8">
<h1 className="text-2xl font-semibold text-gray-800">
Please open this application on a desktop or laptop
</h1>
</div>
</div>
)}
<div className={`${isChatVisible ? 'w-2/3' : 'w-full'} p-1 h-full relative transition-all duration-300 ease-in-out`}> <div className={`${isChatVisible ? 'w-2/3' : 'w-full'} p-1 h-full relative transition-all duration-300 ease-in-out`}>
<DrawIoEmbed <DrawIoEmbed
ref={drawioRef} ref={drawioRef}

View File

@@ -1,15 +1,23 @@
"use client"; "use client";
import type React from "react";
import { useRef, useEffect, useState, useCallback } from "react"; import { useRef, useEffect, useState, useCallback } from "react";
import Image from "next/image"; import Image from "next/image";
import { ScrollArea } from "@/components/ui/scroll-area"; import { ScrollArea } from "@/components/ui/scroll-area";
import ExamplePanel from "./chat-example-panel"; import ExamplePanel from "./chat-example-panel";
import { UIMessage } from "ai"; import { UIMessage } from "ai";
import { convertToLegalXml, replaceNodes } from "@/lib/utils"; import { convertToLegalXml, replaceNodes } from "@/lib/utils";
import { Copy, Check, X } from "lucide-react";
import { useDiagram } from "@/contexts/diagram-context"; import { useDiagram } from "@/contexts/diagram-context";
const getMessageTextContent = (message: UIMessage): string => {
if (!message.parts) return "";
return message.parts
.filter((part: any) => part.type === "text")
.map((part: any) => part.text)
.join("\n");
};
interface ChatMessageDisplayProps { interface ChatMessageDisplayProps {
messages: UIMessage[]; messages: UIMessage[];
error?: Error | null; error?: Error | null;
@@ -30,6 +38,21 @@ export function ChatMessageDisplay({
const [expandedTools, setExpandedTools] = useState<Record<string, boolean>>( const [expandedTools, setExpandedTools] = useState<Record<string, boolean>>(
{} {}
); );
const [copiedMessageId, setCopiedMessageId] = useState<string | null>(null);
const [copyFailedMessageId, setCopyFailedMessageId] = useState<string | null>(null);
const copyMessageToClipboard = async (messageId: string, text: string) => {
try {
await navigator.clipboard.writeText(text);
setCopiedMessageId(messageId);
setTimeout(() => setCopiedMessageId(null), 2000);
} catch (err) {
console.error("Failed to copy message:", err);
setCopyFailedMessageId(messageId);
setTimeout(() => setCopyFailedMessageId(null), 2000);
}
};
const handleDisplayChart = useCallback( const handleDisplayChart = useCallback(
(xml: string) => { (xml: string) => {
const currentXml = xml || ""; const currentXml = xml || "";
@@ -137,16 +160,16 @@ export function ChatMessageDisplay({
{output || (toolName === "display_diagram" {output || (toolName === "display_diagram"
? "Diagram generated" ? "Diagram generated"
: toolName === "edit_diagram" : toolName === "edit_diagram"
? "Diagram edited" ? "Diagram edited"
: "Tool executed")} : "Tool executed")}
</div> </div>
) : state === "output-error" ? ( ) : state === "output-error" ? (
<div className="text-red-600"> <div className="text-red-600">
{output || (toolName === "display_diagram" {output || (toolName === "display_diagram"
? "Error generating diagram" ? "Error generating diagram"
: toolName === "edit_diagram" : toolName === "edit_diagram"
? "Error editing diagram" ? "Error editing diagram"
: "Tool error")} : "Tool error")}
</div> </div>
) : null} ) : null}
</div> </div>
@@ -160,51 +183,66 @@ export function ChatMessageDisplay({
{messages.length === 0 ? ( {messages.length === 0 ? (
<ExamplePanel setInput={setInput} setFiles={setFiles} /> <ExamplePanel setInput={setInput} setFiles={setFiles} />
) : ( ) : (
messages.map((message) => ( messages.map((message) => {
<div const userMessageText = message.role === "user" ? getMessageTextContent(message) : "";
key={message.id} return (
className={`mb-4 ${
message.role === "user" ? "text-right" : "text-left"
}`}
>
<div <div
className={`inline-block px-4 py-2 whitespace-pre-wrap text-sm rounded-lg max-w-[85%] break-words ${ key={message.id}
message.role === "user" className={`mb-4 flex ${message.role === "user" ? "justify-end" : "justify-start"}`}
? "bg-primary text-primary-foreground"
: "bg-muted text-muted-foreground"
}`}
> >
{message.parts?.map((part: any, index: number) => { {message.role === "user" && userMessageText && (
switch (part.type) { <button
case "text": onClick={() => copyMessageToClipboard(message.id, userMessageText)}
return ( className="p-1 text-gray-400 hover:text-gray-600 transition-colors self-center mr-1"
<div key={index}>{part.text}</div> title={copiedMessageId === message.id ? "Copied!" : copyFailedMessageId === message.id ? "Failed to copy" : "Copy message"}
); >
case "file": {copiedMessageId === message.id ? (
return ( <Check className="h-3.5 w-3.5 text-green-500" />
<div key={index} className="mt-2"> ) : copyFailedMessageId === message.id ? (
<Image <X className="h-3.5 w-3.5 text-red-500" />
src={part.url} ) : (
width={200} <Copy className="h-3.5 w-3.5" />
height={200} )}
alt={`Uploaded diagram or image for AI analysis`} </button>
className="rounded-md border" )}
style={{ <div
objectFit: "contain", className={`px-4 py-2 whitespace-pre-wrap text-sm rounded-lg max-w-[85%] break-words ${message.role === "user"
}} ? "bg-primary text-primary-foreground"
/> : "bg-muted text-muted-foreground"
</div> }`}
); >
default: {message.parts?.map((part: any, index: number) => {
if (part.type?.startsWith("tool-")) { switch (part.type) {
return renderToolPart(part); case "text":
} return (
return null; <div key={index}>{part.text}</div>
} );
})} case "file":
return (
<div key={index} className="mt-2">
<Image
src={part.url}
width={200}
height={200}
alt={`Uploaded diagram or image for AI analysis`}
className="rounded-md border"
style={{
objectFit: "contain",
}}
/>
</div>
);
default:
if (part.type?.startsWith("tool-")) {
return renderToolPart(part);
}
return null;
}
})}
</div>
</div> </div>
</div> );
)) })
)} )}
{error && ( {error && (
<div className="text-red-500 text-sm mt-2"> <div className="text-red-500 text-sm mt-2">

View File

@@ -14,6 +14,7 @@ AI_MODEL=global.anthropic.claude-sonnet-4-5-20250929-v1:0
# OpenAI Configuration # OpenAI Configuration
# OPENAI_API_KEY=sk-... # OPENAI_API_KEY=sk-...
# OPENAI_BASE_URL=https://api.openai.com/v1 # Optional: Custom OpenAI-compatible endpoint
# OPENAI_ORGANIZATION=org-... # Optional # OPENAI_ORGANIZATION=org-... # Optional
# OPENAI_PROJECT=proj_... # Optional # OPENAI_PROJECT=proj_... # Optional

View File

@@ -1,5 +1,5 @@
import { bedrock } from '@ai-sdk/amazon-bedrock'; import { bedrock } from '@ai-sdk/amazon-bedrock';
import { openai } from '@ai-sdk/openai'; import { openai, createOpenAI } from '@ai-sdk/openai';
import { anthropic } from '@ai-sdk/anthropic'; import { anthropic } from '@ai-sdk/anthropic';
import { google } from '@ai-sdk/google'; import { google } from '@ai-sdk/google';
import { azure } from '@ai-sdk/azure'; import { azure } from '@ai-sdk/azure';
@@ -18,15 +18,19 @@ export type ProviderName =
interface ModelConfig { interface ModelConfig {
model: any; model: any;
providerOptions?: any; providerOptions?: any;
headers?: Record<string, string>;
} }
// Anthropic beta headers for fine-grained tool streaming // Bedrock provider options for Anthropic beta features
const ANTHROPIC_BETA_OPTIONS = { const BEDROCK_ANTHROPIC_BETA = {
anthropic: { bedrock: {
additionalModelRequestFields: { anthropicBeta: ['fine-grained-tool-streaming-2025-05-14'],
anthropic_beta: ['fine-grained-tool-streaming-2025-05-14'] },
} };
}
// Direct Anthropic API headers for beta features
const ANTHROPIC_BETA_HEADERS = {
'anthropic-beta': 'fine-grained-tool-streaming-2025-05-14',
}; };
/** /**
@@ -61,6 +65,7 @@ function validateProviderCredentials(provider: ProviderName): void {
* *
* Provider-specific env vars: * Provider-specific env vars:
* - OPENAI_API_KEY: OpenAI API key * - OPENAI_API_KEY: OpenAI API key
* - OPENAI_BASE_URL: Custom OpenAI-compatible endpoint (optional)
* - ANTHROPIC_API_KEY: Anthropic API key * - ANTHROPIC_API_KEY: Anthropic API key
* - GOOGLE_GENERATIVE_AI_API_KEY: Google API key * - GOOGLE_GENERATIVE_AI_API_KEY: Google API key
* - AZURE_RESOURCE_NAME, AZURE_API_KEY: Azure OpenAI credentials * - AZURE_RESOURCE_NAME, AZURE_API_KEY: Azure OpenAI credentials
@@ -86,24 +91,33 @@ export function getAIModel(): ModelConfig {
let model: any; let model: any;
let providerOptions: any = undefined; let providerOptions: any = undefined;
let headers: Record<string, string> | undefined = undefined;
switch (provider) { switch (provider) {
case 'bedrock': case 'bedrock':
model = bedrock(modelId); model = bedrock(modelId);
// Add Anthropic beta headers if using Claude models via Bedrock // Add Anthropic beta options if using Claude models via Bedrock
if (modelId.includes('anthropic.claude')) { if (modelId.includes('anthropic.claude')) {
providerOptions = ANTHROPIC_BETA_OPTIONS; providerOptions = BEDROCK_ANTHROPIC_BETA;
} }
break; break;
case 'openai': case 'openai':
model = openai(modelId); if (process.env.OPENAI_BASE_URL) {
const customOpenAI = createOpenAI({
apiKey: process.env.OPENAI_API_KEY,
baseURL: process.env.OPENAI_BASE_URL,
});
model = customOpenAI.chat(modelId);
} else {
model = openai(modelId);
}
break; break;
case 'anthropic': case 'anthropic':
model = anthropic(modelId); model = anthropic(modelId);
// Add beta headers for fine-grained tool streaming // Add beta headers for fine-grained tool streaming
providerOptions = ANTHROPIC_BETA_OPTIONS; headers = ANTHROPIC_BETA_HEADERS;
break; break;
case 'google': case 'google':
@@ -131,10 +145,10 @@ export function getAIModel(): ModelConfig {
); );
} }
// Log if provider options are being applied // Log if provider options or headers are being applied
if (providerOptions) { if (providerOptions || headers) {
console.log('[AI Provider] Applying provider-specific options'); console.log('[AI Provider] Applying provider-specific options/headers');
} }
return { model, providerOptions }; return { model, providerOptions, headers };
} }

4753
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"@ai-sdk/amazon-bedrock": "^3.0.52", "@ai-sdk/amazon-bedrock": "^3.0.62",
"@ai-sdk/anthropic": "^2.0.44", "@ai-sdk/anthropic": "^2.0.44",
"@ai-sdk/azure": "^2.0.69", "@ai-sdk/azure": "^2.0.69",
"@ai-sdk/google": "^2.0.0", "@ai-sdk/google": "^2.0.0",
@@ -46,6 +46,8 @@
"@types/pako": "^2.0.3", "@types/pako": "^2.0.3",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"eslint": "9.39.1",
"eslint-config-next": "16.0.5",
"tailwindcss": "^4", "tailwindcss": "^4",
"typescript": "^5" "typescript": "^5"
} }