mirror of
https://github.com/openshiporg/openship.git
synced 2026-06-19 07:35:55 +00:00
refactor(chat): migrate to new AI SDK packages
- Add @ai-sdk/mcp and @ai-sdk/react dependencies - Switch from @ai-sdk/openai to @openrouter/ai-sdk-provider - Use useChat hook instead of custom useChatSubmission - Update completion API with convertToModelMessages and stepCountIs - Simplify stream handling using toUIMessageStreamResponse - Improve cookie handling in CookieAwareTransport with getSetCookie support
This commit is contained in:
+79
-134
@@ -1,5 +1,6 @@
|
||||
import { streamText, experimental_createMCPClient } from 'ai';
|
||||
import { createOpenAI } from '@ai-sdk/openai';
|
||||
import { convertToModelMessages, streamText, stepCountIs } from 'ai';
|
||||
import { createMCPClient } from '@ai-sdk/mcp';
|
||||
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
|
||||
import { getBaseUrl } from '@/features/dashboard/lib/getBaseUrl';
|
||||
import { StreamableHTTPClientTransport, StreamableHTTPClientTransportOptions } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
||||
|
||||
@@ -10,137 +11,107 @@ class CookieAwareTransport extends StreamableHTTPClientTransport {
|
||||
|
||||
constructor(url: URL, opts?: StreamableHTTPClientTransportOptions, cookies?: string) {
|
||||
super(url, opts);
|
||||
|
||||
|
||||
this.originalFetch = global.fetch;
|
||||
|
||||
// Set initial cookies if provided
|
||||
|
||||
if (cookies) {
|
||||
this.cookies = [cookies];
|
||||
}
|
||||
|
||||
// Override global fetch to include cookies
|
||||
|
||||
global.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
||||
init = init || {};
|
||||
const headers = new Headers(init.headers);
|
||||
|
||||
|
||||
if (this.cookies.length > 0) {
|
||||
headers.set('Cookie', this.cookies.join('; '));
|
||||
}
|
||||
|
||||
|
||||
init.headers = headers;
|
||||
|
||||
|
||||
const response = await this.originalFetch(input, init);
|
||||
|
||||
// Store any new cookies from response
|
||||
const setCookieHeader = response.headers.get('set-cookie');
|
||||
if (setCookieHeader) {
|
||||
const newCookies = setCookieHeader.split(',').map(cookie => cookie.trim());
|
||||
this.cookies = [...this.cookies, ...newCookies];
|
||||
|
||||
if (typeof response.headers.getSetCookie === 'function') {
|
||||
const setCookies = response.headers.getSetCookie();
|
||||
if (setCookies.length > 0) {
|
||||
this.cookies = [...this.cookies, ...setCookies];
|
||||
}
|
||||
} else {
|
||||
const setCookieHeader = response.headers.get('set-cookie');
|
||||
if (setCookieHeader) {
|
||||
this.cookies = [...this.cookies, setCookieHeader];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return response;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
async close(): Promise<void> {
|
||||
// Restore original fetch
|
||||
global.fetch = this.originalFetch;
|
||||
this.cookies = [];
|
||||
await super.close();
|
||||
}
|
||||
}
|
||||
|
||||
// OpenRouter configuration - will be set from request body
|
||||
|
||||
export async function POST(req: Request) {
|
||||
let mcpClient: any = null;
|
||||
let dataHasChanged = false;
|
||||
|
||||
|
||||
try {
|
||||
const body = await req.json();
|
||||
let messages = body.messages || [];
|
||||
const prompt = body.prompt || body.messages?.[body.messages.length - 1]?.content || '';
|
||||
|
||||
// Trim messages if conversation is too long (keep system context by preserving recent messages)
|
||||
const MAX_MESSAGES = 20; // Keep last 20 messages for context
|
||||
|
||||
const MAX_MESSAGES = 20;
|
||||
if (messages.length > MAX_MESSAGES) {
|
||||
messages = messages.slice(-MAX_MESSAGES);
|
||||
}
|
||||
|
||||
|
||||
// Require API key to be provided in request
|
||||
|
||||
if (!body.useLocalKeys || !body.apiKey) {
|
||||
return new Response(JSON.stringify({
|
||||
return new Response(JSON.stringify({
|
||||
error: 'API key is required',
|
||||
details: 'API key must be provided in request body'
|
||||
}), {
|
||||
}), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
const openrouterConfig = {
|
||||
apiKey: body.apiKey,
|
||||
baseURL: 'https://openrouter.ai/api/v1',
|
||||
};
|
||||
const apiKey = body.apiKey;
|
||||
const baseURL = 'https://openrouter.ai/api/v1';
|
||||
|
||||
// Get dynamic base URL
|
||||
const baseUrl = await getBaseUrl();
|
||||
const mcpEndpoint = `${baseUrl}/api/mcp-transport/http`;
|
||||
|
||||
const cookie = req.headers.get('cookie') || '';
|
||||
|
||||
// Create MCP client
|
||||
const transport = new CookieAwareTransport(
|
||||
new URL(mcpEndpoint),
|
||||
{},
|
||||
cookie
|
||||
);
|
||||
|
||||
mcpClient = await experimental_createMCPClient({
|
||||
transport,
|
||||
});
|
||||
|
||||
const aiTools = await mcpClient.tools();
|
||||
|
||||
// Create OpenRouter client with current configuration
|
||||
const openrouter = createOpenAI(openrouterConfig);
|
||||
|
||||
// Require model to be provided in request
|
||||
if (!body.model) {
|
||||
return new Response(JSON.stringify({
|
||||
return new Response(JSON.stringify({
|
||||
error: 'Model is required',
|
||||
details: 'Model must be provided in request body'
|
||||
}), {
|
||||
}), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const model = body.model;
|
||||
const maxTokens = body.maxTokens ? parseInt(body.maxTokens) : undefined;
|
||||
|
||||
// Debug logging
|
||||
|
||||
console.log('Starting completion request:', {
|
||||
model,
|
||||
maxTokens,
|
||||
hasApiKey: !!openrouterConfig.apiKey,
|
||||
apiKeyPrefix: openrouterConfig.apiKey?.substring(0, 10) + '...'
|
||||
hasApiKey: !!apiKey,
|
||||
apiKeyPrefix: apiKey?.substring(0, 10) + '...'
|
||||
});
|
||||
|
||||
// Test the API key with a simple request first to catch auth errors early
|
||||
try {
|
||||
const testResponse = await fetch('https://openrouter.ai/api/v1/models', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${openrouterConfig.apiKey}`,
|
||||
'Authorization': `Bearer ${apiKey}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
if (!testResponse.ok) {
|
||||
const errorText = await testResponse.text();
|
||||
console.log('API key validation failed:', errorText);
|
||||
|
||||
|
||||
let errorMessage = 'Invalid API key';
|
||||
try {
|
||||
const errorJson = JSON.parse(errorText);
|
||||
@@ -148,26 +119,34 @@ export async function POST(req: Request) {
|
||||
} catch {
|
||||
// Failed to parse error, use default message
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
error: 'Authentication Error',
|
||||
details: errorMessage
|
||||
}), {
|
||||
}), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
} catch (validationError) {
|
||||
console.error('API key validation error:', validationError);
|
||||
return new Response(JSON.stringify({
|
||||
return new Response(JSON.stringify({
|
||||
error: 'Authentication Error',
|
||||
details: 'Failed to validate API key'
|
||||
}), {
|
||||
}), {
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const baseUrl = await getBaseUrl();
|
||||
const mcpEndpoint = `${baseUrl}/api/mcp-transport/http`;
|
||||
const cookie = req.headers.get('cookie') || '';
|
||||
|
||||
const transport = new CookieAwareTransport(new URL(mcpEndpoint), {}, cookie);
|
||||
mcpClient = await createMCPClient({ transport });
|
||||
const aiTools = await mcpClient.tools();
|
||||
|
||||
const systemInstructions = `You're an expert at converting natural language to GraphQL queries for our KeystoneJS API.
|
||||
|
||||
YOUR EXPERTISE:
|
||||
@@ -247,14 +226,19 @@ EXAMPLES:
|
||||
|
||||
Always complete the full workflow and return actual data, not just schema discovery. The system works with any model type dynamically.`;
|
||||
|
||||
const openrouter = createOpenRouter({ apiKey, baseURL });
|
||||
|
||||
const inputMessages = messages.length > 0
|
||||
? messages
|
||||
: [{ id: `prompt-${Date.now()}`, role: 'user', parts: [{ type: 'text', text: prompt }] }];
|
||||
|
||||
const streamTextConfig: any = {
|
||||
model: openrouter(model),
|
||||
tools: aiTools,
|
||||
messages: messages.length > 0 ? messages : [{ role: 'user', content: prompt }],
|
||||
messages: await convertToModelMessages(inputMessages),
|
||||
system: systemInstructions,
|
||||
maxSteps: 10,
|
||||
onStepFinish: async (step: { toolCalls?: any[]; toolResults?: any[]; finishReason?: string; usage?: any; text?: string; }) => {
|
||||
// Track if any CRUD operations were called
|
||||
stopWhen: stepCountIs(10),
|
||||
onStepFinish: async (step: { toolCalls?: any[] }) => {
|
||||
if (step.toolCalls && step.toolCalls.length > 0) {
|
||||
for (const toolCall of step.toolCalls) {
|
||||
if (['createData', 'updateData', 'deleteData'].includes(toolCall.toolName)) {
|
||||
@@ -265,13 +249,8 @@ Always complete the full workflow and return actual data, not just schema discov
|
||||
}
|
||||
}
|
||||
},
|
||||
onFinish: async (result: { text: string; finishReason: string; usage: any; response: any }) => {
|
||||
console.log('Completion finished successfully');
|
||||
// Send data change notification through the stream
|
||||
if (dataHasChanged) {
|
||||
console.log('Sending data change notification');
|
||||
// We'll append this as a special message at the end
|
||||
}
|
||||
onFinish: async () => {
|
||||
console.log('Completion finished successfully', { dataHasChanged });
|
||||
await mcpClient.close();
|
||||
},
|
||||
onError: async (error: unknown) => {
|
||||
@@ -279,70 +258,36 @@ Always complete the full workflow and return actual data, not just schema discov
|
||||
await mcpClient.close();
|
||||
},
|
||||
};
|
||||
|
||||
// Add maxTokens only if specified
|
||||
|
||||
if (maxTokens) {
|
||||
streamTextConfig.maxTokens = maxTokens;
|
||||
streamTextConfig.maxOutputTokens = maxTokens;
|
||||
}
|
||||
|
||||
const response = streamText(streamTextConfig);
|
||||
|
||||
// Create a custom stream that includes our data change notification
|
||||
const stream = response.toDataStream();
|
||||
const reader = stream.getReader();
|
||||
|
||||
return new Response(
|
||||
new ReadableStream({
|
||||
async start(controller) {
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
|
||||
if (done) {
|
||||
// Before ending the stream, send data change notification if needed
|
||||
if (dataHasChanged) {
|
||||
console.log('Sending data change notification through stream');
|
||||
const dataChangeMessage = `9:{"dataHasChanged":true}\n`;
|
||||
controller.enqueue(new TextEncoder().encode(dataChangeMessage));
|
||||
}
|
||||
controller.close();
|
||||
break;
|
||||
}
|
||||
|
||||
controller.enqueue(value);
|
||||
}
|
||||
} catch (error) {
|
||||
controller.error(error);
|
||||
}
|
||||
}
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const result = streamText(streamTextConfig);
|
||||
|
||||
return result.toUIMessageStreamResponse({
|
||||
originalMessages: inputMessages,
|
||||
onError: (error) => error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
} catch (error) {
|
||||
// Clean up MCP client if it was created
|
||||
if (mcpClient) {
|
||||
try {
|
||||
await mcpClient.close();
|
||||
} catch (closeError) {}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// Log the full error for debugging
|
||||
|
||||
console.error('Completion API Error:', {
|
||||
message: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
details: error
|
||||
});
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
error: 'Internal Server Error',
|
||||
details: error instanceof Error ? error.message : String(error)
|
||||
}), {
|
||||
}), {
|
||||
status: 500,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useChat, type UIMessage } from "@ai-sdk/react";
|
||||
import { DefaultChatTransport } from "ai";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkBreaks from "remark-breaks";
|
||||
@@ -10,36 +13,17 @@ import {
|
||||
ChatContainerScrollAnchor,
|
||||
} from "./chat-container";
|
||||
import { ScrollButton } from "./scroll-button";
|
||||
import {
|
||||
ArrowUp,
|
||||
X,
|
||||
} from "lucide-react";
|
||||
import { ArrowUp, X } from "lucide-react";
|
||||
|
||||
// UI Components
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ModeSplitButton } from "./mode-split-button";
|
||||
import { useSidebarWithSide } from "@/components/ui/sidebar";
|
||||
import { useChatMode } from "../DashboardLayout";
|
||||
import { useAiConfig } from "../../hooks/use-ai-config";
|
||||
import { useChatSubmission } from "../../hooks/use-chat-submission";
|
||||
import { ChatEmptyState } from "./chat-empty-state";
|
||||
import { getSharedKeys } from "../../actions/ai-chat";
|
||||
|
||||
|
||||
// Chat mode types
|
||||
type ChatMode = "sidebar" | "chatbox";
|
||||
|
||||
// Types
|
||||
interface Message {
|
||||
id: string;
|
||||
content: string;
|
||||
isUser: boolean;
|
||||
timestamp: Date;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Compact Chat Message for Sidebar
|
||||
function ChatMessage({
|
||||
isUser,
|
||||
children,
|
||||
@@ -48,22 +32,7 @@ function ChatMessage({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={`text-base flex items-center gap-2 ${
|
||||
isUser ? "justify-end" : ""
|
||||
}`}
|
||||
>
|
||||
{/* {isUser ? (
|
||||
<div className="w-7 h-7 rounded-full bg-gradient-to-br from-rose-500 to-indigo-600 shadow-sm order-1 flex-shrink-0" />
|
||||
) : (
|
||||
<img
|
||||
className="rounded-full border border-black/[0.08] shadow-sm flex-shrink-0"
|
||||
src="https://raw.githubusercontent.com/origin-space/origin-images/refs/heads/main/exp2/user-01_i5l7tp.png"
|
||||
alt="AI"
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
)} */}
|
||||
<div className={`text-base flex items-center gap-2 ${isUser ? "justify-end" : ""}`}>
|
||||
<div
|
||||
className={cn(
|
||||
"max-w-[calc(100%-2rem)] break-words overflow-hidden",
|
||||
@@ -78,27 +47,103 @@ function ChatMessage({
|
||||
);
|
||||
}
|
||||
|
||||
// This component is no longer used - replaced with ChatUnactivatedState
|
||||
const CRUD_TOOLS = new Set(["createData", "updateData", "deleteData"]);
|
||||
|
||||
function getMessageText(message: UIMessage) {
|
||||
return message.parts
|
||||
?.filter((part: any) => part.type === "text")
|
||||
.map((part: any) => part.text)
|
||||
.join("")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function responseHasCrudTool(messages: UIMessage[]) {
|
||||
return messages.some((message: any) =>
|
||||
message.role === "assistant" &&
|
||||
message.parts?.some((part: any) => {
|
||||
if (part.type === "dynamic-tool") {
|
||||
return CRUD_TOOLS.has(part.toolName);
|
||||
}
|
||||
if (typeof part.type === "string" && part.type.startsWith("tool-")) {
|
||||
return CRUD_TOOLS.has(part.type.slice(5));
|
||||
}
|
||||
if (part.type === "tool-invocation" && part.toolInvocation?.toolName) {
|
||||
return CRUD_TOOLS.has(part.toolInvocation.toolName);
|
||||
}
|
||||
return false;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Main Sidebar Chat Component
|
||||
export function AiChatSidebar() {
|
||||
const { toggleSidebar } = useSidebarWithSide("right");
|
||||
const { messages, setMessages, loading, setLoading, sending, setSending, user } = useChatMode();
|
||||
const { user } = useChatMode();
|
||||
const { config: aiConfig } = useAiConfig();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const [input, setInput] = useState("");
|
||||
const { config: aiConfig, setConfig: setAiConfig } = useAiConfig();
|
||||
const { handleSubmit: submitChat } = useChatSubmission({
|
||||
messages,
|
||||
setMessages,
|
||||
setLoading,
|
||||
setSending,
|
||||
|
||||
const { messages, sendMessage, status, error } = useChat({
|
||||
transport: new DefaultChatTransport({
|
||||
api: "/api/completion",
|
||||
credentials: "include",
|
||||
}),
|
||||
onFinish: ({ messages }) => {
|
||||
if (responseHasCrudTool(messages as UIMessage[])) {
|
||||
queryClient.invalidateQueries();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const isLoading = status === "submitted" || status === "streaming";
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!input.trim()) return;
|
||||
const currentInput = input;
|
||||
const text = input.trim();
|
||||
if (!text || isLoading) return;
|
||||
|
||||
setInput("");
|
||||
await submitChat(currentInput);
|
||||
|
||||
try {
|
||||
if (aiConfig.keyMode === "local") {
|
||||
if (!aiConfig.localKeys?.apiKey || !aiConfig.localKeys?.model) {
|
||||
throw new Error("Local API key and model are required. Please configure them in settings.");
|
||||
}
|
||||
|
||||
await sendMessage(
|
||||
{ text },
|
||||
{
|
||||
body: {
|
||||
useLocalKeys: true,
|
||||
apiKey: aiConfig.localKeys.apiKey,
|
||||
model: aiConfig.localKeys.model,
|
||||
maxTokens: parseInt(aiConfig.localKeys.maxTokens || "4000", 10),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const keysResult = await getSharedKeys();
|
||||
if (!keysResult.success || !keysResult.keys) {
|
||||
throw new Error(keysResult.error || "Shared API keys are not configured.");
|
||||
}
|
||||
|
||||
await sendMessage(
|
||||
{ text },
|
||||
{
|
||||
body: {
|
||||
useLocalKeys: true,
|
||||
apiKey: keysResult.keys.apiKey,
|
||||
model: keysResult.keys.model,
|
||||
maxTokens: keysResult.keys.maxTokens,
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (submitError) {
|
||||
setInput(text);
|
||||
console.error("Chat submit error:", submitError);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
@@ -108,131 +153,100 @@ export function AiChatSidebar() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
{/* Header */}
|
||||
<div className="flex h-16 shrink-0 items-center justify-between px-4 border-b">
|
||||
<h3 className="font-medium text-muted-foreground">AI Assistant</h3>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={toggleSidebar}
|
||||
className="h-8 w-8"
|
||||
>
|
||||
<Button variant="ghost" size="icon" onClick={toggleSidebar} className="h-8 w-8">
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Messages */}
|
||||
<ChatContainerRoot className="flex-1 pt-3 px-3 relative">
|
||||
<ChatContainerContent className="space-y-3">
|
||||
{messages.length === 0 ? (
|
||||
<ChatEmptyState userName={user?.name} />
|
||||
) : (
|
||||
messages.map((message) => (
|
||||
<ChatMessage key={message.id} isUser={message.isUser}>
|
||||
{message.isUser ? (
|
||||
<p className="whitespace-pre-wrap break-words">
|
||||
{message.content}
|
||||
</p>
|
||||
<ChatContainerContent className="space-y-3">
|
||||
{messages.length === 0 ? (
|
||||
<ChatEmptyState userName={user?.name} />
|
||||
) : (
|
||||
messages.map((message) => {
|
||||
const text = getMessageText(message as UIMessage);
|
||||
const isUser = message.role === "user";
|
||||
|
||||
return (
|
||||
<ChatMessage key={message.id} isUser={isUser}>
|
||||
{isUser ? (
|
||||
<p className="whitespace-pre-wrap break-words">{text}</p>
|
||||
) : text ? (
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm, remarkBreaks]}
|
||||
components={{
|
||||
p: ({ children }) => <div className="mb-1 last:mb-0 break-words">{children}</div>,
|
||||
ul: ({ children }) => <ul className="mb-1 last:mb-0 pl-2">{children}</ul>,
|
||||
ol: ({ children }) => <ol className="mb-1 last:mb-0 pl-2">{children}</ol>,
|
||||
li: ({ children }) => <li className="mb-0.5">{children}</li>,
|
||||
strong: ({ children }) => <strong className="font-semibold">{children}</strong>,
|
||||
code: ({ children, ...props }) =>
|
||||
(props as any).inline ? (
|
||||
<code className="bg-muted px-1 rounded font-mono break-all">{children}</code>
|
||||
) : (
|
||||
<pre className="bg-muted border rounded p-2 overflow-x-auto">
|
||||
<code className="font-mono break-all">{children}</code>
|
||||
</pre>
|
||||
),
|
||||
pre: ({ children }) => <div className="mb-1 last:mb-0">{children}</div>,
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</ReactMarkdown>
|
||||
) : (
|
||||
<>
|
||||
{message.content ? (
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm, remarkBreaks]}
|
||||
components={{
|
||||
p: ({ children }) => (
|
||||
<div className="mb-1 last:mb-0 break-words">
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
ul: ({ children }) => (
|
||||
<ul className="mb-1 last:mb-0 pl-2">{children}</ul>
|
||||
),
|
||||
ol: ({ children }) => (
|
||||
<ol className="mb-1 last:mb-0 pl-2">{children}</ol>
|
||||
),
|
||||
li: ({ children }) => (
|
||||
<li className="mb-0.5">{children}</li>
|
||||
),
|
||||
strong: ({ children }) => (
|
||||
<strong className="font-semibold">{children}</strong>
|
||||
),
|
||||
code: ({ children, ...props }) => {
|
||||
if ((props as any).inline) {
|
||||
return (
|
||||
<code className="bg-muted px-1 rounded font-mono break-all">
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<pre className="bg-muted border rounded p-2 overflow-x-auto">
|
||||
<code className="font-mono break-all">
|
||||
{children}
|
||||
</code>
|
||||
</pre>
|
||||
);
|
||||
},
|
||||
pre: ({ children }) => (
|
||||
<div className="mb-1 last:mb-0">{children}</div>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{message.content}
|
||||
</ReactMarkdown>
|
||||
) : (
|
||||
<div className="flex items-center gap-1 text-muted-foreground">
|
||||
<span className="animate-pulse">Thinking...</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
<div className="flex items-center gap-1 text-muted-foreground">
|
||||
<span className="animate-pulse">Thinking...</span>
|
||||
</div>
|
||||
)}
|
||||
</ChatMessage>
|
||||
))
|
||||
)}
|
||||
|
||||
<ChatContainerScrollAnchor />
|
||||
</ChatContainerContent>
|
||||
|
||||
{/* PromptKit Scroll Button */}
|
||||
{messages.length > 0 && (
|
||||
<div className="absolute bottom-4 right-4">
|
||||
<ScrollButton />
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</ChatContainerRoot>
|
||||
|
||||
{/* Input Area */}
|
||||
<div className="shadow bg-background border border-transparent ring-1 ring-foreground/10 mx-3 mb-3 space-y-3 rounded-lg p-3">
|
||||
<textarea
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Ask me anything..."
|
||||
className="w-full text-base bg-transparent border-0 resize-none focus:outline-none placeholder:text-muted-foreground min-h-[40px] break-words"
|
||||
disabled={sending || loading}
|
||||
rows={1}
|
||||
/>
|
||||
{error && (
|
||||
<div className="text-sm text-destructive px-1">Error: {error.message}</div>
|
||||
)}
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="flex gap-2">
|
||||
<ModeSplitButton
|
||||
disabled={sending || loading}
|
||||
/>
|
||||
</div>
|
||||
<ChatContainerScrollAnchor />
|
||||
</ChatContainerContent>
|
||||
|
||||
<Button
|
||||
size="icon"
|
||||
className="size-8 rounded-2xl bg-foreground text-background hover:bg-foreground/90"
|
||||
onClick={handleSubmit}
|
||||
disabled={sending || loading || !input.trim()}
|
||||
>
|
||||
<ArrowUp strokeWidth={3} />
|
||||
</Button>
|
||||
{messages.length > 0 && (
|
||||
<div className="absolute bottom-4 right-4">
|
||||
<ScrollButton />
|
||||
</div>
|
||||
)}
|
||||
</ChatContainerRoot>
|
||||
|
||||
<div className="shadow bg-background border border-transparent ring-1 ring-foreground/10 mx-3 mb-3 space-y-3 rounded-lg p-3">
|
||||
<textarea
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder="Ask me anything..."
|
||||
className="w-full text-base bg-transparent border-0 resize-none focus:outline-none placeholder:text-muted-foreground min-h-[40px] break-words"
|
||||
disabled={isLoading}
|
||||
rows={1}
|
||||
/>
|
||||
|
||||
<div className="flex justify-between">
|
||||
<div className="flex gap-2">
|
||||
<ModeSplitButton disabled={isLoading} />
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="icon"
|
||||
className="size-8 rounded-2xl bg-foreground text-background hover:bg-foreground/90"
|
||||
onClick={handleSubmit}
|
||||
disabled={isLoading || !input.trim()}
|
||||
>
|
||||
<ArrowUp strokeWidth={3} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,10 +36,11 @@ export function AIModelSelector({ disabled = false }: AIModelSelectorProps) {
|
||||
const { config, setConfig } = useAiConfig();
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
|
||||
const currentModel =
|
||||
config.keyMode === "local"
|
||||
? config.localKeys?.model || "openai/gpt-4o-mini"
|
||||
: process.env.NEXT_PUBLIC_OPENROUTER_MODEL || "openai/gpt-4o-mini";
|
||||
: null;
|
||||
|
||||
const handleModelChange = (modelSlug: string) => {
|
||||
if (config.keyMode === "local") {
|
||||
@@ -74,8 +75,13 @@ export function AIModelSelector({ disabled = false }: AIModelSelectorProps) {
|
||||
setIsDropdownOpen(false);
|
||||
};
|
||||
|
||||
const selectedModel = POPULAR_MODELS.find((m) => m.slug === currentModel);
|
||||
const displayText = selectedModel?.name || currentModel;
|
||||
const selectedModel = currentModel
|
||||
? POPULAR_MODELS.find((m) => m.slug === currentModel)
|
||||
: null;
|
||||
const displayText =
|
||||
config.keyMode === "env"
|
||||
? "Custom"
|
||||
: selectedModel?.name || "Custom";
|
||||
|
||||
const selector = (
|
||||
<Popover open={isDropdownOpen} onOpenChange={setIsDropdownOpen}>
|
||||
|
||||
Generated
+222
-103
@@ -8,7 +8,8 @@
|
||||
"name": "openship",
|
||||
"version": "3.0.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai": "^1.3.23",
|
||||
"@ai-sdk/mcp": "^1.0.13",
|
||||
"@ai-sdk/react": "^3.0.51",
|
||||
"@graphql-tools/schema": "^10.0.23",
|
||||
"@hapi/iron": "^7.0.1",
|
||||
"@keystone-6/auth": "^8.1.0",
|
||||
@@ -16,6 +17,7 @@
|
||||
"@keystone-6/document-renderer": "^1.1.2",
|
||||
"@keystone-6/fields-document": "^9.1.1",
|
||||
"@modelcontextprotocol/sdk": "^1.17.1",
|
||||
"@openrouter/ai-sdk-provider": "^2.0.2",
|
||||
"@prisma/client": "6.5.0",
|
||||
"@radix-ui/react-accordion": "^1.2.11",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.14",
|
||||
@@ -51,7 +53,7 @@
|
||||
"@tanstack/react-table": "^8.21.2",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"@types/sortablejs": "^1.15.8",
|
||||
"ai": "^4.3.17",
|
||||
"ai": "^6.0.49",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -124,26 +126,27 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@ai-sdk/openai": {
|
||||
"version": "1.3.24",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-1.3.24.tgz",
|
||||
"integrity": "sha512-GYXnGJTHRTZc4gJMSmFRgEQudjqd4PUN0ZjQhPwOAYH1yOAvQoG/Ikqs+HyISRbLPCrhbZnPKCNHuRU4OfpW0Q==",
|
||||
"node_modules/@ai-sdk/gateway": {
|
||||
"version": "3.0.39",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.39.tgz",
|
||||
"integrity": "sha512-SeCZBAdDNbWpVUXiYgOAqis22p5MEYfrjRw0hiBa5hM+7sDGYQpMinUjkM8kbPXMkY+AhKLrHleBl+SuqpzlgA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "1.1.3",
|
||||
"@ai-sdk/provider-utils": "2.2.8"
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@ai-sdk/provider-utils": "4.0.14",
|
||||
"@vercel/oidc": "3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.0.0"
|
||||
"zod": "^3.25.76 || ^4.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/provider": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.3.tgz",
|
||||
"integrity": "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==",
|
||||
"node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz",
|
||||
"integrity": "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"json-schema": "^0.4.0"
|
||||
@@ -152,31 +155,77 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/provider-utils": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz",
|
||||
"integrity": "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==",
|
||||
"node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider-utils": {
|
||||
"version": "4.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.14.tgz",
|
||||
"integrity": "sha512-7bzKd9lgiDeXM7O4U4nQ8iTxguAOkg8LZGD9AfDVZYjO5cKYRwBPwVjboFcVrxncRHu0tYxZtXZtiLKpG4pEng==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "1.1.3",
|
||||
"nanoid": "^3.3.8",
|
||||
"secure-json-parse": "^2.7.0"
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
"eventsource-parser": "^3.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.23.8"
|
||||
"zod": "^3.25.76 || ^4.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/mcp": {
|
||||
"version": "1.0.19",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/mcp/-/mcp-1.0.19.tgz",
|
||||
"integrity": "sha512-RJ5I9IU0MNOFMJXTNh+pjGM8EeUNxT5FZHADoW/x2HhkMvZzpbx6ZKXvLzJ6/+uJBe50PHj4ZNxObeihH0JdoA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@ai-sdk/provider-utils": "4.0.14",
|
||||
"pkce-challenge": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.76 || ^4.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/mcp/node_modules/@ai-sdk/provider": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz",
|
||||
"integrity": "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"json-schema": "^0.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/mcp/node_modules/@ai-sdk/provider-utils": {
|
||||
"version": "4.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.14.tgz",
|
||||
"integrity": "sha512-7bzKd9lgiDeXM7O4U4nQ8iTxguAOkg8LZGD9AfDVZYjO5cKYRwBPwVjboFcVrxncRHu0tYxZtXZtiLKpG4pEng==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
"eventsource-parser": "^3.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.76 || ^4.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/react": {
|
||||
"version": "1.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.12.tgz",
|
||||
"integrity": "sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==",
|
||||
"version": "3.0.79",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-3.0.79.tgz",
|
||||
"integrity": "sha512-s/Y+/sISlsEX5Zo/by0jwOyA6vnQ7+CldpRYGv5hMmgnarZ1m5B6myw3Y1Bc2xnozUy+wrmwA6HttlmR4xOOEg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider-utils": "2.2.8",
|
||||
"@ai-sdk/ui-utils": "1.2.11",
|
||||
"@ai-sdk/provider-utils": "4.0.14",
|
||||
"ai": "6.0.77",
|
||||
"swr": "^2.2.5",
|
||||
"throttleit": "2.1.0"
|
||||
},
|
||||
@@ -184,30 +233,36 @@
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18 || ^19 || ^19.0.0-rc",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"zod": {
|
||||
"optional": true
|
||||
}
|
||||
"react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/ui-utils": {
|
||||
"version": "1.2.11",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.11.tgz",
|
||||
"integrity": "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==",
|
||||
"node_modules/@ai-sdk/react/node_modules/@ai-sdk/provider": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz",
|
||||
"integrity": "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "1.1.3",
|
||||
"@ai-sdk/provider-utils": "2.2.8",
|
||||
"zod-to-json-schema": "^3.24.1"
|
||||
"json-schema": "^0.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@ai-sdk/react/node_modules/@ai-sdk/provider-utils": {
|
||||
"version": "4.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.14.tgz",
|
||||
"integrity": "sha512-7bzKd9lgiDeXM7O4U4nQ8iTxguAOkg8LZGD9AfDVZYjO5cKYRwBPwVjboFcVrxncRHu0tYxZtXZtiLKpG4pEng==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
"eventsource-parser": "^3.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.23.8"
|
||||
"zod": "^3.25.76 || ^4.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@alloc/quick-lru": {
|
||||
@@ -4982,6 +5037,19 @@
|
||||
"node": ">=12.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@openrouter/ai-sdk-provider": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@openrouter/ai-sdk-provider/-/ai-sdk-provider-2.1.1.tgz",
|
||||
"integrity": "sha512-UypPbVnSExxmG/4Zg0usRiit3auvQVrjUXSyEhm0sZ9GQnW/d8p/bKgCk2neh1W5YyRSo7PNQvCrAEBHZnqQkQ==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ai": "^6.0.0",
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@opentelemetry/api": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
|
||||
@@ -7896,6 +7964,12 @@
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@standard-schema/spec": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
||||
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@swc/counter": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||
@@ -8134,6 +8208,66 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
|
||||
"version": "1.7.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.1.0",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||
"version": "1.7.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.1.0",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.1.0",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "^1.7.1",
|
||||
"@emnapi/runtime": "^1.7.1",
|
||||
"@tybys/wasm-util": "^0.10.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "0BSD",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||
"version": "4.1.18",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz",
|
||||
@@ -8513,12 +8647,6 @@
|
||||
"@types/ms": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/diff-match-patch": {
|
||||
"version": "1.0.36",
|
||||
"resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz",
|
||||
"integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
@@ -9359,6 +9487,15 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@vercel/oidc": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.1.0.tgz",
|
||||
"integrity": "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/@whatwg-node/events": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@whatwg-node/events/-/events-0.0.2.tgz",
|
||||
@@ -9524,29 +9661,51 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ai": {
|
||||
"version": "4.3.19",
|
||||
"resolved": "https://registry.npmjs.org/ai/-/ai-4.3.19.tgz",
|
||||
"integrity": "sha512-dIE2bfNpqHN3r6IINp9znguYdhIOheKW2LDigAMrgt/upT3B8eBGPSCblENvaZGoq+hxaN9fSMzjWpbqloP+7Q==",
|
||||
"version": "6.0.77",
|
||||
"resolved": "https://registry.npmjs.org/ai/-/ai-6.0.77.tgz",
|
||||
"integrity": "sha512-tyyhrRpCRFVlivdNIFLK8cexSBB2jwTqO0z1qJQagk+UxZ+MW8h5V8xsvvb+xdKDY482Y8KAm0mr7TDnPKvvlw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "1.1.3",
|
||||
"@ai-sdk/provider-utils": "2.2.8",
|
||||
"@ai-sdk/react": "1.2.12",
|
||||
"@ai-sdk/ui-utils": "1.2.11",
|
||||
"@opentelemetry/api": "1.9.0",
|
||||
"jsondiffpatch": "0.6.0"
|
||||
"@ai-sdk/gateway": "3.0.39",
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@ai-sdk/provider-utils": "4.0.14",
|
||||
"@opentelemetry/api": "1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18 || ^19 || ^19.0.0-rc",
|
||||
"zod": "^3.23.8"
|
||||
"zod": "^3.25.76 || ^4.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/ai/node_modules/@ai-sdk/provider": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.8.tgz",
|
||||
"integrity": "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"json-schema": "^0.4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react": {
|
||||
"optional": true
|
||||
}
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/ai/node_modules/@ai-sdk/provider-utils": {
|
||||
"version": "4.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.14.tgz",
|
||||
"integrity": "sha512-7bzKd9lgiDeXM7O4U4nQ8iTxguAOkg8LZGD9AfDVZYjO5cKYRwBPwVjboFcVrxncRHu0tYxZtXZtiLKpG4pEng==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
"eventsource-parser": "^3.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.76 || ^4.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
@@ -11186,12 +11345,6 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/diff-match-patch": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
|
||||
"integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/direction": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz",
|
||||
@@ -11841,6 +11994,7 @@
|
||||
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@rtsao/scc": "^1.1.0",
|
||||
"array-includes": "^3.1.9",
|
||||
@@ -14299,35 +14453,6 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/jsondiffpatch": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz",
|
||||
"integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/diff-match-patch": "^1.0.36",
|
||||
"chalk": "^5.3.0",
|
||||
"diff-match-patch": "^1.0.5"
|
||||
},
|
||||
"bin": {
|
||||
"jsondiffpatch": "bin/jsondiffpatch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jsondiffpatch/node_modules/chalk": {
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
|
||||
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/jsonfile": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
|
||||
@@ -19300,12 +19425,6 @@
|
||||
"compute-scroll-into-view": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/secure-json-parse": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
|
||||
"integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
||||
|
||||
+4
-2
@@ -11,7 +11,8 @@
|
||||
"migrate": "prisma migrate deploy"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/openai": "^1.3.23",
|
||||
"@ai-sdk/mcp": "^1.0.13",
|
||||
"@ai-sdk/react": "^3.0.51",
|
||||
"@graphql-tools/schema": "^10.0.23",
|
||||
"@hapi/iron": "^7.0.1",
|
||||
"@keystone-6/auth": "^8.1.0",
|
||||
@@ -19,6 +20,7 @@
|
||||
"@keystone-6/document-renderer": "^1.1.2",
|
||||
"@keystone-6/fields-document": "^9.1.1",
|
||||
"@modelcontextprotocol/sdk": "^1.17.1",
|
||||
"@openrouter/ai-sdk-provider": "^2.0.2",
|
||||
"@prisma/client": "6.5.0",
|
||||
"@radix-ui/react-accordion": "^1.2.11",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.14",
|
||||
@@ -54,7 +56,7 @@
|
||||
"@tanstack/react-table": "^8.21.2",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"@types/sortablejs": "^1.15.8",
|
||||
"ai": "^4.3.17",
|
||||
"ai": "^6.0.49",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
|
||||
Reference in New Issue
Block a user