mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-01-04 15:22:29 +08:00
Compare commits
15 Commits
feat/multi
...
chore/upgr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76d453b533 | ||
|
|
8cb7494d16 | ||
|
|
98625dd72a | ||
|
|
b5734aa5e1 | ||
|
|
87cdc53665 | ||
|
|
b4fc259de8 | ||
|
|
28f9a81e7b | ||
|
|
0f67884ead | ||
|
|
3521495ead | ||
|
|
6446454cd7 | ||
|
|
84959637db | ||
|
|
9e9ea10beb | ||
|
|
deae5c2c38 | ||
|
|
6e2d98e52d | ||
|
|
85cb441e26 |
2
.github/workflows/docker-build.yml
vendored
2
.github/workflows/docker-build.yml
vendored
@@ -63,6 +63,8 @@ jobs:
|
|||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
cache-to: type=gha,mode=max
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
build-args: |
|
||||||
|
NEXT_PUBLIC_SHOW_ABOUT_AND_NOTICE=true
|
||||||
|
|
||||||
# Push to AWS ECR for App Runner auto-deploy
|
# Push to AWS ECR for App Runner auto-deploy
|
||||||
- name: Configure AWS credentials
|
- name: Configure AWS credentials
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ ENV NEXT_TELEMETRY_DISABLED=1
|
|||||||
ARG NEXT_PUBLIC_DRAWIO_BASE_URL=https://embed.diagrams.net
|
ARG NEXT_PUBLIC_DRAWIO_BASE_URL=https://embed.diagrams.net
|
||||||
ENV NEXT_PUBLIC_DRAWIO_BASE_URL=${NEXT_PUBLIC_DRAWIO_BASE_URL}
|
ENV NEXT_PUBLIC_DRAWIO_BASE_URL=${NEXT_PUBLIC_DRAWIO_BASE_URL}
|
||||||
|
|
||||||
|
# Build-time argument to show About link and Notice icon
|
||||||
|
ARG NEXT_PUBLIC_SHOW_ABOUT_AND_NOTICE=false
|
||||||
|
ENV NEXT_PUBLIC_SHOW_ABOUT_AND_NOTICE=${NEXT_PUBLIC_SHOW_ABOUT_AND_NOTICE}
|
||||||
|
|
||||||
# Build Next.js application (standalone mode)
|
# Build Next.js application (standalone mode)
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
|||||||
@@ -117,9 +117,9 @@ export default function AboutCN() {
|
|||||||
(TPS/TPM)。一旦超限,系统就会暂停,导致请求失败。
|
(TPS/TPM)。一旦超限,系统就会暂停,导致请求失败。
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
由于使用量过高,我已将模型从 Claude 更换为{" "}
|
由于使用量过高,我已将模型从 Opus 4.5 更换为{" "}
|
||||||
<span className="font-semibold text-amber-700">
|
<span className="font-semibold text-amber-700">
|
||||||
minimax-m2
|
Haiku 4.5
|
||||||
</span>
|
</span>
|
||||||
,以降低成本。
|
,以降低成本。
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -126,9 +126,9 @@ export default function AboutJA() {
|
|||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
利用量の増加に伴い、コスト削減のためモデルを
|
利用量の増加に伴い、コスト削減のためモデルを
|
||||||
Claude から{" "}
|
Opus 4.5 から{" "}
|
||||||
<span className="font-semibold text-amber-700">
|
<span className="font-semibold text-amber-700">
|
||||||
minimax-m2
|
Haiku 4.5
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
に変更しました。
|
に変更しました。
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -129,9 +129,9 @@ export default function About() {
|
|||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Due to the high usage, I have changed the
|
Due to the high usage, I have changed the
|
||||||
model from Claude to{" "}
|
model from Opus 4.5 to{" "}
|
||||||
<span className="font-semibold text-amber-700">
|
<span className="font-semibold text-amber-700">
|
||||||
minimax-m2
|
Haiku 4.5
|
||||||
</span>
|
</span>
|
||||||
, which is more cost-effective.
|
, which is more cost-effective.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -248,7 +248,7 @@ ${userInputText}
|
|||||||
"""`
|
"""`
|
||||||
|
|
||||||
// Convert UIMessages to ModelMessages and add system message
|
// Convert UIMessages to ModelMessages and add system message
|
||||||
const modelMessages = convertToModelMessages(messages)
|
const modelMessages = await convertToModelMessages(messages)
|
||||||
|
|
||||||
// DEBUG: Log incoming messages structure
|
// DEBUG: Log incoming messages structure
|
||||||
console.log("[route.ts] Incoming messages count:", messages.length)
|
console.log("[route.ts] Incoming messages count:", messages.length)
|
||||||
@@ -686,7 +686,8 @@ Call this tool to get shape names and usage syntax for a specific library.`,
|
|||||||
// Total input = non-cached + cached (these are separate counts)
|
// Total input = non-cached + cached (these are separate counts)
|
||||||
// Note: cacheWriteInputTokens is not available on finish part
|
// Note: cacheWriteInputTokens is not available on finish part
|
||||||
const totalInputTokens =
|
const totalInputTokens =
|
||||||
(usage.inputTokens ?? 0) + (usage.cachedInputTokens ?? 0)
|
(usage.inputTokens ?? 0) +
|
||||||
|
(usage.inputTokenDetails?.cacheReadTokens ?? 0)
|
||||||
return {
|
return {
|
||||||
inputTokens: totalInputTokens,
|
inputTokens: totalInputTokens,
|
||||||
outputTokens: usage.outputTokens ?? 0,
|
outputTokens: usage.outputTokens ?? 0,
|
||||||
|
|||||||
@@ -11,6 +11,66 @@ import { createOllama } from "ollama-ai-provider-v2"
|
|||||||
|
|
||||||
export const runtime = "nodejs"
|
export const runtime = "nodejs"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SECURITY: Check if URL points to private/internal network (SSRF protection)
|
||||||
|
* Blocks: localhost, private IPs, link-local, AWS metadata service
|
||||||
|
*/
|
||||||
|
function isPrivateUrl(urlString: string): boolean {
|
||||||
|
try {
|
||||||
|
const url = new URL(urlString)
|
||||||
|
const hostname = url.hostname.toLowerCase()
|
||||||
|
|
||||||
|
// Block localhost
|
||||||
|
if (
|
||||||
|
hostname === "localhost" ||
|
||||||
|
hostname === "127.0.0.1" ||
|
||||||
|
hostname === "::1"
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block AWS/cloud metadata endpoints
|
||||||
|
if (
|
||||||
|
hostname === "169.254.169.254" ||
|
||||||
|
hostname === "metadata.google.internal"
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for private IPv4 ranges
|
||||||
|
const ipv4Match = hostname.match(
|
||||||
|
/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/,
|
||||||
|
)
|
||||||
|
if (ipv4Match) {
|
||||||
|
const [, a, b] = ipv4Match.map(Number)
|
||||||
|
// 10.0.0.0/8
|
||||||
|
if (a === 10) return true
|
||||||
|
// 172.16.0.0/12
|
||||||
|
if (a === 172 && b >= 16 && b <= 31) return true
|
||||||
|
// 192.168.0.0/16
|
||||||
|
if (a === 192 && b === 168) return true
|
||||||
|
// 169.254.0.0/16 (link-local)
|
||||||
|
if (a === 169 && b === 254) return true
|
||||||
|
// 127.0.0.0/8 (loopback)
|
||||||
|
if (a === 127) return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block common internal hostnames
|
||||||
|
if (
|
||||||
|
hostname.endsWith(".local") ||
|
||||||
|
hostname.endsWith(".internal") ||
|
||||||
|
hostname.endsWith(".localhost")
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
} catch {
|
||||||
|
// Invalid URL - block it
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface ValidateRequest {
|
interface ValidateRequest {
|
||||||
provider: string
|
provider: string
|
||||||
apiKey: string
|
apiKey: string
|
||||||
@@ -42,6 +102,14 @@ export async function POST(req: Request) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SECURITY: Block SSRF attacks via custom baseUrl
|
||||||
|
if (baseUrl && isPrivateUrl(baseUrl)) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ valid: false, error: "Invalid base URL" },
|
||||||
|
{ status: 400 },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Validate credentials based on provider
|
// Validate credentials based on provider
|
||||||
if (provider === "bedrock") {
|
if (provider === "bedrock") {
|
||||||
if (!awsAccessKeyId || !awsSecretAccessKey || !awsRegion) {
|
if (!awsAccessKeyId || !awsSecretAccessKey || !awsRegion) {
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
import type { MetadataRoute } from "next"
|
import type { MetadataRoute } from "next"
|
||||||
|
import { getAssetUrl } from "@/lib/base-path"
|
||||||
export default function manifest(): MetadataRoute.Manifest {
|
export default function manifest(): MetadataRoute.Manifest {
|
||||||
return {
|
return {
|
||||||
name: "Next AI Draw.io",
|
name: "Next AI Draw.io",
|
||||||
short_name: "AIDraw.io",
|
short_name: "AIDraw.io",
|
||||||
description:
|
description:
|
||||||
"Create AWS architecture diagrams, flowcharts, and technical diagrams using AI. Free online tool integrating draw.io with AI assistance for professional diagram creation.",
|
"Create AWS architecture diagrams, flowcharts, and technical diagrams using AI. Free online tool integrating draw.io with AI assistance for professional diagram creation.",
|
||||||
start_url: "/",
|
start_url: getAssetUrl("/"),
|
||||||
display: "standalone",
|
display: "standalone",
|
||||||
background_color: "#f9fafb",
|
background_color: "#f9fafb",
|
||||||
theme_color: "#171d26",
|
theme_color: "#171d26",
|
||||||
icons: [
|
icons: [
|
||||||
{
|
{
|
||||||
src: "/favicon-192x192.png",
|
src: getAssetUrl("/favicon-192x192.png"),
|
||||||
sizes: "192x192",
|
sizes: "192x192",
|
||||||
type: "image/png",
|
type: "image/png",
|
||||||
purpose: "any",
|
purpose: "any",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: "/favicon-512x512.png",
|
src: getAssetUrl("/favicon-512x512.png"),
|
||||||
sizes: "512x512",
|
sizes: "512x512",
|
||||||
type: "image/png",
|
type: "image/png",
|
||||||
purpose: "any",
|
purpose: "any",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
Zap,
|
Zap,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { useDictionary } from "@/hooks/use-dictionary"
|
import { useDictionary } from "@/hooks/use-dictionary"
|
||||||
|
import { getAssetUrl } from "@/lib/base-path"
|
||||||
|
|
||||||
interface ExampleCardProps {
|
interface ExampleCardProps {
|
||||||
icon: React.ReactNode
|
icon: React.ReactNode
|
||||||
@@ -79,7 +80,7 @@ export default function ExamplePanel({
|
|||||||
setInput("Replicate this flowchart.")
|
setInput("Replicate this flowchart.")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/example.png")
|
const response = await fetch(getAssetUrl("/example.png"))
|
||||||
const blob = await response.blob()
|
const blob = await response.blob()
|
||||||
const file = new File([blob], "example.png", { type: "image/png" })
|
const file = new File([blob], "example.png", { type: "image/png" })
|
||||||
setFiles([file])
|
setFiles([file])
|
||||||
@@ -92,7 +93,7 @@ export default function ExamplePanel({
|
|||||||
setInput("Replicate this in aws style")
|
setInput("Replicate this in aws style")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/architecture.png")
|
const response = await fetch(getAssetUrl("/architecture.png"))
|
||||||
const blob = await response.blob()
|
const blob = await response.blob()
|
||||||
const file = new File([blob], "architecture.png", {
|
const file = new File([blob], "architecture.png", {
|
||||||
type: "image/png",
|
type: "image/png",
|
||||||
@@ -107,7 +108,7 @@ export default function ExamplePanel({
|
|||||||
setInput("Summarize this paper as a diagram")
|
setInput("Summarize this paper as a diagram")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/chain-of-thought.txt")
|
const response = await fetch(getAssetUrl("/chain-of-thought.txt"))
|
||||||
const blob = await response.blob()
|
const blob = await response.blob()
|
||||||
const file = new File([blob], "chain-of-thought.txt", {
|
const file = new File([blob], "chain-of-thought.txt", {
|
||||||
type: "text/plain",
|
type: "text/plain",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
ReasoningTrigger,
|
ReasoningTrigger,
|
||||||
} from "@/components/ai-elements/reasoning"
|
} from "@/components/ai-elements/reasoning"
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||||
|
import { getApiEndpoint } from "@/lib/base-path"
|
||||||
import {
|
import {
|
||||||
applyDiagramOperations,
|
applyDiagramOperations,
|
||||||
convertToLegalXml,
|
convertToLegalXml,
|
||||||
@@ -291,7 +292,7 @@ export function ChatMessageDisplay({
|
|||||||
setFeedback((prev) => ({ ...prev, [messageId]: value }))
|
setFeedback((prev) => ({ ...prev, [messageId]: value }))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fetch("/api/log-feedback", {
|
await fetch(getApiEndpoint("/api/log-feedback"), {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
|
|||||||
@@ -21,16 +21,21 @@ import { ChatInput } from "@/components/chat-input"
|
|||||||
import { ModelConfigDialog } from "@/components/model-config-dialog"
|
import { ModelConfigDialog } from "@/components/model-config-dialog"
|
||||||
import { ResetWarningModal } from "@/components/reset-warning-modal"
|
import { ResetWarningModal } from "@/components/reset-warning-modal"
|
||||||
import { SettingsDialog } from "@/components/settings-dialog"
|
import { SettingsDialog } from "@/components/settings-dialog"
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from "@/components/ui/tooltip"
|
||||||
import { useDiagram } from "@/contexts/diagram-context"
|
import { useDiagram } from "@/contexts/diagram-context"
|
||||||
import { useDictionary } from "@/hooks/use-dictionary"
|
import { useDictionary } from "@/hooks/use-dictionary"
|
||||||
import { getSelectedAIConfig, useModelConfig } from "@/hooks/use-model-config"
|
import { getSelectedAIConfig, useModelConfig } from "@/hooks/use-model-config"
|
||||||
|
import { getApiEndpoint } from "@/lib/base-path"
|
||||||
import { findCachedResponse } from "@/lib/cached-responses"
|
import { findCachedResponse } from "@/lib/cached-responses"
|
||||||
import { isPdfFile, isTextFile } from "@/lib/pdf-utils"
|
import { isPdfFile, isTextFile } from "@/lib/pdf-utils"
|
||||||
import { type FileData, useFileProcessor } from "@/lib/use-file-processor"
|
import { type FileData, useFileProcessor } from "@/lib/use-file-processor"
|
||||||
import { useQuotaManager } from "@/lib/use-quota-manager"
|
import { useQuotaManager } from "@/lib/use-quota-manager"
|
||||||
import { formatXML, isMxCellXmlComplete, wrapWithMxFile } from "@/lib/utils"
|
import { formatXML, isMxCellXmlComplete, wrapWithMxFile } from "@/lib/utils"
|
||||||
import { ChatMessageDisplay } from "./chat-message-display"
|
import { ChatMessageDisplay } from "./chat-message-display"
|
||||||
import LanguageToggle from "./language-toggle"
|
|
||||||
|
|
||||||
// localStorage keys for persistence
|
// localStorage keys for persistence
|
||||||
const STORAGE_MESSAGES_KEY = "next-ai-draw-io-messages"
|
const STORAGE_MESSAGES_KEY = "next-ai-draw-io-messages"
|
||||||
@@ -168,7 +173,7 @@ export default function ChatPanel({
|
|||||||
|
|
||||||
// Check config on mount
|
// Check config on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch("/api/config")
|
fetch(getApiEndpoint("/api/config"))
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
setDailyRequestLimit(data.dailyRequestLimit || 0)
|
setDailyRequestLimit(data.dailyRequestLimit || 0)
|
||||||
@@ -239,7 +244,7 @@ export default function ChatPanel({
|
|||||||
setMessages,
|
setMessages,
|
||||||
} = useChat({
|
} = useChat({
|
||||||
transport: new DefaultChatTransport({
|
transport: new DefaultChatTransport({
|
||||||
api: "/api/chat",
|
api: getApiEndpoint("/api/chat"),
|
||||||
}),
|
}),
|
||||||
async onToolCall({ toolCall }) {
|
async onToolCall({ toolCall }) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
@@ -1264,32 +1269,18 @@ Continue from EXACTLY where you stopped.`,
|
|||||||
Next AI Drawio
|
Next AI Drawio
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
{!isMobile && (
|
{!isMobile &&
|
||||||
<Link
|
process.env.NEXT_PUBLIC_SHOW_ABOUT_AND_NOTICE ===
|
||||||
href="/about"
|
"true" && (
|
||||||
target="_blank"
|
<Link
|
||||||
rel="noopener noreferrer"
|
href="/about"
|
||||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors ml-2"
|
target="_blank"
|
||||||
>
|
rel="noopener noreferrer"
|
||||||
About
|
className="text-sm text-muted-foreground hover:text-foreground transition-colors ml-2"
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
{!isMobile && (
|
|
||||||
<Link
|
|
||||||
href="/about"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<ButtonWithTooltip
|
|
||||||
tooltipContent="Due to high usage, I have changed the model to minimax-m2 and added some usage limits. See About page for details."
|
|
||||||
variant="ghost"
|
|
||||||
size="icon"
|
|
||||||
className="h-6 w-6 text-amber-500 hover:text-amber-600"
|
|
||||||
>
|
>
|
||||||
<AlertTriangle className="h-4 w-4" />
|
About
|
||||||
</ButtonWithTooltip>
|
</Link>
|
||||||
</Link>
|
)}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1 justify-end overflow-visible">
|
<div className="flex items-center gap-1 justify-end overflow-visible">
|
||||||
<ButtonWithTooltip
|
<ButtonWithTooltip
|
||||||
@@ -1304,16 +1295,23 @@ Continue from EXACTLY where you stopped.`,
|
|||||||
/>
|
/>
|
||||||
</ButtonWithTooltip>
|
</ButtonWithTooltip>
|
||||||
<div className="w-px h-5 bg-border mx-1" />
|
<div className="w-px h-5 bg-border mx-1" />
|
||||||
<a
|
|
||||||
href="https://github.com/DayuanJiang/next-ai-draw-io"
|
<Tooltip>
|
||||||
target="_blank"
|
<TooltipTrigger asChild>
|
||||||
rel="noopener noreferrer"
|
<a
|
||||||
className="p-2 rounded-lg text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
|
href="https://github.com/DayuanJiang/next-ai-draw-io"
|
||||||
>
|
target="_blank"
|
||||||
<FaGithub
|
rel="noopener noreferrer"
|
||||||
className={`${isMobile ? "w-4 h-4" : "w-5 h-5"}`}
|
className="inline-flex items-center justify-center h-9 w-9 rounded-md text-muted-foreground hover:text-foreground hover:bg-accent transition-colors"
|
||||||
/>
|
>
|
||||||
</a>
|
<FaGithub
|
||||||
|
className={`${isMobile ? "w-4 h-4" : "w-5 h-5"}`}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>{dict.nav.github}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
<ButtonWithTooltip
|
<ButtonWithTooltip
|
||||||
tooltipContent={dict.nav.settings}
|
tooltipContent={dict.nav.settings}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -1326,7 +1324,6 @@ Continue from EXACTLY where you stopped.`,
|
|||||||
/>
|
/>
|
||||||
</ButtonWithTooltip>
|
</ButtonWithTooltip>
|
||||||
<div className="hidden sm:flex items-center gap-2">
|
<div className="hidden sm:flex items-center gap-2">
|
||||||
<LanguageToggle />
|
|
||||||
{!isMobile && (
|
{!isMobile && (
|
||||||
<ButtonWithTooltip
|
<ButtonWithTooltip
|
||||||
tooltipContent={dict.nav.hidePanel}
|
tooltipContent={dict.nav.hidePanel}
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { Globe } from "lucide-react"
|
|
||||||
import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
|
||||||
import { Suspense, useEffect, useRef, useState } from "react"
|
|
||||||
import { i18n, type Locale } from "@/lib/i18n/config"
|
|
||||||
|
|
||||||
const LABELS: Record<string, string> = {
|
|
||||||
en: "EN",
|
|
||||||
zh: "中文",
|
|
||||||
ja: "日本語",
|
|
||||||
}
|
|
||||||
|
|
||||||
function LanguageToggleInner({ className = "" }: { className?: string }) {
|
|
||||||
const router = useRouter()
|
|
||||||
const pathname = usePathname() || "/"
|
|
||||||
const search = useSearchParams()
|
|
||||||
const [open, setOpen] = useState(false)
|
|
||||||
const [value, setValue] = useState<Locale>(i18n.defaultLocale)
|
|
||||||
const ref = useRef<HTMLDivElement | null>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const seg = pathname.split("/").filter(Boolean)
|
|
||||||
const first = seg[0]
|
|
||||||
if (first && i18n.locales.includes(first as Locale))
|
|
||||||
setValue(first as Locale)
|
|
||||||
else setValue(i18n.defaultLocale)
|
|
||||||
}, [pathname])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
function onDoc(e: MouseEvent) {
|
|
||||||
if (!ref.current) return
|
|
||||||
if (!ref.current.contains(e.target as Node)) setOpen(false)
|
|
||||||
}
|
|
||||||
if (open) document.addEventListener("mousedown", onDoc)
|
|
||||||
return () => document.removeEventListener("mousedown", onDoc)
|
|
||||||
}, [open])
|
|
||||||
|
|
||||||
const changeLocale = (lang: string) => {
|
|
||||||
const parts = pathname.split("/")
|
|
||||||
if (parts.length > 1 && i18n.locales.includes(parts[1] as Locale)) {
|
|
||||||
parts[1] = lang
|
|
||||||
} else {
|
|
||||||
parts.splice(1, 0, lang)
|
|
||||||
}
|
|
||||||
const newPath = parts.join("/") || "/"
|
|
||||||
const searchStr = search?.toString() ? `?${search.toString()}` : ""
|
|
||||||
setOpen(false)
|
|
||||||
router.push(newPath + searchStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`relative inline-flex ${className}`} ref={ref}>
|
|
||||||
<button
|
|
||||||
aria-haspopup="menu"
|
|
||||||
aria-expanded={open}
|
|
||||||
onClick={() => setOpen((s) => !s)}
|
|
||||||
className="p-2 rounded-full hover:bg-accent/20 transition-colors text-muted-foreground"
|
|
||||||
aria-label="Change language"
|
|
||||||
>
|
|
||||||
<Globe className="w-5 h-5" />
|
|
||||||
</button>
|
|
||||||
{open && (
|
|
||||||
<div className="absolute right-0 top-full mt-2 w-40 bg-popover dark:bg-popover text-popover-foreground rounded-xl shadow-md border border-border/30 overflow-hidden z-50">
|
|
||||||
<div className="grid gap-0 divide-y divide-border/30">
|
|
||||||
{i18n.locales.map((loc) => (
|
|
||||||
<button
|
|
||||||
key={loc}
|
|
||||||
onClick={() => changeLocale(loc)}
|
|
||||||
className={`flex items-center gap-2 px-4 py-2 text-sm w-full text-left hover:bg-accent/10 transition-colors ${value === loc ? "bg-accent/10 font-semibold" : ""}`}
|
|
||||||
>
|
|
||||||
<span className="flex-1">
|
|
||||||
{LABELS[loc] ?? loc}
|
|
||||||
</span>
|
|
||||||
{value === loc && (
|
|
||||||
<span className="text-xs opacity-70">
|
|
||||||
✓
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function LanguageToggle({
|
|
||||||
className = "",
|
|
||||||
}: {
|
|
||||||
className?: string
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<Suspense
|
|
||||||
fallback={
|
|
||||||
<button
|
|
||||||
className="p-2 rounded-full text-muted-foreground opacity-50"
|
|
||||||
disabled
|
|
||||||
>
|
|
||||||
<Globe className="w-5 h-5" />
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<LanguageToggleInner className={className} />
|
|
||||||
</Suspense>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -52,6 +52,7 @@ import {
|
|||||||
} from "@/components/ui/select"
|
} from "@/components/ui/select"
|
||||||
import { useDictionary } from "@/hooks/use-dictionary"
|
import { useDictionary } from "@/hooks/use-dictionary"
|
||||||
import type { UseModelConfigReturn } from "@/hooks/use-model-config"
|
import type { UseModelConfigReturn } from "@/hooks/use-model-config"
|
||||||
|
import { formatMessage } from "@/lib/i18n/utils"
|
||||||
import type { ProviderConfig, ProviderName } from "@/lib/types/model-config"
|
import type { ProviderConfig, ProviderName } from "@/lib/types/model-config"
|
||||||
import { PROVIDER_INFO, SUGGESTED_MODELS } from "@/lib/types/model-config"
|
import { PROVIDER_INFO, SUGGESTED_MODELS } from "@/lib/types/model-config"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
@@ -107,10 +108,12 @@ function ValidationButton({
|
|||||||
status,
|
status,
|
||||||
onClick,
|
onClick,
|
||||||
disabled,
|
disabled,
|
||||||
|
dict,
|
||||||
}: {
|
}: {
|
||||||
status: ValidationStatus
|
status: ValidationStatus
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
disabled: boolean
|
disabled: boolean
|
||||||
|
dict: ReturnType<typeof useDictionary>
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
@@ -129,10 +132,10 @@ function ValidationButton({
|
|||||||
) : status === "success" ? (
|
) : status === "success" ? (
|
||||||
<>
|
<>
|
||||||
<Check className="h-4 w-4 mr-1.5" />
|
<Check className="h-4 w-4 mr-1.5" />
|
||||||
Verified
|
{dict.modelConfig.verified}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
"Test"
|
dict.modelConfig.test
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
@@ -406,7 +409,7 @@ export function ModelConfigDialog({
|
|||||||
<div className="w-56 flex-shrink-0 flex flex-col border-r bg-muted/20">
|
<div className="w-56 flex-shrink-0 flex flex-col border-r bg-muted/20">
|
||||||
<div className="px-4 py-3 border-b">
|
<div className="px-4 py-3 border-b">
|
||||||
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
<span className="text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
||||||
Providers
|
{dict.modelConfig.providers}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -418,7 +421,7 @@ export function ModelConfigDialog({
|
|||||||
<Plus className="h-5 w-5 text-muted-foreground" />
|
<Plus className="h-5 w-5 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Add a provider to get started
|
{dict.modelConfig.addProviderHint}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -484,7 +487,11 @@ export function ModelConfigDialog({
|
|||||||
>
|
>
|
||||||
<SelectTrigger className="h-9 bg-background hover:bg-accent">
|
<SelectTrigger className="h-9 bg-background hover:bg-accent">
|
||||||
<Plus className="h-4 w-4 mr-2 text-muted-foreground" />
|
<Plus className="h-4 w-4 mr-2 text-muted-foreground" />
|
||||||
<SelectValue placeholder="Add Provider" />
|
<SelectValue
|
||||||
|
placeholder={
|
||||||
|
dict.modelConfig.addProvider
|
||||||
|
}
|
||||||
|
/>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{availableProviders.map((p) => (
|
{availableProviders.map((p) => (
|
||||||
@@ -552,15 +559,27 @@ export function ModelConfigDialog({
|
|||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
{selectedProvider.models
|
{selectedProvider.models
|
||||||
.length === 0
|
.length === 0
|
||||||
? "No models configured"
|
? dict.modelConfig
|
||||||
: `${selectedProvider.models.length} model${selectedProvider.models.length > 1 ? "s" : ""} configured`}
|
.noModelsConfigured
|
||||||
|
: formatMessage(
|
||||||
|
dict.modelConfig
|
||||||
|
.modelsConfiguredCount,
|
||||||
|
{
|
||||||
|
count: selectedProvider
|
||||||
|
.models
|
||||||
|
.length,
|
||||||
|
},
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{selectedProvider.validated && (
|
{selectedProvider.validated && (
|
||||||
<div className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-emerald-500/10 text-emerald-600 dark:text-emerald-400">
|
<div className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-emerald-500/10 text-emerald-600 dark:text-emerald-400">
|
||||||
<Check className="h-3.5 w-3.5" />
|
<Check className="h-3.5 w-3.5" />
|
||||||
<span className="text-xs font-medium">
|
<span className="text-xs font-medium">
|
||||||
Verified
|
{
|
||||||
|
dict.modelConfig
|
||||||
|
.verified
|
||||||
|
}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -570,7 +589,12 @@ export function ModelConfigDialog({
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center gap-2 text-sm font-medium text-muted-foreground">
|
<div className="flex items-center gap-2 text-sm font-medium text-muted-foreground">
|
||||||
<Settings2 className="h-4 w-4" />
|
<Settings2 className="h-4 w-4" />
|
||||||
<span>Configuration</span>
|
<span>
|
||||||
|
{
|
||||||
|
dict.modelConfig
|
||||||
|
.configuration
|
||||||
|
}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="rounded-xl border bg-card p-4 space-y-4">
|
<div className="rounded-xl border bg-card p-4 space-y-4">
|
||||||
@@ -581,7 +605,10 @@ export function ModelConfigDialog({
|
|||||||
className="text-xs font-medium flex items-center gap-1.5"
|
className="text-xs font-medium flex items-center gap-1.5"
|
||||||
>
|
>
|
||||||
<Tag className="h-3.5 w-3.5 text-muted-foreground" />
|
<Tag className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
Display Name
|
{
|
||||||
|
dict.modelConfig
|
||||||
|
.displayName
|
||||||
|
}
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="provider-name"
|
id="provider-name"
|
||||||
@@ -616,8 +643,11 @@ export function ModelConfigDialog({
|
|||||||
className="text-xs font-medium flex items-center gap-1.5"
|
className="text-xs font-medium flex items-center gap-1.5"
|
||||||
>
|
>
|
||||||
<Key className="h-3.5 w-3.5 text-muted-foreground" />
|
<Key className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
AWS Access Key
|
{
|
||||||
ID
|
dict
|
||||||
|
.modelConfig
|
||||||
|
.awsAccessKeyId
|
||||||
|
}
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="aws-access-key-id"
|
id="aws-access-key-id"
|
||||||
@@ -649,8 +679,11 @@ export function ModelConfigDialog({
|
|||||||
className="text-xs font-medium flex items-center gap-1.5"
|
className="text-xs font-medium flex items-center gap-1.5"
|
||||||
>
|
>
|
||||||
<Key className="h-3.5 w-3.5 text-muted-foreground" />
|
<Key className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
AWS Secret
|
{
|
||||||
Access Key
|
dict
|
||||||
|
.modelConfig
|
||||||
|
.awsSecretAccessKey
|
||||||
|
}
|
||||||
</Label>
|
</Label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Input
|
<Input
|
||||||
@@ -674,7 +707,11 @@ export function ModelConfigDialog({
|
|||||||
.value,
|
.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
placeholder="Enter your secret access key"
|
placeholder={
|
||||||
|
dict
|
||||||
|
.modelConfig
|
||||||
|
.enterSecretKey
|
||||||
|
}
|
||||||
className="h-9 pr-10 font-mono text-xs"
|
className="h-9 pr-10 font-mono text-xs"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
@@ -707,7 +744,11 @@ export function ModelConfigDialog({
|
|||||||
className="text-xs font-medium flex items-center gap-1.5"
|
className="text-xs font-medium flex items-center gap-1.5"
|
||||||
>
|
>
|
||||||
<Link2 className="h-3.5 w-3.5 text-muted-foreground" />
|
<Link2 className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
AWS Region
|
{
|
||||||
|
dict
|
||||||
|
.modelConfig
|
||||||
|
.awsRegion
|
||||||
|
}
|
||||||
</Label>
|
</Label>
|
||||||
<Select
|
<Select
|
||||||
value={
|
value={
|
||||||
@@ -724,7 +765,13 @@ export function ModelConfigDialog({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="h-9 font-mono text-xs hover:bg-accent">
|
<SelectTrigger className="h-9 font-mono text-xs hover:bg-accent">
|
||||||
<SelectValue placeholder="Select region" />
|
<SelectValue
|
||||||
|
placeholder={
|
||||||
|
dict
|
||||||
|
.modelConfig
|
||||||
|
.selectRegion
|
||||||
|
}
|
||||||
|
/>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent className="max-h-64">
|
<SelectContent className="max-h-64">
|
||||||
<SelectItem value="us-east-1">
|
<SelectItem value="us-east-1">
|
||||||
@@ -819,10 +866,16 @@ export function ModelConfigDialog({
|
|||||||
"success" ? (
|
"success" ? (
|
||||||
<>
|
<>
|
||||||
<Check className="h-4 w-4 mr-1.5" />
|
<Check className="h-4 w-4 mr-1.5" />
|
||||||
Verified
|
{
|
||||||
|
dict
|
||||||
|
.modelConfig
|
||||||
|
.verified
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
"Test"
|
dict
|
||||||
|
.modelConfig
|
||||||
|
.test
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
{validationStatus ===
|
{validationStatus ===
|
||||||
@@ -846,7 +899,11 @@ export function ModelConfigDialog({
|
|||||||
className="text-xs font-medium flex items-center gap-1.5"
|
className="text-xs font-medium flex items-center gap-1.5"
|
||||||
>
|
>
|
||||||
<Key className="h-3.5 w-3.5 text-muted-foreground" />
|
<Key className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
API Key
|
{
|
||||||
|
dict
|
||||||
|
.modelConfig
|
||||||
|
.apiKey
|
||||||
|
}
|
||||||
</Label>
|
</Label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="relative flex-1">
|
<div className="relative flex-1">
|
||||||
@@ -870,7 +927,11 @@ export function ModelConfigDialog({
|
|||||||
.value,
|
.value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
placeholder="Enter your API key"
|
placeholder={
|
||||||
|
dict
|
||||||
|
.modelConfig
|
||||||
|
.enterApiKey
|
||||||
|
}
|
||||||
className="h-9 pr-10 font-mono text-xs"
|
className="h-9 pr-10 font-mono text-xs"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
@@ -924,10 +985,16 @@ export function ModelConfigDialog({
|
|||||||
"success" ? (
|
"success" ? (
|
||||||
<>
|
<>
|
||||||
<Check className="h-4 w-4 mr-1.5" />
|
<Check className="h-4 w-4 mr-1.5" />
|
||||||
Verified
|
{
|
||||||
|
dict
|
||||||
|
.modelConfig
|
||||||
|
.verified
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
"Test"
|
dict
|
||||||
|
.modelConfig
|
||||||
|
.test
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -950,9 +1017,17 @@ export function ModelConfigDialog({
|
|||||||
className="text-xs font-medium flex items-center gap-1.5"
|
className="text-xs font-medium flex items-center gap-1.5"
|
||||||
>
|
>
|
||||||
<Link2 className="h-3.5 w-3.5 text-muted-foreground" />
|
<Link2 className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
Base URL
|
{
|
||||||
|
dict
|
||||||
|
.modelConfig
|
||||||
|
.baseUrl
|
||||||
|
}
|
||||||
<span className="text-muted-foreground font-normal">
|
<span className="text-muted-foreground font-normal">
|
||||||
(optional)
|
{
|
||||||
|
dict
|
||||||
|
.modelConfig
|
||||||
|
.optional
|
||||||
|
}
|
||||||
</span>
|
</span>
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
@@ -974,7 +1049,9 @@ export function ModelConfigDialog({
|
|||||||
.provider
|
.provider
|
||||||
]
|
]
|
||||||
.defaultBaseUrl ||
|
.defaultBaseUrl ||
|
||||||
"Custom endpoint URL"
|
dict
|
||||||
|
.modelConfig
|
||||||
|
.customEndpoint
|
||||||
}
|
}
|
||||||
className="h-9 font-mono text-xs"
|
className="h-9 font-mono text-xs"
|
||||||
/>
|
/>
|
||||||
@@ -989,12 +1066,20 @@ export function ModelConfigDialog({
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-2 text-sm font-medium text-muted-foreground">
|
<div className="flex items-center gap-2 text-sm font-medium text-muted-foreground">
|
||||||
<Sparkles className="h-4 w-4" />
|
<Sparkles className="h-4 w-4" />
|
||||||
<span>Models</span>
|
<span>
|
||||||
|
{
|
||||||
|
dict.modelConfig
|
||||||
|
.models
|
||||||
|
}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Input
|
<Input
|
||||||
placeholder="Custom model ID..."
|
placeholder={
|
||||||
|
dict.modelConfig
|
||||||
|
.customModelId
|
||||||
|
}
|
||||||
value={
|
value={
|
||||||
customModelInput
|
customModelInput
|
||||||
}
|
}
|
||||||
@@ -1088,8 +1173,12 @@ export function ModelConfigDialog({
|
|||||||
<span className="text-xs">
|
<span className="text-xs">
|
||||||
{availableSuggestions.length ===
|
{availableSuggestions.length ===
|
||||||
0
|
0
|
||||||
? "All added"
|
? dict
|
||||||
: "Suggested"}
|
.modelConfig
|
||||||
|
.allAdded
|
||||||
|
: dict
|
||||||
|
.modelConfig
|
||||||
|
.suggested}
|
||||||
</span>
|
</span>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent className="max-h-72">
|
<SelectContent className="max-h-72">
|
||||||
@@ -1124,7 +1213,10 @@ export function ModelConfigDialog({
|
|||||||
<Sparkles className="h-5 w-5 text-muted-foreground" />
|
<Sparkles className="h-5 w-5 text-muted-foreground" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
No models configured
|
{
|
||||||
|
dict.modelConfig
|
||||||
|
.noModelsConfigured
|
||||||
|
}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -1291,7 +1383,9 @@ export function ModelConfigDialog({
|
|||||||
!newModelId
|
!newModelId
|
||||||
) {
|
) {
|
||||||
showError(
|
showError(
|
||||||
"Model ID cannot be empty",
|
dict
|
||||||
|
.modelConfig
|
||||||
|
.modelIdEmpty,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1319,7 +1413,9 @@ export function ModelConfigDialog({
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
showError(
|
showError(
|
||||||
"This model ID already exists",
|
dict
|
||||||
|
.modelConfig
|
||||||
|
.modelIdExists,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1383,7 +1479,10 @@ export function ModelConfigDialog({
|
|||||||
className="text-muted-foreground hover:text-destructive hover:bg-destructive/10"
|
className="text-muted-foreground hover:text-destructive hover:bg-destructive/10"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-4 w-4 mr-2" />
|
<Trash2 className="h-4 w-4 mr-2" />
|
||||||
Delete Provider
|
{
|
||||||
|
dict.modelConfig
|
||||||
|
.deleteProvider
|
||||||
|
}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1395,11 +1494,10 @@ export function ModelConfigDialog({
|
|||||||
<Server className="h-8 w-8 text-primary/60" />
|
<Server className="h-8 w-8 text-primary/60" />
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-semibold mb-1">
|
<h3 className="font-semibold mb-1">
|
||||||
Configure AI Providers
|
{dict.modelConfig.configureProviders}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-sm text-muted-foreground max-w-xs">
|
<p className="text-sm text-muted-foreground max-w-xs">
|
||||||
Select a provider from the list or add a new
|
{dict.modelConfig.selectProviderHint}
|
||||||
one to configure API keys and models
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1410,7 +1508,7 @@ export function ModelConfigDialog({
|
|||||||
<div className="px-6 py-3 border-t bg-muted/20">
|
<div className="px-6 py-3 border-t bg-muted/20">
|
||||||
<p className="text-xs text-muted-foreground text-center flex items-center justify-center gap-1.5">
|
<p className="text-xs text-muted-foreground text-center flex items-center justify-center gap-1.5">
|
||||||
<Key className="h-3 w-3" />
|
<Key className="h-3 w-3" />
|
||||||
API keys are stored locally in your browser
|
{dict.modelConfig.apiKeyStored}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
@@ -1429,19 +1527,16 @@ export function ModelConfigDialog({
|
|||||||
<AlertCircle className="h-6 w-6 text-destructive" />
|
<AlertCircle className="h-6 w-6 text-destructive" />
|
||||||
</div>
|
</div>
|
||||||
<AlertDialogTitle className="text-center">
|
<AlertDialogTitle className="text-center">
|
||||||
Delete Provider
|
{dict.modelConfig.deleteProvider}
|
||||||
</AlertDialogTitle>
|
</AlertDialogTitle>
|
||||||
<AlertDialogDescription className="text-center">
|
<AlertDialogDescription className="text-center">
|
||||||
Are you sure you want to delete{" "}
|
{formatMessage(dict.modelConfig.deleteConfirmDesc, {
|
||||||
<span className="font-medium text-foreground">
|
name: selectedProvider
|
||||||
{selectedProvider
|
|
||||||
? selectedProvider.name ||
|
? selectedProvider.name ||
|
||||||
PROVIDER_INFO[selectedProvider.provider]
|
PROVIDER_INFO[selectedProvider.provider]
|
||||||
.label
|
.label
|
||||||
: "this provider"}
|
: "this provider",
|
||||||
</span>
|
})}
|
||||||
? This will remove all configured models and cannot
|
|
||||||
be undone.
|
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
{selectedProvider &&
|
{selectedProvider &&
|
||||||
@@ -1451,11 +1546,16 @@ export function ModelConfigDialog({
|
|||||||
htmlFor="delete-confirm"
|
htmlFor="delete-confirm"
|
||||||
className="text-sm text-muted-foreground"
|
className="text-sm text-muted-foreground"
|
||||||
>
|
>
|
||||||
Type "
|
{formatMessage(
|
||||||
{selectedProvider.name ||
|
dict.modelConfig.typeToConfirm,
|
||||||
PROVIDER_INFO[selectedProvider.provider]
|
{
|
||||||
.label}
|
name:
|
||||||
" to confirm
|
selectedProvider.name ||
|
||||||
|
PROVIDER_INFO[
|
||||||
|
selectedProvider.provider
|
||||||
|
].label,
|
||||||
|
},
|
||||||
|
)}
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="delete-confirm"
|
id="delete-confirm"
|
||||||
@@ -1463,13 +1563,17 @@ export function ModelConfigDialog({
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setDeleteConfirmText(e.target.value)
|
setDeleteConfirmText(e.target.value)
|
||||||
}
|
}
|
||||||
placeholder="Type provider name..."
|
placeholder={
|
||||||
|
dict.modelConfig.typeProviderName
|
||||||
|
}
|
||||||
className="h-9"
|
className="h-9"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
<AlertDialogCancel>
|
||||||
|
{dict.modelConfig.cancel}
|
||||||
|
</AlertDialogCancel>
|
||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
onClick={handleDeleteProvider}
|
onClick={handleDeleteProvider}
|
||||||
disabled={
|
disabled={
|
||||||
@@ -1482,7 +1586,7 @@ export function ModelConfigDialog({
|
|||||||
}
|
}
|
||||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90 disabled:opacity-50"
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90 disabled:opacity-50"
|
||||||
>
|
>
|
||||||
Delete
|
{dict.modelConfig.delete}
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
ModelSelectorTrigger,
|
ModelSelectorTrigger,
|
||||||
} from "@/components/ai-elements/model-selector"
|
} from "@/components/ai-elements/model-selector"
|
||||||
import { ButtonWithTooltip } from "@/components/button-with-tooltip"
|
import { ButtonWithTooltip } from "@/components/button-with-tooltip"
|
||||||
|
import { useDictionary } from "@/hooks/use-dictionary"
|
||||||
import type { FlattenedModel } from "@/lib/types/model-config"
|
import type { FlattenedModel } from "@/lib/types/model-config"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
@@ -67,6 +68,7 @@ export function ModelSelector({
|
|||||||
onConfigure,
|
onConfigure,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
}: ModelSelectorProps) {
|
}: ModelSelectorProps) {
|
||||||
|
const dict = useDictionary()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
// Only show validated models in the selector
|
// Only show validated models in the selector
|
||||||
const validatedModels = useMemo(
|
const validatedModels = useMemo(
|
||||||
@@ -96,8 +98,8 @@ export function ModelSelector({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tooltipContent = selectedModel
|
const tooltipContent = selectedModel
|
||||||
? `${selectedModel.modelId} (click to change)`
|
? `${selectedModel.modelId} ${dict.modelConfig.clickToChange}`
|
||||||
: "Using server default model (click to change)"
|
: `${dict.modelConfig.usingServerDefault} ${dict.modelConfig.clickToChange}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModelSelectorRoot open={open} onOpenChange={setOpen}>
|
<ModelSelectorRoot open={open} onOpenChange={setOpen}>
|
||||||
@@ -111,22 +113,26 @@ export function ModelSelector({
|
|||||||
>
|
>
|
||||||
<Bot className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
|
<Bot className="h-4 w-4 flex-shrink-0 text-muted-foreground" />
|
||||||
<span className="text-xs truncate">
|
<span className="text-xs truncate">
|
||||||
{selectedModel ? selectedModel.modelId : "Default"}
|
{selectedModel
|
||||||
|
? selectedModel.modelId
|
||||||
|
: dict.modelConfig.default}
|
||||||
</span>
|
</span>
|
||||||
<ChevronDown className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
|
<ChevronDown className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
|
||||||
</ButtonWithTooltip>
|
</ButtonWithTooltip>
|
||||||
</ModelSelectorTrigger>
|
</ModelSelectorTrigger>
|
||||||
<ModelSelectorContent title="Select Model">
|
<ModelSelectorContent title={dict.modelConfig.selectModel}>
|
||||||
<ModelSelectorInput placeholder="Search models..." />
|
<ModelSelectorInput
|
||||||
|
placeholder={dict.modelConfig.searchModels}
|
||||||
|
/>
|
||||||
<ModelSelectorList>
|
<ModelSelectorList>
|
||||||
<ModelSelectorEmpty>
|
<ModelSelectorEmpty>
|
||||||
{validatedModels.length === 0 && models.length > 0
|
{validatedModels.length === 0 && models.length > 0
|
||||||
? "No verified models. Test your models first."
|
? dict.modelConfig.noVerifiedModels
|
||||||
: "No models found."}
|
: dict.modelConfig.noModelsFound}
|
||||||
</ModelSelectorEmpty>
|
</ModelSelectorEmpty>
|
||||||
|
|
||||||
{/* Server Default Option */}
|
{/* Server Default Option */}
|
||||||
<ModelSelectorGroup heading="Default">
|
<ModelSelectorGroup heading={dict.modelConfig.default}>
|
||||||
<ModelSelectorItem
|
<ModelSelectorItem
|
||||||
value="__server_default__"
|
value="__server_default__"
|
||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
@@ -145,7 +151,7 @@ export function ModelSelector({
|
|||||||
/>
|
/>
|
||||||
<Server className="mr-2 h-4 w-4 text-muted-foreground" />
|
<Server className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||||
<ModelSelectorName>
|
<ModelSelectorName>
|
||||||
Server Default
|
{dict.modelConfig.serverDefault}
|
||||||
</ModelSelectorName>
|
</ModelSelectorName>
|
||||||
</ModelSelectorItem>
|
</ModelSelectorItem>
|
||||||
</ModelSelectorGroup>
|
</ModelSelectorGroup>
|
||||||
@@ -201,13 +207,13 @@ export function ModelSelector({
|
|||||||
>
|
>
|
||||||
<Settings2 className="mr-2 h-4 w-4" />
|
<Settings2 className="mr-2 h-4 w-4" />
|
||||||
<ModelSelectorName>
|
<ModelSelectorName>
|
||||||
Configure Models...
|
{dict.modelConfig.configureModels}
|
||||||
</ModelSelectorName>
|
</ModelSelectorName>
|
||||||
</ModelSelectorItem>
|
</ModelSelectorItem>
|
||||||
</ModelSelectorGroup>
|
</ModelSelectorGroup>
|
||||||
{/* Info text */}
|
{/* Info text */}
|
||||||
<div className="px-3 py-2 text-xs text-muted-foreground border-t">
|
<div className="px-3 py-2 text-xs text-muted-foreground border-t">
|
||||||
Only verified models are shown
|
{dict.modelConfig.onlyVerifiedShown}
|
||||||
</div>
|
</div>
|
||||||
</ModelSelectorList>
|
</ModelSelectorList>
|
||||||
</ModelSelectorContent>
|
</ModelSelectorContent>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { Moon, Sun } from "lucide-react"
|
import { Moon, Sun } from "lucide-react"
|
||||||
import { useEffect, useState } from "react"
|
import { usePathname, useRouter, useSearchParams } from "next/navigation"
|
||||||
|
import { Suspense, useEffect, useState } from "react"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -12,8 +13,23 @@ import {
|
|||||||
} from "@/components/ui/dialog"
|
} from "@/components/ui/dialog"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select"
|
||||||
import { Switch } from "@/components/ui/switch"
|
import { Switch } from "@/components/ui/switch"
|
||||||
import { useDictionary } from "@/hooks/use-dictionary"
|
import { useDictionary } from "@/hooks/use-dictionary"
|
||||||
|
import { getApiEndpoint } from "@/lib/base-path"
|
||||||
|
import { i18n, type Locale } from "@/lib/i18n/config"
|
||||||
|
|
||||||
|
const LANGUAGE_LABELS: Record<Locale, string> = {
|
||||||
|
en: "English",
|
||||||
|
zh: "中文",
|
||||||
|
ja: "日本語",
|
||||||
|
}
|
||||||
|
|
||||||
interface SettingsDialogProps {
|
interface SettingsDialogProps {
|
||||||
open: boolean
|
open: boolean
|
||||||
@@ -36,7 +52,7 @@ function getStoredAccessCodeRequired(): boolean | null {
|
|||||||
return stored === "true"
|
return stored === "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SettingsDialog({
|
function SettingsContent({
|
||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
onCloseProtectionChange,
|
onCloseProtectionChange,
|
||||||
@@ -46,6 +62,9 @@ export function SettingsDialog({
|
|||||||
onToggleDarkMode,
|
onToggleDarkMode,
|
||||||
}: SettingsDialogProps) {
|
}: SettingsDialogProps) {
|
||||||
const dict = useDictionary()
|
const dict = useDictionary()
|
||||||
|
const router = useRouter()
|
||||||
|
const pathname = usePathname() || "/"
|
||||||
|
const search = useSearchParams()
|
||||||
const [accessCode, setAccessCode] = useState("")
|
const [accessCode, setAccessCode] = useState("")
|
||||||
const [closeProtection, setCloseProtection] = useState(true)
|
const [closeProtection, setCloseProtection] = useState(true)
|
||||||
const [isVerifying, setIsVerifying] = useState(false)
|
const [isVerifying, setIsVerifying] = useState(false)
|
||||||
@@ -53,12 +72,13 @@ export function SettingsDialog({
|
|||||||
const [accessCodeRequired, setAccessCodeRequired] = useState(
|
const [accessCodeRequired, setAccessCodeRequired] = useState(
|
||||||
() => getStoredAccessCodeRequired() ?? false,
|
() => getStoredAccessCodeRequired() ?? false,
|
||||||
)
|
)
|
||||||
|
const [currentLang, setCurrentLang] = useState("en")
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Only fetch if not cached in localStorage
|
// Only fetch if not cached in localStorage
|
||||||
if (getStoredAccessCodeRequired() !== null) return
|
if (getStoredAccessCodeRequired() !== null) return
|
||||||
|
|
||||||
fetch("/api/config")
|
fetch(getApiEndpoint("/api/config"))
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||||
return res.json()
|
return res.json()
|
||||||
@@ -77,6 +97,17 @@ export function SettingsDialog({
|
|||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
// Detect current language from pathname
|
||||||
|
useEffect(() => {
|
||||||
|
const seg = pathname.split("/").filter(Boolean)
|
||||||
|
const first = seg[0]
|
||||||
|
if (first && i18n.locales.includes(first as Locale)) {
|
||||||
|
setCurrentLang(first)
|
||||||
|
} else {
|
||||||
|
setCurrentLang(i18n.defaultLocale)
|
||||||
|
}
|
||||||
|
}, [pathname])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
const storedCode =
|
const storedCode =
|
||||||
@@ -93,6 +124,18 @@ export function SettingsDialog({
|
|||||||
}
|
}
|
||||||
}, [open])
|
}, [open])
|
||||||
|
|
||||||
|
const changeLanguage = (lang: string) => {
|
||||||
|
const parts = pathname.split("/")
|
||||||
|
if (parts.length > 1 && i18n.locales.includes(parts[1] as Locale)) {
|
||||||
|
parts[1] = lang
|
||||||
|
} else {
|
||||||
|
parts.splice(1, 0, lang)
|
||||||
|
}
|
||||||
|
const newPath = parts.join("/") || "/"
|
||||||
|
const searchStr = search?.toString() ? `?${search.toString()}` : ""
|
||||||
|
router.push(newPath + searchStr)
|
||||||
|
}
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
if (!accessCodeRequired) return
|
if (!accessCodeRequired) return
|
||||||
|
|
||||||
@@ -100,12 +143,15 @@ export function SettingsDialog({
|
|||||||
setIsVerifying(true)
|
setIsVerifying(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch("/api/verify-access-code", {
|
const response = await fetch(
|
||||||
method: "POST",
|
getApiEndpoint("/api/verify-access-code"),
|
||||||
headers: {
|
{
|
||||||
"x-access-code": accessCode.trim(),
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"x-access-code": accessCode.trim(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
|
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
|
|
||||||
@@ -131,128 +177,166 @@ export function SettingsDialog({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<DialogContent className="sm:max-w-md">
|
||||||
<DialogContent className="sm:max-w-md">
|
<DialogHeader>
|
||||||
<DialogHeader>
|
<DialogTitle>{dict.settings.title}</DialogTitle>
|
||||||
<DialogTitle>{dict.settings.title}</DialogTitle>
|
<DialogDescription>
|
||||||
<DialogDescription>
|
{dict.settings.description}
|
||||||
{dict.settings.description}
|
</DialogDescription>
|
||||||
</DialogDescription>
|
</DialogHeader>
|
||||||
</DialogHeader>
|
<div className="space-y-4 py-2">
|
||||||
<div className="space-y-4 py-2">
|
{accessCodeRequired && (
|
||||||
{accessCodeRequired && (
|
<div className="space-y-2">
|
||||||
<div className="space-y-2">
|
<Label htmlFor="access-code">
|
||||||
<Label htmlFor="access-code">
|
{dict.settings.accessCode}
|
||||||
{dict.settings.accessCode}
|
</Label>
|
||||||
</Label>
|
<div className="flex gap-2">
|
||||||
<div className="flex gap-2">
|
<Input
|
||||||
<Input
|
id="access-code"
|
||||||
id="access-code"
|
type="password"
|
||||||
type="password"
|
value={accessCode}
|
||||||
value={accessCode}
|
onChange={(e) => setAccessCode(e.target.value)}
|
||||||
onChange={(e) =>
|
onKeyDown={handleKeyDown}
|
||||||
setAccessCode(e.target.value)
|
placeholder={
|
||||||
}
|
dict.settings.accessCodePlaceholder
|
||||||
onKeyDown={handleKeyDown}
|
}
|
||||||
placeholder={
|
autoComplete="off"
|
||||||
dict.settings.accessCodePlaceholder
|
/>
|
||||||
}
|
<Button
|
||||||
autoComplete="off"
|
onClick={handleSave}
|
||||||
/>
|
disabled={isVerifying || !accessCode.trim()}
|
||||||
<Button
|
>
|
||||||
onClick={handleSave}
|
{isVerifying ? "..." : dict.common.save}
|
||||||
disabled={isVerifying || !accessCode.trim()}
|
</Button>
|
||||||
>
|
|
||||||
{isVerifying ? "..." : dict.common.save}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<p className="text-[0.8rem] text-muted-foreground">
|
|
||||||
{dict.settings.accessCodeDescription}
|
|
||||||
</p>
|
|
||||||
{error && (
|
|
||||||
<p className="text-[0.8rem] text-destructive">
|
|
||||||
{error}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<p className="text-[0.8rem] text-muted-foreground">
|
||||||
<div className="flex items-center justify-between">
|
{dict.settings.accessCodeDescription}
|
||||||
<div className="space-y-0.5">
|
</p>
|
||||||
<Label htmlFor="theme-toggle">
|
{error && (
|
||||||
{dict.settings.theme}
|
<p className="text-[0.8rem] text-destructive">
|
||||||
</Label>
|
{error}
|
||||||
<p className="text-[0.8rem] text-muted-foreground">
|
|
||||||
{dict.settings.themeDescription}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
)}
|
||||||
<Button
|
|
||||||
id="theme-toggle"
|
|
||||||
variant="outline"
|
|
||||||
size="icon"
|
|
||||||
onClick={onToggleDarkMode}
|
|
||||||
>
|
|
||||||
{darkMode ? (
|
|
||||||
<Sun className="h-4 w-4" />
|
|
||||||
) : (
|
|
||||||
<Moon className="h-4 w-4" />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<Label htmlFor="drawio-ui">
|
<Label htmlFor="language-select">
|
||||||
{dict.settings.drawioStyle}
|
{dict.settings.language}
|
||||||
</Label>
|
</Label>
|
||||||
<p className="text-[0.8rem] text-muted-foreground">
|
<p className="text-[0.8rem] text-muted-foreground">
|
||||||
{dict.settings.drawioStyleDescription}{" "}
|
{dict.settings.languageDescription}
|
||||||
{drawioUi === "min"
|
</p>
|
||||||
? dict.settings.minimal
|
</div>
|
||||||
: dict.settings.sketch}
|
<Select value={currentLang} onValueChange={changeLanguage}>
|
||||||
</p>
|
<SelectTrigger id="language-select" className="w-32">
|
||||||
</div>
|
<SelectValue />
|
||||||
<Button
|
</SelectTrigger>
|
||||||
id="drawio-ui"
|
<SelectContent>
|
||||||
variant="outline"
|
{i18n.locales.map((locale) => (
|
||||||
size="sm"
|
<SelectItem key={locale} value={locale}>
|
||||||
onClick={onToggleDrawioUi}
|
{LANGUAGE_LABELS[locale]}
|
||||||
>
|
</SelectItem>
|
||||||
{dict.settings.switchTo}{" "}
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<Label htmlFor="theme-toggle">
|
||||||
|
{dict.settings.theme}
|
||||||
|
</Label>
|
||||||
|
<p className="text-[0.8rem] text-muted-foreground">
|
||||||
|
{dict.settings.themeDescription}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
id="theme-toggle"
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
onClick={onToggleDarkMode}
|
||||||
|
>
|
||||||
|
{darkMode ? (
|
||||||
|
<Sun className="h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<Moon className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<Label htmlFor="drawio-ui">
|
||||||
|
{dict.settings.drawioStyle}
|
||||||
|
</Label>
|
||||||
|
<p className="text-[0.8rem] text-muted-foreground">
|
||||||
|
{dict.settings.drawioStyleDescription}{" "}
|
||||||
{drawioUi === "min"
|
{drawioUi === "min"
|
||||||
? dict.settings.sketch
|
? dict.settings.minimal
|
||||||
: dict.settings.minimal}
|
: dict.settings.sketch}
|
||||||
</Button>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<Button
|
||||||
|
id="drawio-ui"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={onToggleDrawioUi}
|
||||||
|
>
|
||||||
|
{dict.settings.switchTo}{" "}
|
||||||
|
{drawioUi === "min"
|
||||||
|
? dict.settings.sketch
|
||||||
|
: dict.settings.minimal}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<Label htmlFor="close-protection">
|
<Label htmlFor="close-protection">
|
||||||
{dict.settings.closeProtection}
|
{dict.settings.closeProtection}
|
||||||
</Label>
|
</Label>
|
||||||
<p className="text-[0.8rem] text-muted-foreground">
|
<p className="text-[0.8rem] text-muted-foreground">
|
||||||
{dict.settings.closeProtectionDescription}
|
{dict.settings.closeProtectionDescription}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
id="close-protection"
|
|
||||||
checked={closeProtection}
|
|
||||||
onCheckedChange={(checked) => {
|
|
||||||
setCloseProtection(checked)
|
|
||||||
localStorage.setItem(
|
|
||||||
STORAGE_CLOSE_PROTECTION_KEY,
|
|
||||||
checked.toString(),
|
|
||||||
)
|
|
||||||
onCloseProtectionChange?.(checked)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<Switch
|
||||||
|
id="close-protection"
|
||||||
|
checked={closeProtection}
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
setCloseProtection(checked)
|
||||||
|
localStorage.setItem(
|
||||||
|
STORAGE_CLOSE_PROTECTION_KEY,
|
||||||
|
checked.toString(),
|
||||||
|
)
|
||||||
|
onCloseProtectionChange?.(checked)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="pt-4 border-t border-border/50">
|
</div>
|
||||||
<p className="text-[0.75rem] text-muted-foreground text-center">
|
<div className="pt-4 border-t border-border/50">
|
||||||
Version {process.env.APP_VERSION}
|
<p className="text-[0.75rem] text-muted-foreground text-center">
|
||||||
</p>
|
Version {process.env.APP_VERSION}
|
||||||
</div>
|
</p>
|
||||||
</DialogContent>
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SettingsDialog(props: SettingsDialogProps) {
|
||||||
|
return (
|
||||||
|
<Dialog open={props.open} onOpenChange={props.onOpenChange}>
|
||||||
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<DialogContent className="sm:max-w-md">
|
||||||
|
<div className="h-64 flex items-center justify-center">
|
||||||
|
<div className="animate-spin h-8 w-8 border-4 border-primary border-t-transparent rounded-full" />
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SettingsContent {...props} />
|
||||||
|
</Suspense>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { createContext, useContext, useEffect, useRef, useState } from "react"
|
|||||||
import type { DrawIoEmbedRef } from "react-drawio"
|
import type { DrawIoEmbedRef } from "react-drawio"
|
||||||
import { STORAGE_DIAGRAM_XML_KEY } from "@/components/chat-panel"
|
import { STORAGE_DIAGRAM_XML_KEY } from "@/components/chat-panel"
|
||||||
import type { ExportFormat } from "@/components/save-dialog"
|
import type { ExportFormat } from "@/components/save-dialog"
|
||||||
|
import { getApiEndpoint } from "@/lib/base-path"
|
||||||
import { extractDiagramXML, validateAndFixXml } from "../lib/utils"
|
import { extractDiagramXML, validateAndFixXml } from "../lib/utils"
|
||||||
|
|
||||||
interface DiagramContextType {
|
interface DiagramContextType {
|
||||||
@@ -329,7 +330,7 @@ export function DiagramProvider({ children }: { children: React.ReactNode }) {
|
|||||||
sessionId?: string,
|
sessionId?: string,
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
await fetch("/api/log-save", {
|
await fetch(getApiEndpoint("/api/log-save"), {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ filename, format, sessionId }),
|
body: JSON.stringify({ filename, format, sessionId }),
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ services:
|
|||||||
context: .
|
context: .
|
||||||
args:
|
args:
|
||||||
- NEXT_PUBLIC_DRAWIO_BASE_URL=http://localhost:8080
|
- NEXT_PUBLIC_DRAWIO_BASE_URL=http://localhost:8080
|
||||||
|
# Uncomment below for subdirectory deployment
|
||||||
|
# - NEXT_PUBLIC_BASE_PATH=/nextaidrawio
|
||||||
ports: ["3000:3000"]
|
ports: ["3000:3000"]
|
||||||
env_file: .env
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
# For subdirectory deployment, uncomment and set your path:
|
||||||
|
# NEXT_PUBLIC_BASE_PATH: /nextaidrawio
|
||||||
depends_on: [drawio]
|
depends_on: [drawio]
|
||||||
|
|||||||
10
env.example
10
env.example
@@ -68,6 +68,10 @@ AI_MODEL=global.anthropic.claude-sonnet-4-5-20250929-v1:0
|
|||||||
# SILICONFLOW_API_KEY=sk-...
|
# SILICONFLOW_API_KEY=sk-...
|
||||||
# SILICONFLOW_BASE_URL=https://api.siliconflow.com/v1 # Optional: switch to https://api.siliconflow.cn/v1 if needed
|
# SILICONFLOW_BASE_URL=https://api.siliconflow.com/v1 # Optional: switch to https://api.siliconflow.cn/v1 if needed
|
||||||
|
|
||||||
|
# SGLang Configuration (OpenAI-compatible)
|
||||||
|
# SGLANG_API_KEY=your-sglang-api-key
|
||||||
|
# SGLANG_BASE_URL=http://127.0.0.1:8000/v1 # Your SGLang endpoint
|
||||||
|
|
||||||
# Vercel AI Gateway Configuration
|
# Vercel AI Gateway Configuration
|
||||||
# Get your API key from: https://vercel.com/ai-gateway
|
# Get your API key from: https://vercel.com/ai-gateway
|
||||||
# Model format: "provider/model" e.g., "openai/gpt-4o", "anthropic/claude-sonnet-4-5"
|
# Model format: "provider/model" e.g., "openai/gpt-4o", "anthropic/claude-sonnet-4-5"
|
||||||
@@ -93,6 +97,12 @@ AI_MODEL=global.anthropic.claude-sonnet-4-5-20250929-v1:0
|
|||||||
# NEXT_PUBLIC_DRAWIO_BASE_URL=https://embed.diagrams.net # Default: https://embed.diagrams.net
|
# NEXT_PUBLIC_DRAWIO_BASE_URL=https://embed.diagrams.net # Default: https://embed.diagrams.net
|
||||||
# Use this to point to a self-hosted draw.io instance
|
# Use this to point to a self-hosted draw.io instance
|
||||||
|
|
||||||
|
# Subdirectory Deployment (Optional)
|
||||||
|
# For deploying to a subdirectory (e.g., https://example.com/nextaidrawio)
|
||||||
|
# Set this to your subdirectory path with leading slash (e.g., /nextaidrawio)
|
||||||
|
# Leave empty for root deployment (default)
|
||||||
|
# NEXT_PUBLIC_BASE_PATH=/nextaidrawio
|
||||||
|
|
||||||
# PDF Input Feature (Optional)
|
# PDF Input Feature (Optional)
|
||||||
# Enable PDF file upload to extract text and generate diagrams
|
# Enable PDF file upload to extract text and generate diagrams
|
||||||
# Enabled by default. Set to "false" to disable.
|
# Enabled by default. Set to "false" to disable.
|
||||||
|
|||||||
@@ -19,10 +19,13 @@ export function register() {
|
|||||||
const spanName = otelSpan.name
|
const spanName = otelSpan.name
|
||||||
// Skip Next.js HTTP infrastructure spans
|
// Skip Next.js HTTP infrastructure spans
|
||||||
if (
|
if (
|
||||||
spanName.startsWith("POST /") ||
|
spanName.startsWith("POST") ||
|
||||||
spanName.startsWith("GET /") ||
|
spanName.startsWith("GET") ||
|
||||||
|
spanName.startsWith("RSC") ||
|
||||||
spanName.includes("BaseServer") ||
|
spanName.includes("BaseServer") ||
|
||||||
spanName.includes("handleRequest")
|
spanName.includes("handleRequest") ||
|
||||||
|
spanName.includes("resolve page") ||
|
||||||
|
spanName.includes("start response")
|
||||||
) {
|
) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -36,4 +39,5 @@ export function register() {
|
|||||||
|
|
||||||
// Register globally so AI SDK's telemetry also uses this processor
|
// Register globally so AI SDK's telemetry also uses this processor
|
||||||
tracerProvider.register()
|
tracerProvider.register()
|
||||||
|
console.log("[Langfuse] Instrumentation initialized successfully")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export type ProviderName =
|
|||||||
| "openrouter"
|
| "openrouter"
|
||||||
| "deepseek"
|
| "deepseek"
|
||||||
| "siliconflow"
|
| "siliconflow"
|
||||||
|
| "sglang"
|
||||||
| "gateway"
|
| "gateway"
|
||||||
|
|
||||||
interface ModelConfig {
|
interface ModelConfig {
|
||||||
@@ -50,6 +51,7 @@ const ALLOWED_CLIENT_PROVIDERS: ProviderName[] = [
|
|||||||
"openrouter",
|
"openrouter",
|
||||||
"deepseek",
|
"deepseek",
|
||||||
"siliconflow",
|
"siliconflow",
|
||||||
|
"sglang",
|
||||||
"gateway",
|
"gateway",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -343,6 +345,7 @@ function buildProviderOptions(
|
|||||||
case "deepseek":
|
case "deepseek":
|
||||||
case "openrouter":
|
case "openrouter":
|
||||||
case "siliconflow":
|
case "siliconflow":
|
||||||
|
case "sglang":
|
||||||
case "gateway": {
|
case "gateway": {
|
||||||
// These providers don't have reasoning configs in AI SDK yet
|
// These providers don't have reasoning configs in AI SDK yet
|
||||||
// Gateway passes through to underlying providers which handle their own configs
|
// Gateway passes through to underlying providers which handle their own configs
|
||||||
@@ -367,6 +370,7 @@ const PROVIDER_ENV_VARS: Record<ProviderName, string | null> = {
|
|||||||
openrouter: "OPENROUTER_API_KEY",
|
openrouter: "OPENROUTER_API_KEY",
|
||||||
deepseek: "DEEPSEEK_API_KEY",
|
deepseek: "DEEPSEEK_API_KEY",
|
||||||
siliconflow: "SILICONFLOW_API_KEY",
|
siliconflow: "SILICONFLOW_API_KEY",
|
||||||
|
sglang: "SGLANG_API_KEY",
|
||||||
gateway: "AI_GATEWAY_API_KEY",
|
gateway: "AI_GATEWAY_API_KEY",
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,7 +436,7 @@ function validateProviderCredentials(provider: ProviderName): void {
|
|||||||
* Get the AI model based on environment variables
|
* Get the AI model based on environment variables
|
||||||
*
|
*
|
||||||
* Environment variables:
|
* Environment variables:
|
||||||
* - AI_PROVIDER: The provider to use (bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek, siliconflow)
|
* - AI_PROVIDER: The provider to use (bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek, siliconflow, sglang, gateway)
|
||||||
* - AI_MODEL: The model ID/name for the selected provider
|
* - AI_MODEL: The model ID/name for the selected provider
|
||||||
*
|
*
|
||||||
* Provider-specific env vars:
|
* Provider-specific env vars:
|
||||||
@@ -448,6 +452,8 @@ function validateProviderCredentials(provider: ProviderName): void {
|
|||||||
* - DEEPSEEK_BASE_URL: DeepSeek endpoint (optional)
|
* - DEEPSEEK_BASE_URL: DeepSeek endpoint (optional)
|
||||||
* - SILICONFLOW_API_KEY: SiliconFlow API key
|
* - SILICONFLOW_API_KEY: SiliconFlow API key
|
||||||
* - SILICONFLOW_BASE_URL: SiliconFlow endpoint (optional, defaults to https://api.siliconflow.com/v1)
|
* - SILICONFLOW_BASE_URL: SiliconFlow endpoint (optional, defaults to https://api.siliconflow.com/v1)
|
||||||
|
* - SGLANG_API_KEY: SGLang API key
|
||||||
|
* - SGLANG_BASE_URL: SGLang endpoint (optional)
|
||||||
*/
|
*/
|
||||||
export function getAIModel(overrides?: ClientOverrides): ModelConfig {
|
export function getAIModel(overrides?: ClientOverrides): ModelConfig {
|
||||||
// SECURITY: Prevent SSRF attacks (GHSA-9qf7-mprq-9qgm)
|
// SECURITY: Prevent SSRF attacks (GHSA-9qf7-mprq-9qgm)
|
||||||
@@ -516,6 +522,7 @@ export function getAIModel(overrides?: ClientOverrides): ModelConfig {
|
|||||||
`- OPENROUTER_API_KEY for OpenRouter\n` +
|
`- OPENROUTER_API_KEY for OpenRouter\n` +
|
||||||
`- AZURE_API_KEY for Azure\n` +
|
`- AZURE_API_KEY for Azure\n` +
|
||||||
`- SILICONFLOW_API_KEY for SiliconFlow\n` +
|
`- SILICONFLOW_API_KEY for SiliconFlow\n` +
|
||||||
|
`- SGLANG_API_KEY for SGLang\n` +
|
||||||
`Or set AI_PROVIDER=ollama for local Ollama.`,
|
`Or set AI_PROVIDER=ollama for local Ollama.`,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -698,6 +705,112 @@ export function getAIModel(overrides?: ClientOverrides): ModelConfig {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "sglang": {
|
||||||
|
const apiKey = overrides?.apiKey || process.env.SGLANG_API_KEY
|
||||||
|
const baseURL = overrides?.baseUrl || process.env.SGLANG_BASE_URL
|
||||||
|
|
||||||
|
const sglangProvider = createOpenAI({
|
||||||
|
apiKey,
|
||||||
|
baseURL,
|
||||||
|
// Add a custom fetch wrapper to intercept and fix the stream from sglang
|
||||||
|
fetch: async (url, options) => {
|
||||||
|
const response = await fetch(url, options)
|
||||||
|
if (!response.body) {
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a transform stream to fix the non-compliant sglang stream
|
||||||
|
let buffer = ""
|
||||||
|
const decoder = new TextDecoder()
|
||||||
|
|
||||||
|
const transformStream = new TransformStream({
|
||||||
|
transform(chunk, controller) {
|
||||||
|
buffer += decoder.decode(chunk, { stream: true })
|
||||||
|
// Process all complete messages in the buffer
|
||||||
|
let messageEndPos
|
||||||
|
while (
|
||||||
|
(messageEndPos = buffer.indexOf("\n\n")) !== -1
|
||||||
|
) {
|
||||||
|
const message = buffer.substring(
|
||||||
|
0,
|
||||||
|
messageEndPos,
|
||||||
|
)
|
||||||
|
buffer = buffer.substring(messageEndPos + 2) // Move past the '\n\n'
|
||||||
|
|
||||||
|
if (message.startsWith("data: ")) {
|
||||||
|
const jsonStr = message.substring(6).trim()
|
||||||
|
if (jsonStr === "[DONE]") {
|
||||||
|
controller.enqueue(
|
||||||
|
new TextEncoder().encode(
|
||||||
|
message + "\n\n",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(jsonStr)
|
||||||
|
const delta = data.choices?.[0]?.delta
|
||||||
|
|
||||||
|
if (delta) {
|
||||||
|
// Fix 1: remove invalid empty role
|
||||||
|
if (delta.role === "") {
|
||||||
|
delete delta.role
|
||||||
|
}
|
||||||
|
// Fix 2: remove non-standard reasoning_content field
|
||||||
|
if ("reasoning_content" in delta) {
|
||||||
|
delete delta.reasoning_content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-serialize and forward the corrected data with the correct SSE format
|
||||||
|
controller.enqueue(
|
||||||
|
new TextEncoder().encode(
|
||||||
|
`data: ${JSON.stringify(data)}\n\n`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
// If parsing fails, forward the original message to avoid breaking the stream.
|
||||||
|
controller.enqueue(
|
||||||
|
new TextEncoder().encode(
|
||||||
|
message + "\n\n",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (message.trim() !== "") {
|
||||||
|
// Pass through other message types (e.g., 'event: ...')
|
||||||
|
controller.enqueue(
|
||||||
|
new TextEncoder().encode(
|
||||||
|
message + "\n\n",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
flush(controller) {
|
||||||
|
// If there's anything left in the buffer, forward it.
|
||||||
|
if (buffer.trim()) {
|
||||||
|
controller.enqueue(
|
||||||
|
new TextEncoder().encode(buffer),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const transformedBody =
|
||||||
|
response.body.pipeThrough(transformStream)
|
||||||
|
|
||||||
|
// Return a new response with the transformed body
|
||||||
|
return new Response(transformedBody, {
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
headers: response.headers,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
model = sglangProvider.chat(modelId)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
case "gateway": {
|
case "gateway": {
|
||||||
// Vercel AI Gateway - unified access to multiple AI providers
|
// Vercel AI Gateway - unified access to multiple AI providers
|
||||||
// Model format: "provider/model" e.g., "openai/gpt-4o", "anthropic/claude-sonnet-4-5"
|
// Model format: "provider/model" e.g., "openai/gpt-4o", "anthropic/claude-sonnet-4-5"
|
||||||
@@ -721,7 +834,7 @@ export function getAIModel(overrides?: ClientOverrides): ModelConfig {
|
|||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unknown AI provider: ${provider}. Supported providers: bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek, siliconflow, gateway`,
|
`Unknown AI provider: ${provider}. Supported providers: bedrock, openai, anthropic, google, azure, ollama, openrouter, deepseek, siliconflow, sglang, gateway`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
37
lib/base-path.ts
Normal file
37
lib/base-path.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* Get the base path for API calls and static assets
|
||||||
|
* This is used for subdirectory deployment support
|
||||||
|
*
|
||||||
|
* Example: If deployed at https://example.com/nextaidrawio, this returns "/nextaidrawio"
|
||||||
|
* For root deployment, this returns ""
|
||||||
|
*
|
||||||
|
* Set NEXT_PUBLIC_BASE_PATH environment variable to your subdirectory path (e.g., /nextaidrawio)
|
||||||
|
*/
|
||||||
|
export function getBasePath(): string {
|
||||||
|
// Read from environment variable (must start with NEXT_PUBLIC_ to be available on client)
|
||||||
|
const basePath = process.env.NEXT_PUBLIC_BASE_PATH || ""
|
||||||
|
if (basePath && !basePath.startsWith("/")) {
|
||||||
|
console.warn("NEXT_PUBLIC_BASE_PATH should start with /")
|
||||||
|
}
|
||||||
|
return basePath
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get full API endpoint URL
|
||||||
|
* @param endpoint - API endpoint path (e.g., "/api/chat", "/api/config")
|
||||||
|
* @returns Full API path with base path prefix
|
||||||
|
*/
|
||||||
|
export function getApiEndpoint(endpoint: string): string {
|
||||||
|
const basePath = getBasePath()
|
||||||
|
return `${basePath}${endpoint}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get full static asset URL
|
||||||
|
* @param assetPath - Asset path (e.g., "/example.png", "/chain-of-thought.txt")
|
||||||
|
* @returns Full asset path with base path prefix
|
||||||
|
*/
|
||||||
|
export function getAssetUrl(assetPath: string): string {
|
||||||
|
const basePath = getBasePath()
|
||||||
|
return `${basePath}${assetPath}`
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
"about": "About",
|
"about": "About",
|
||||||
"editor": "Editor",
|
"editor": "Editor",
|
||||||
"newChat": "Start fresh chat",
|
"newChat": "Start fresh chat",
|
||||||
|
"github": "GitHub",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
"hidePanel": "Hide chat panel (Ctrl+B)",
|
"hidePanel": "Hide chat panel (Ctrl+B)",
|
||||||
"showPanel": "Show chat panel (Ctrl+B)",
|
"showPanel": "Show chat panel (Ctrl+B)",
|
||||||
@@ -87,6 +88,8 @@
|
|||||||
"overrides": "Overrides",
|
"overrides": "Overrides",
|
||||||
"clearSettings": "Clear Settings",
|
"clearSettings": "Clear Settings",
|
||||||
"useServerDefault": "Use Server Default",
|
"useServerDefault": "Use Server Default",
|
||||||
|
"language": "Language",
|
||||||
|
"languageDescription": "Choose your interface language.",
|
||||||
"theme": "Theme",
|
"theme": "Theme",
|
||||||
"themeDescription": "Dark/Light mode for interface and DrawIO canvas.",
|
"themeDescription": "Dark/Light mode for interface and DrawIO canvas.",
|
||||||
"drawioStyle": "DrawIO Style",
|
"drawioStyle": "DrawIO Style",
|
||||||
@@ -147,6 +150,7 @@
|
|||||||
"tokenLimit": "Daily Token Limit Reached",
|
"tokenLimit": "Daily Token Limit Reached",
|
||||||
"tpmLimit": "Rate Limit",
|
"tpmLimit": "Rate Limit",
|
||||||
"tpmMessage": "Too many requests. Please wait a moment.",
|
"tpmMessage": "Too many requests. Please wait a moment.",
|
||||||
|
"tpmMessageDetailed": "Rate limit reached ({limit} tokens/min). Please wait {seconds} seconds before sending another request.",
|
||||||
"messageApi": "Oops — you've reached the daily API limit for this demo! As an indie developer covering all the API costs myself, I have to set these limits to keep things sustainable.",
|
"messageApi": "Oops — you've reached the daily API limit for this demo! As an indie developer covering all the API costs myself, I have to set these limits to keep things sustainable.",
|
||||||
"messageToken": "Oops — you've reached the daily token limit for this demo! As an indie developer covering all the API costs myself, I have to set these limits to keep things sustainable.",
|
"messageToken": "Oops — you've reached the daily token limit for this demo! As an indie developer covering all the API costs myself, I have to set these limits to keep things sustainable.",
|
||||||
"tip": "<strong>Tip:</strong> You can use your own API key (click the Settings icon) or self-host the project to bypass these limits.",
|
"tip": "<strong>Tip:</strong> You can use your own API key (click the Settings icon) or self-host the project to bypass these limits.",
|
||||||
@@ -198,6 +202,47 @@
|
|||||||
"apiKeyStored": "API keys are stored locally in your browser",
|
"apiKeyStored": "API keys are stored locally in your browser",
|
||||||
"test": "Test",
|
"test": "Test",
|
||||||
"validationError": "Validation failed",
|
"validationError": "Validation failed",
|
||||||
"addModelFirst": "Add at least one model to validate"
|
"addModelFirst": "Add at least one model to validate",
|
||||||
|
"providers": "Providers",
|
||||||
|
"addProviderHint": "Add a provider to get started",
|
||||||
|
"verified": "Verified",
|
||||||
|
"configuration": "Configuration",
|
||||||
|
"displayName": "Display Name",
|
||||||
|
"awsAccessKeyId": "AWS Access Key ID",
|
||||||
|
"awsSecretAccessKey": "AWS Secret Access Key",
|
||||||
|
"awsRegion": "AWS Region",
|
||||||
|
"selectRegion": "Select region",
|
||||||
|
"apiKey": "API Key",
|
||||||
|
"enterApiKey": "Enter your API key",
|
||||||
|
"enterSecretKey": "Enter your secret access key",
|
||||||
|
"baseUrl": "Base URL",
|
||||||
|
"optional": "(optional)",
|
||||||
|
"customEndpoint": "Custom endpoint URL",
|
||||||
|
"models": "Models",
|
||||||
|
"customModelId": "Custom model ID...",
|
||||||
|
"allAdded": "All added",
|
||||||
|
"suggested": "Suggested",
|
||||||
|
"noModelsConfigured": "No models configured",
|
||||||
|
"modelIdEmpty": "Model ID cannot be empty",
|
||||||
|
"modelIdExists": "This model ID already exists",
|
||||||
|
"configureProviders": "Configure AI Providers",
|
||||||
|
"selectProviderHint": "Select a provider from the list or add a new one to configure API keys and models",
|
||||||
|
"deleteConfirmDesc": "Are you sure you want to delete {name}? This will remove all configured models and cannot be undone.",
|
||||||
|
"typeToConfirm": "Type \"{name}\" to confirm",
|
||||||
|
"typeProviderName": "Type provider name...",
|
||||||
|
"modelsConfiguredCount": "{count} model(s) configured",
|
||||||
|
"validationFailedCount": "{count} model(s) failed validation",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"delete": "Delete",
|
||||||
|
"clickToChange": "(click to change)",
|
||||||
|
"usingServerDefault": "Using server default model",
|
||||||
|
"selectModel": "Select Model",
|
||||||
|
"searchModels": "Search models...",
|
||||||
|
"noVerifiedModels": "No verified models. Test your models first.",
|
||||||
|
"noModelsFound": "No models found.",
|
||||||
|
"default": "Default",
|
||||||
|
"serverDefault": "Server Default",
|
||||||
|
"configureModels": "Configure Models...",
|
||||||
|
"onlyVerifiedShown": "Only verified models are shown"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"about": "概要",
|
"about": "概要",
|
||||||
"editor": "エディタ",
|
"editor": "エディタ",
|
||||||
"newChat": "新しいチャットを開始",
|
"newChat": "新しいチャットを開始",
|
||||||
|
"github": "GitHub",
|
||||||
"settings": "設定",
|
"settings": "設定",
|
||||||
"hidePanel": "チャットパネルを非表示 (Ctrl+B)",
|
"hidePanel": "チャットパネルを非表示 (Ctrl+B)",
|
||||||
"showPanel": "チャットパネルを表示 (Ctrl+B)",
|
"showPanel": "チャットパネルを表示 (Ctrl+B)",
|
||||||
@@ -87,6 +88,8 @@
|
|||||||
"overrides": "上書き",
|
"overrides": "上書き",
|
||||||
"clearSettings": "設定をクリア",
|
"clearSettings": "設定をクリア",
|
||||||
"useServerDefault": "サーバーデフォルトを使用",
|
"useServerDefault": "サーバーデフォルトを使用",
|
||||||
|
"language": "言語",
|
||||||
|
"languageDescription": "インターフェース言語を選択します。",
|
||||||
"theme": "テーマ",
|
"theme": "テーマ",
|
||||||
"themeDescription": "インターフェースと DrawIO キャンバスのダーク/ライトモード。",
|
"themeDescription": "インターフェースと DrawIO キャンバスのダーク/ライトモード。",
|
||||||
"drawioStyle": "DrawIO スタイル",
|
"drawioStyle": "DrawIO スタイル",
|
||||||
@@ -147,6 +150,7 @@
|
|||||||
"tokenLimit": "1日のトークン制限に達しました",
|
"tokenLimit": "1日のトークン制限に達しました",
|
||||||
"tpmLimit": "レート制限",
|
"tpmLimit": "レート制限",
|
||||||
"tpmMessage": "リクエストが多すぎます。しばらくお待ちください。",
|
"tpmMessage": "リクエストが多すぎます。しばらくお待ちください。",
|
||||||
|
"tpmMessageDetailed": "レート制限に達しました({limit}トークン/分)。{seconds}秒待ってからもう一度リクエストしてください。",
|
||||||
"messageApi": "おっと — このデモの1日の API 制限に達しました!個人開発者として API コストをすべて負担しているため、持続可能性を保つためにこれらの制限を設定する必要があります。",
|
"messageApi": "おっと — このデモの1日の API 制限に達しました!個人開発者として API コストをすべて負担しているため、持続可能性を保つためにこれらの制限を設定する必要があります。",
|
||||||
"messageToken": "おっと — このデモの1日のトークン制限に達しました!個人開発者として API コストをすべて負担しているため、持続可能性を保つためにこれらの制限を設定する必要があります。",
|
"messageToken": "おっと — このデモの1日のトークン制限に達しました!個人開発者として API コストをすべて負担しているため、持続可能性を保つためにこれらの制限を設定する必要があります。",
|
||||||
"tip": "<strong>ヒント:</strong>独自の API キーを使用する(設定アイコンをクリック)か、プロジェクトをセルフホストしてこれらの制限を回避できます。",
|
"tip": "<strong>ヒント:</strong>独自の API キーを使用する(設定アイコンをクリック)か、プロジェクトをセルフホストしてこれらの制限を回避できます。",
|
||||||
@@ -198,6 +202,47 @@
|
|||||||
"apiKeyStored": "APIキーはブラウザにローカル保存されます",
|
"apiKeyStored": "APIキーはブラウザにローカル保存されます",
|
||||||
"test": "テスト",
|
"test": "テスト",
|
||||||
"validationError": "検証に失敗しました",
|
"validationError": "検証に失敗しました",
|
||||||
"addModelFirst": "検証するには少なくとも1つのモデルを追加してください"
|
"addModelFirst": "検証するには少なくとも1つのモデルを追加してください",
|
||||||
|
"providers": "プロバイダー",
|
||||||
|
"addProviderHint": "プロバイダーを追加して開始",
|
||||||
|
"verified": "検証済み",
|
||||||
|
"configuration": "設定",
|
||||||
|
"displayName": "表示名",
|
||||||
|
"awsAccessKeyId": "AWS アクセスキー ID",
|
||||||
|
"awsSecretAccessKey": "AWS シークレットアクセスキー",
|
||||||
|
"awsRegion": "AWS リージョン",
|
||||||
|
"selectRegion": "リージョンを選択",
|
||||||
|
"apiKey": "API キー",
|
||||||
|
"enterApiKey": "API キーを入力",
|
||||||
|
"enterSecretKey": "シークレットアクセスキーを入力",
|
||||||
|
"baseUrl": "ベース URL",
|
||||||
|
"optional": "(オプション)",
|
||||||
|
"customEndpoint": "カスタムエンドポイント URL",
|
||||||
|
"models": "モデル",
|
||||||
|
"customModelId": "カスタムモデル ID...",
|
||||||
|
"allAdded": "すべて追加済み",
|
||||||
|
"suggested": "おすすめ",
|
||||||
|
"noModelsConfigured": "モデルが設定されていません",
|
||||||
|
"modelIdEmpty": "モデル ID は空にできません",
|
||||||
|
"modelIdExists": "このモデル ID は既に存在します",
|
||||||
|
"configureProviders": "AI プロバイダーを設定",
|
||||||
|
"selectProviderHint": "リストからプロバイダーを選択するか、新規追加して API キーとモデルを設定",
|
||||||
|
"deleteConfirmDesc": "{name} を削除してもよろしいですか?設定されたすべてのモデルが削除され、元に戻せません。",
|
||||||
|
"typeToConfirm": "確認のため「{name}」と入力",
|
||||||
|
"typeProviderName": "プロバイダー名を入力...",
|
||||||
|
"modelsConfiguredCount": "{count} 個のモデルを設定済み",
|
||||||
|
"validationFailedCount": "{count} 個のモデルの検証に失敗",
|
||||||
|
"cancel": "キャンセル",
|
||||||
|
"delete": "削除",
|
||||||
|
"clickToChange": "(クリックして変更)",
|
||||||
|
"usingServerDefault": "サーバーデフォルトモデルを使用中",
|
||||||
|
"selectModel": "モデルを選択",
|
||||||
|
"searchModels": "モデルを検索...",
|
||||||
|
"noVerifiedModels": "検証済みのモデルがありません。先にモデルをテストしてください。",
|
||||||
|
"noModelsFound": "モデルが見つかりません。",
|
||||||
|
"default": "デフォルト",
|
||||||
|
"serverDefault": "サーバーデフォルト",
|
||||||
|
"configureModels": "モデルを設定...",
|
||||||
|
"onlyVerifiedShown": "検証済みのモデルのみ表示"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"about": "关于",
|
"about": "关于",
|
||||||
"editor": "编辑器",
|
"editor": "编辑器",
|
||||||
"newChat": "开始新对话",
|
"newChat": "开始新对话",
|
||||||
|
"github": "GitHub",
|
||||||
"settings": "设置",
|
"settings": "设置",
|
||||||
"hidePanel": "隐藏聊天面板 (Ctrl+B)",
|
"hidePanel": "隐藏聊天面板 (Ctrl+B)",
|
||||||
"showPanel": "显示聊天面板 (Ctrl+B)",
|
"showPanel": "显示聊天面板 (Ctrl+B)",
|
||||||
@@ -87,6 +88,8 @@
|
|||||||
"overrides": "覆盖",
|
"overrides": "覆盖",
|
||||||
"clearSettings": "清除设置",
|
"clearSettings": "清除设置",
|
||||||
"useServerDefault": "使用服务器默认值",
|
"useServerDefault": "使用服务器默认值",
|
||||||
|
"language": "语言",
|
||||||
|
"languageDescription": "选择界面语言。",
|
||||||
"theme": "主题",
|
"theme": "主题",
|
||||||
"themeDescription": "界面和 DrawIO 画布的深色/浅色模式。",
|
"themeDescription": "界面和 DrawIO 画布的深色/浅色模式。",
|
||||||
"drawioStyle": "DrawIO 样式",
|
"drawioStyle": "DrawIO 样式",
|
||||||
@@ -147,6 +150,7 @@
|
|||||||
"tokenLimit": "已达每日令牌限制",
|
"tokenLimit": "已达每日令牌限制",
|
||||||
"tpmLimit": "速率限制",
|
"tpmLimit": "速率限制",
|
||||||
"tpmMessage": "请求过多。请稍等片刻。",
|
"tpmMessage": "请求过多。请稍等片刻。",
|
||||||
|
"tpmMessageDetailed": "达到速率限制({limit} 令牌/分钟)。请等待 {seconds} 秒后再发送请求。",
|
||||||
"messageApi": "糟糕 — 您已达到此演示的每日 API 限制!作为一名独立开发者,我自己承担所有 API 费用,因此必须设置这些限制以保持可持续性。",
|
"messageApi": "糟糕 — 您已达到此演示的每日 API 限制!作为一名独立开发者,我自己承担所有 API 费用,因此必须设置这些限制以保持可持续性。",
|
||||||
"messageToken": "糟糕 — 您已达到此演示的每日令牌限制!作为一名独立开发者,我自己承担所有 API 费用,因此必须设置这些限制以保持可持续性。",
|
"messageToken": "糟糕 — 您已达到此演示的每日令牌限制!作为一名独立开发者,我自己承担所有 API 费用,因此必须设置这些限制以保持可持续性。",
|
||||||
"tip": "<strong>提示:</strong>您可以使用自己的 API 密钥(点击设置图标)或自托管项目来绕过这些限制。",
|
"tip": "<strong>提示:</strong>您可以使用自己的 API 密钥(点击设置图标)或自托管项目来绕过这些限制。",
|
||||||
@@ -198,6 +202,47 @@
|
|||||||
"apiKeyStored": "API 密钥存储在您的浏览器本地",
|
"apiKeyStored": "API 密钥存储在您的浏览器本地",
|
||||||
"test": "测试",
|
"test": "测试",
|
||||||
"validationError": "验证失败",
|
"validationError": "验证失败",
|
||||||
"addModelFirst": "请先添加至少一个模型以进行验证"
|
"addModelFirst": "请先添加至少一个模型以进行验证",
|
||||||
|
"providers": "提供商",
|
||||||
|
"addProviderHint": "添加提供商即可开始使用",
|
||||||
|
"verified": "已验证",
|
||||||
|
"configuration": "配置",
|
||||||
|
"displayName": "显示名称",
|
||||||
|
"awsAccessKeyId": "AWS 访问密钥 ID",
|
||||||
|
"awsSecretAccessKey": "AWS Secret Access Key",
|
||||||
|
"awsRegion": "AWS 区域",
|
||||||
|
"selectRegion": "选择区域",
|
||||||
|
"apiKey": "API 密钥",
|
||||||
|
"enterApiKey": "输入您的 API 密钥",
|
||||||
|
"enterSecretKey": "输入您的 Secret Key",
|
||||||
|
"baseUrl": "基础 URL",
|
||||||
|
"optional": "(可选)",
|
||||||
|
"customEndpoint": "自定义端点 URL",
|
||||||
|
"models": "模型",
|
||||||
|
"customModelId": "自定义模型 ID...",
|
||||||
|
"allAdded": "已全部添加",
|
||||||
|
"suggested": "推荐",
|
||||||
|
"noModelsConfigured": "尚未配置模型",
|
||||||
|
"modelIdEmpty": "模型 ID 不能为空",
|
||||||
|
"modelIdExists": "此模型 ID 已存在",
|
||||||
|
"configureProviders": "配置 AI 提供商",
|
||||||
|
"selectProviderHint": "从列表中选择提供商或添加新的以配置 API 密钥和模型",
|
||||||
|
"deleteConfirmDesc": "确定要删除 {name} 吗?这将移除所有配置的模型且无法撤销。",
|
||||||
|
"typeToConfirm": "输入 \"{name}\" 以确认",
|
||||||
|
"typeProviderName": "输入提供商名称...",
|
||||||
|
"modelsConfiguredCount": "已配置 {count} 个模型",
|
||||||
|
"validationFailedCount": "{count} 个模型验证失败",
|
||||||
|
"cancel": "取消",
|
||||||
|
"delete": "删除",
|
||||||
|
"clickToChange": "(点击更改)",
|
||||||
|
"usingServerDefault": "使用服务器默认模型",
|
||||||
|
"selectModel": "选择模型",
|
||||||
|
"searchModels": "搜索模型...",
|
||||||
|
"noVerifiedModels": "没有已验证的模型。请先测试您的模型。",
|
||||||
|
"noModelsFound": "未找到模型。",
|
||||||
|
"default": "默认",
|
||||||
|
"serverDefault": "服务器默认",
|
||||||
|
"configureModels": "配置模型...",
|
||||||
|
"onlyVerifiedShown": "仅显示已验证的模型"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
import { useCallback, useMemo } from "react"
|
import { useCallback, useMemo } from "react"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import { QuotaLimitToast } from "@/components/quota-limit-toast"
|
import { QuotaLimitToast } from "@/components/quota-limit-toast"
|
||||||
|
import { useDictionary } from "@/hooks/use-dictionary"
|
||||||
|
import { formatMessage } from "@/lib/i18n/utils"
|
||||||
import { STORAGE_KEYS } from "@/lib/storage"
|
import { STORAGE_KEYS } from "@/lib/storage"
|
||||||
|
|
||||||
export interface QuotaConfig {
|
export interface QuotaConfig {
|
||||||
@@ -40,6 +42,8 @@ export function useQuotaManager(config: QuotaConfig): {
|
|||||||
} {
|
} {
|
||||||
const { dailyRequestLimit, dailyTokenLimit, tpmLimit } = config
|
const { dailyRequestLimit, dailyTokenLimit, tpmLimit } = config
|
||||||
|
|
||||||
|
const dict = useDictionary()
|
||||||
|
|
||||||
// Check if user has their own API key configured (bypass limits)
|
// Check if user has their own API key configured (bypass limits)
|
||||||
const hasOwnApiKey = useCallback((): boolean => {
|
const hasOwnApiKey = useCallback((): boolean => {
|
||||||
const provider = localStorage.getItem(STORAGE_KEYS.aiProvider)
|
const provider = localStorage.getItem(STORAGE_KEYS.aiProvider)
|
||||||
@@ -221,11 +225,12 @@ export function useQuotaManager(config: QuotaConfig): {
|
|||||||
const showTPMLimitToast = useCallback(() => {
|
const showTPMLimitToast = useCallback(() => {
|
||||||
const limitDisplay =
|
const limitDisplay =
|
||||||
tpmLimit >= 1000 ? `${tpmLimit / 1000}k` : String(tpmLimit)
|
tpmLimit >= 1000 ? `${tpmLimit / 1000}k` : String(tpmLimit)
|
||||||
toast.error(
|
const message = formatMessage(dict.quota.tpmMessageDetailed, {
|
||||||
`Rate limit reached (${limitDisplay} tokens/min). Please wait 60 seconds before sending another request.`,
|
limit: limitDisplay,
|
||||||
{ duration: 8000 },
|
seconds: 60,
|
||||||
)
|
})
|
||||||
}, [tpmLimit])
|
toast.error(message, { duration: 8000 })
|
||||||
|
}, [tpmLimit, dict])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// Check functions
|
// Check functions
|
||||||
|
|||||||
@@ -4,9 +4,16 @@ import packageJson from "./package.json"
|
|||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
/* config options here */
|
/* config options here */
|
||||||
output: "standalone",
|
output: "standalone",
|
||||||
|
// Support for subdirectory deployment (e.g., https://example.com/nextaidrawio)
|
||||||
|
// Set NEXT_PUBLIC_BASE_PATH environment variable to your subdirectory path (e.g., /nextaidrawio)
|
||||||
|
basePath: process.env.NEXT_PUBLIC_BASE_PATH || "",
|
||||||
env: {
|
env: {
|
||||||
APP_VERSION: packageJson.version,
|
APP_VERSION: packageJson.version,
|
||||||
},
|
},
|
||||||
|
// Include instrumentation.ts in standalone build for Langfuse telemetry
|
||||||
|
outputFileTracingIncludes: {
|
||||||
|
"*": ["./instrumentation.ts"],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default nextConfig
|
export default nextConfig
|
||||||
|
|||||||
383
package-lock.json
generated
383
package-lock.json
generated
@@ -1,29 +1,29 @@
|
|||||||
{
|
{
|
||||||
"name": "next-ai-draw-io",
|
"name": "next-ai-draw-io",
|
||||||
"version": "0.4.5",
|
"version": "0.4.6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "next-ai-draw-io",
|
"name": "next-ai-draw-io",
|
||||||
"version": "0.4.5",
|
"version": "0.4.6",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/amazon-bedrock": "^3.0.70",
|
"@ai-sdk/amazon-bedrock": "^4.0.1",
|
||||||
"@ai-sdk/anthropic": "^2.0.44",
|
"@ai-sdk/anthropic": "^3.0.0",
|
||||||
"@ai-sdk/azure": "^2.0.69",
|
"@ai-sdk/azure": "^3.0.0",
|
||||||
"@ai-sdk/deepseek": "^1.0.30",
|
"@ai-sdk/deepseek": "^2.0.0",
|
||||||
"@ai-sdk/gateway": "^2.0.21",
|
"@ai-sdk/gateway": "^3.0.0",
|
||||||
"@ai-sdk/google": "^2.0.0",
|
"@ai-sdk/google": "^3.0.0",
|
||||||
"@ai-sdk/openai": "^2.0.19",
|
"@ai-sdk/openai": "^3.0.0",
|
||||||
"@ai-sdk/react": "^2.0.107",
|
"@ai-sdk/react": "^3.0.1",
|
||||||
"@aws-sdk/credential-providers": "^3.943.0",
|
"@aws-sdk/credential-providers": "^3.943.0",
|
||||||
"@formatjs/intl-localematcher": "^0.7.2",
|
"@formatjs/intl-localematcher": "^0.7.2",
|
||||||
"@langfuse/client": "^4.4.9",
|
"@langfuse/client": "^4.4.9",
|
||||||
"@langfuse/otel": "^4.4.4",
|
"@langfuse/otel": "^4.4.4",
|
||||||
"@langfuse/tracing": "^4.4.9",
|
"@langfuse/tracing": "^4.4.9",
|
||||||
"@next/third-parties": "^16.0.6",
|
"@next/third-parties": "^16.0.6",
|
||||||
"@openrouter/ai-sdk-provider": "^1.2.3",
|
"@openrouter/ai-sdk-provider": "^1.5.4",
|
||||||
"@opentelemetry/exporter-trace-otlp-http": "^0.208.0",
|
"@opentelemetry/exporter-trace-otlp-http": "^0.208.0",
|
||||||
"@opentelemetry/sdk-trace-node": "^2.2.0",
|
"@opentelemetry/sdk-trace-node": "^2.2.0",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
"@radix-ui/react-tooltip": "^1.1.8",
|
"@radix-ui/react-tooltip": "^1.1.8",
|
||||||
"@radix-ui/react-use-controllable-state": "^1.2.2",
|
"@radix-ui/react-use-controllable-state": "^1.2.2",
|
||||||
"@xmldom/xmldom": "^0.9.8",
|
"@xmldom/xmldom": "^0.9.8",
|
||||||
"ai": "^5.0.89",
|
"ai": "^6.0.1",
|
||||||
"base-64": "^1.0.0",
|
"base-64": "^1.0.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@@ -93,14 +93,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ai-sdk/amazon-bedrock": {
|
"node_modules/@ai-sdk/amazon-bedrock": {
|
||||||
"version": "3.0.70",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/amazon-bedrock/-/amazon-bedrock-3.0.70.tgz",
|
"resolved": "https://registry.npmjs.org/@ai-sdk/amazon-bedrock/-/amazon-bedrock-4.0.1.tgz",
|
||||||
"integrity": "sha512-4NIBlwuS/iLKq2ynOqqyJ9imk/oyHuOzhBx88Bfm5I0ihQPKJ0dMMD1IKKuyDZvLRYKmlOEpa//P+/ZBp10drw==",
|
"integrity": "sha512-8Qu5wHTHYTwptZ1L4Sv8hvXMyKRNWUD6dH8wm+Zl8RDOEXZJNENS6zec7u/sWJKuvefL7j+xN8z3IVduhGfxig==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "2.0.56",
|
"@ai-sdk/anthropic": "3.0.0",
|
||||||
"@ai-sdk/provider": "2.0.0",
|
"@ai-sdk/provider": "3.0.0",
|
||||||
"@ai-sdk/provider-utils": "3.0.19",
|
"@ai-sdk/provider-utils": "4.0.0",
|
||||||
"@smithy/eventstream-codec": "^4.0.1",
|
"@smithy/eventstream-codec": "^4.0.1",
|
||||||
"@smithy/util-utf8": "^4.0.0",
|
"@smithy/util-utf8": "^4.0.0",
|
||||||
"aws4fetch": "^1.0.20"
|
"aws4fetch": "^1.0.20"
|
||||||
@@ -112,48 +112,14 @@
|
|||||||
"zod": "^3.25.76 || ^4.1.8"
|
"zod": "^3.25.76 || ^4.1.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ai-sdk/amazon-bedrock/node_modules/@ai-sdk/provider-utils": {
|
|
||||||
"version": "3.0.19",
|
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.19.tgz",
|
|
||||||
"integrity": "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@ai-sdk/provider": "2.0.0",
|
|
||||||
"@standard-schema/spec": "^1.0.0",
|
|
||||||
"eventsource-parser": "^3.0.6"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"zod": "^3.25.76 || ^4.1.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@ai-sdk/anthropic": {
|
"node_modules/@ai-sdk/anthropic": {
|
||||||
"version": "2.0.56",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-2.0.56.tgz",
|
"resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-3.0.0.tgz",
|
||||||
"integrity": "sha512-XHJKu0Yvfu9SPzRfsAFESa+9T7f2YJY6TxykKMfRsAwpeWAiX/Gbx5J5uM15AzYC3Rw8tVP3oH+j7jEivENirQ==",
|
"integrity": "sha512-4BnxkXwRkvh+OB1ze0mHbskT90HL4MNrg6JUsRDkIsU9w5vitvGzxwc/XwlByUGMap/5I8/LZ3XZDzv6KViCuQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/provider": "2.0.0",
|
"@ai-sdk/provider": "3.0.0",
|
||||||
"@ai-sdk/provider-utils": "3.0.19"
|
"@ai-sdk/provider-utils": "4.0.0"
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"zod": "^3.25.76 || ^4.1.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@ai-sdk/anthropic/node_modules/@ai-sdk/provider-utils": {
|
|
||||||
"version": "3.0.19",
|
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.19.tgz",
|
|
||||||
"integrity": "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@ai-sdk/provider": "2.0.0",
|
|
||||||
"@standard-schema/spec": "^1.0.0",
|
|
||||||
"eventsource-parser": "^3.0.6"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@@ -163,31 +129,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ai-sdk/azure": {
|
"node_modules/@ai-sdk/azure": {
|
||||||
"version": "2.0.69",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/azure/-/azure-2.0.69.tgz",
|
"resolved": "https://registry.npmjs.org/@ai-sdk/azure/-/azure-3.0.0.tgz",
|
||||||
"integrity": "sha512-0Y+f0XHviWw9ixB2Dkqyg07V67oczUh8adh4B/t0LgVMVkvOsf/WEzfYx2/LDqdvI/o8IYyJ6JzsCKpBwbS61g==",
|
"integrity": "sha512-bY1EfX4aisnpOYcuZkmkuBIUc0noNT/25ZoWispSiMZMS53yF55xkT1SFM+cxDjJ4M3HVJwVuU6r1szA8f9IPA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/openai": "2.0.67",
|
"@ai-sdk/openai": "3.0.0",
|
||||||
"@ai-sdk/provider": "2.0.0",
|
"@ai-sdk/provider": "3.0.0",
|
||||||
"@ai-sdk/provider-utils": "3.0.17"
|
"@ai-sdk/provider-utils": "4.0.0"
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"zod": "^3.25.76 || ^4.1.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@ai-sdk/azure/node_modules/@ai-sdk/provider-utils": {
|
|
||||||
"version": "3.0.17",
|
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.17.tgz",
|
|
||||||
"integrity": "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@ai-sdk/provider": "2.0.0",
|
|
||||||
"@standard-schema/spec": "^1.0.0",
|
|
||||||
"eventsource-parser": "^3.0.6"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@@ -197,14 +146,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ai-sdk/deepseek": {
|
"node_modules/@ai-sdk/deepseek": {
|
||||||
"version": "1.0.30",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/deepseek/-/deepseek-1.0.30.tgz",
|
"resolved": "https://registry.npmjs.org/@ai-sdk/deepseek/-/deepseek-2.0.0.tgz",
|
||||||
"integrity": "sha512-pafNclW9L8Z3WimaRwlpHrGbdeaDE/UklT3rMi2aoRRyrA+s7zGcFuu1zbO2ViLNlKfaS91XZa9MFAPXbIftUA==",
|
"integrity": "sha512-qRX06mouHaF4OePE4S8W8+fLX7Iq4dWk7Ul4+SSPBzx6zBJKUqwHYW2XyPShNWhT4Rcogpl5yHJUrBaOExkiyg==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/openai-compatible": "1.0.28",
|
"@ai-sdk/provider": "3.0.0",
|
||||||
"@ai-sdk/provider": "2.0.0",
|
"@ai-sdk/provider-utils": "4.0.0"
|
||||||
"@ai-sdk/provider-utils": "3.0.18"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@@ -214,13 +162,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ai-sdk/gateway": {
|
"node_modules/@ai-sdk/gateway": {
|
||||||
"version": "2.0.21",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.21.tgz",
|
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.0.tgz",
|
||||||
"integrity": "sha512-BwV7DU/lAm3Xn6iyyvZdWgVxgLu3SNXzl5y57gMvkW4nGhAOV5269IrJzQwGt03bb107sa6H6uJwWxc77zXoGA==",
|
"integrity": "sha512-JcjePYVpbezv+XOxkxPemwnorjWpgDiiKWMYy6FXTCG2rFABIK2Co1bFxIUSDT4vYO6f1448x9rKbn38vbhDiA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/provider": "2.0.0",
|
"@ai-sdk/provider": "3.0.0",
|
||||||
"@ai-sdk/provider-utils": "3.0.19",
|
"@ai-sdk/provider-utils": "4.0.0",
|
||||||
"@vercel/oidc": "3.0.5"
|
"@vercel/oidc": "3.0.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -230,15 +178,14 @@
|
|||||||
"zod": "^3.25.76 || ^4.1.8"
|
"zod": "^3.25.76 || ^4.1.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ai-sdk/gateway/node_modules/@ai-sdk/provider-utils": {
|
"node_modules/@ai-sdk/google": {
|
||||||
"version": "3.0.19",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.19.tgz",
|
"resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-3.0.0.tgz",
|
||||||
"integrity": "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA==",
|
"integrity": "sha512-KFS9pR7KGDyt7p1OQibglS3amoLjCXxwF7DVg+gL2RLcwFRQV0s6Tp7Q+PvGNFSqPdrPYW8mHyvn8ODK4WTImA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/provider": "2.0.0",
|
"@ai-sdk/provider": "3.0.0",
|
||||||
"@standard-schema/spec": "^1.0.0",
|
"@ai-sdk/provider-utils": "4.0.0"
|
||||||
"eventsource-parser": "^3.0.6"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@@ -247,90 +194,14 @@
|
|||||||
"zod": "^3.25.76 || ^4.1.8"
|
"zod": "^3.25.76 || ^4.1.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ai-sdk/google": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-2.0.0.tgz",
|
|
||||||
"integrity": "sha512-35uWKG+aWm0QClJV/kNhcyR9IVrDkZoI1UlWvUCjwoqbCxj4/L/1LKKbpM3JSRa9u74ghHzBB0UjLHdgcIoanw==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@ai-sdk/provider": "2.0.0",
|
|
||||||
"@ai-sdk/provider-utils": "3.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"zod": "^3.25.76 || ^4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@ai-sdk/google/node_modules/@ai-sdk/provider-utils": {
|
|
||||||
"version": "3.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.0.tgz",
|
|
||||||
"integrity": "sha512-BoQZtGcBxkeSH1zK+SRYNDtJPIPpacTeiMZqnG4Rv6xXjEwM0FH4MGs9c+PlhyEWmQCzjRM2HAotEydFhD4dYw==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@ai-sdk/provider": "2.0.0",
|
|
||||||
"@standard-schema/spec": "^1.0.0",
|
|
||||||
"eventsource-parser": "^3.0.3",
|
|
||||||
"zod-to-json-schema": "^3.24.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"zod": "^3.25.76 || ^4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@ai-sdk/google/node_modules/@ai-sdk/provider-utils/node_modules/zod-to-json-schema": {
|
|
||||||
"version": "3.24.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz",
|
|
||||||
"integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==",
|
|
||||||
"license": "ISC",
|
|
||||||
"peerDependencies": {
|
|
||||||
"zod": "^3.24.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@ai-sdk/openai": {
|
"node_modules/@ai-sdk/openai": {
|
||||||
"version": "2.0.67",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-2.0.67.tgz",
|
"resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-3.0.0.tgz",
|
||||||
"integrity": "sha512-JhB3fUpY+IxAocyJt2PHuhfNwH+e+rDbZ8Q+d0hgSyNycuPRrV0xutLaf7mgDTvjr5FCrVEkXmM73tJprzZMiA==",
|
"integrity": "sha512-/o2xCQlRA+O0cAXIIBOfMeT35H6Fonzilz9r/IJojPOMQnmIL+0jPQVKOUPr5bouRqCjnwKpwuKEBRqm8jUZkQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/provider": "2.0.0",
|
"@ai-sdk/provider": "3.0.0",
|
||||||
"@ai-sdk/provider-utils": "3.0.17"
|
"@ai-sdk/provider-utils": "4.0.0"
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"zod": "^3.25.76 || ^4.1.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@ai-sdk/openai-compatible": {
|
|
||||||
"version": "1.0.28",
|
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-1.0.28.tgz",
|
|
||||||
"integrity": "sha512-yKubDxLYtXyGUzkr9lNStf/lE/I+Okc8tmotvyABhsQHHieLKk6oV5fJeRJxhr67Ejhg+FRnwUOxAmjRoFM4dA==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@ai-sdk/provider": "2.0.0",
|
|
||||||
"@ai-sdk/provider-utils": "3.0.18"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"zod": "^3.25.76 || ^4.1.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@ai-sdk/openai/node_modules/@ai-sdk/provider-utils": {
|
|
||||||
"version": "3.0.17",
|
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.17.tgz",
|
|
||||||
"integrity": "sha512-TR3Gs4I3Tym4Ll+EPdzRdvo/rc8Js6c4nVhFLuvGLX/Y4V9ZcQMa/HTiYsHEgmYrf1zVi6Q145UEZUfleOwOjw==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@ai-sdk/provider": "2.0.0",
|
|
||||||
"@standard-schema/spec": "^1.0.0",
|
|
||||||
"eventsource-parser": "^3.0.6"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@@ -340,9 +211,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ai-sdk/provider": {
|
"node_modules/@ai-sdk/provider": {
|
||||||
"version": "2.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.0.tgz",
|
||||||
"integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==",
|
"integrity": "sha512-m9ka3ptkPQbaHHZHqDXDF9C9B5/Mav0KTdky1k2HZ3/nrW2t1AgObxIVPyGDWQNS9FXT/FS6PIoSjpcP/No8rQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"json-schema": "^0.4.0"
|
"json-schema": "^0.4.0"
|
||||||
@@ -352,13 +223,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ai-sdk/provider-utils": {
|
"node_modules/@ai-sdk/provider-utils": {
|
||||||
"version": "3.0.18",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.18.tgz",
|
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.0.tgz",
|
||||||
"integrity": "sha512-ypv1xXMsgGcNKUP+hglKqtdDuMg68nWHucPPAhIENrbFAI+xCHiqPVN8Zllxyv1TNZwGWUghPxJXU+Mqps0YRQ==",
|
"integrity": "sha512-HyCyOls9I3a3e38+gtvOJOEjuw9KRcvbBnCL5GBuSmJvS9Jh9v3fz7pRC6ha1EUo/ZH1zwvLWYXBMtic8MTguA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/provider": "2.0.0",
|
"@ai-sdk/provider": "3.0.0",
|
||||||
"@standard-schema/spec": "^1.0.0",
|
"@standard-schema/spec": "^1.1.0",
|
||||||
"eventsource-parser": "^3.0.6"
|
"eventsource-parser": "^3.0.6"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -369,13 +240,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ai-sdk/react": {
|
"node_modules/@ai-sdk/react": {
|
||||||
"version": "2.0.107",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.107.tgz",
|
"resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-3.0.1.tgz",
|
||||||
"integrity": "sha512-rv0u+tAi2r2zJu2uSLXcC3TBgGrkQIWXRM+i6us6qcGmYQ2kOu2VYg+lxviOSGPhL9PVebvTlN5x8mf3rDqX+w==",
|
"integrity": "sha512-XUPDMFgalNtqBQg+Q3UiiEmWE3PC5pAoc+Drs5Z1Mxqe57za+hKCEwViYADuqeZrc0q6PXTzbcFlQb3pjyGjcQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/provider-utils": "3.0.18",
|
"@ai-sdk/provider-utils": "4.0.0",
|
||||||
"ai": "5.0.107",
|
"ai": "6.0.1",
|
||||||
"swr": "^2.2.5",
|
"swr": "^2.2.5",
|
||||||
"throttleit": "2.1.0"
|
"throttleit": "2.1.0"
|
||||||
},
|
},
|
||||||
@@ -383,13 +254,7 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^18 || ^19 || ^19.0.0-rc",
|
"react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1"
|
||||||
"zod": "^3.25.76 || ^4.1.8"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"zod": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@alloc/quick-lru": {
|
"node_modules/@alloc/quick-lru": {
|
||||||
@@ -2182,7 +2047,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@electron/windows-sign": {
|
"node_modules/@electron/windows-sign": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmmirror.com/@electron/windows-sign/-/windows-sign-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz",
|
||||||
"integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==",
|
"integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
@@ -2204,7 +2069,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@electron/windows-sign/node_modules/fs-extra": {
|
"node_modules/@electron/windows-sign/node_modules/fs-extra": {
|
||||||
"version": "11.3.3",
|
"version": "11.3.3",
|
||||||
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz",
|
||||||
"integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==",
|
"integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -2221,7 +2086,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@electron/windows-sign/node_modules/jsonfile": {
|
"node_modules/@electron/windows-sign/node_modules/jsonfile": {
|
||||||
"version": "6.2.0",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz",
|
||||||
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
|
"integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -2236,7 +2101,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@electron/windows-sign/node_modules/universalify": {
|
"node_modules/@electron/windows-sign/node_modules/universalify": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -4020,12 +3885,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@openrouter/ai-sdk-provider": {
|
"node_modules/@openrouter/ai-sdk-provider": {
|
||||||
"version": "1.2.3",
|
"version": "1.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/@openrouter/ai-sdk-provider/-/ai-sdk-provider-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@openrouter/ai-sdk-provider/-/ai-sdk-provider-1.5.4.tgz",
|
||||||
"integrity": "sha512-a6Nc8dPRHakRH9966YJ/HZJhLOds7DuPTscNZDoAr+Aw+tEFUlacSJMvb/b3gukn74mgbuaJRji9YOn62ipfVg==",
|
"integrity": "sha512-xrSQPUIH8n9zuyYZR0XK7Ba0h2KsjJcMkxnwaYfmv13pKs3sDkjPzVPPhlhzqBGddHb5cFEwJ9VFuFeDcxCDSw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@openrouter/sdk": "^0.1.8"
|
"@openrouter/sdk": "^0.1.27"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@@ -4036,28 +3901,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@openrouter/sdk": {
|
"node_modules/@openrouter/sdk": {
|
||||||
"version": "0.1.11",
|
"version": "0.1.27",
|
||||||
"resolved": "https://registry.npmjs.org/@openrouter/sdk/-/sdk-0.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/@openrouter/sdk/-/sdk-0.1.27.tgz",
|
||||||
"integrity": "sha512-OuPc8qqidL/PUM8+9WgrOfSR9+b6rKIWiezGcUJ54iPTdh+Gye5Qjut6hrLWlOCMZE7Z853gN90r1ft4iChj7Q==",
|
"integrity": "sha512-RH//L10bSmc81q25zAZudiI4kNkLgxF2E+WU42vghp3N6TEvZ6F0jK7uT3tOxkEn91gzmMw9YVmDENy7SJsajQ==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"zod": "^3.25.0 || ^4.0.0"
|
"zod": "^3.25.0 || ^4.0.0"
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@tanstack/react-query": "^5",
|
|
||||||
"react": "^18 || ^19",
|
|
||||||
"react-dom": "^18 || ^19"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@tanstack/react-query": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"react": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"react-dom": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@opentelemetry/api": {
|
"node_modules/@opentelemetry/api": {
|
||||||
@@ -7168,9 +7017,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@standard-schema/spec": {
|
"node_modules/@standard-schema/spec": {
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
||||||
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
|
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@swc/helpers": {
|
"node_modules/@swc/helpers": {
|
||||||
@@ -8287,14 +8136,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ai": {
|
"node_modules/ai": {
|
||||||
"version": "5.0.107",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ai/-/ai-5.0.107.tgz",
|
"resolved": "https://registry.npmjs.org/ai/-/ai-6.0.1.tgz",
|
||||||
"integrity": "sha512-laZlS9ZC/DZfSaxPgrBqI4mM+kxRvTPBBQfa74ceBFskkunZKEsaGVFNEs4cfyGa3nCCCl1WO/fjxixp4V8Zag==",
|
"integrity": "sha512-g/jPakC6h4vUJKDww0d6+VaJmfMC38UqH3kKsngiP+coT0uvCUdQ7lpFDJ0mNmamaOyRMaY2zwEB2RnTAaJU/w==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/gateway": "2.0.18",
|
"@ai-sdk/gateway": "3.0.0",
|
||||||
"@ai-sdk/provider": "2.0.0",
|
"@ai-sdk/provider": "3.0.0",
|
||||||
"@ai-sdk/provider-utils": "3.0.18",
|
"@ai-sdk/provider-utils": "4.0.0",
|
||||||
"@opentelemetry/api": "1.9.0"
|
"@opentelemetry/api": "1.9.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -8304,23 +8153,6 @@
|
|||||||
"zod": "^3.25.76 || ^4.1.8"
|
"zod": "^3.25.76 || ^4.1.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ai/node_modules/@ai-sdk/gateway": {
|
|
||||||
"version": "2.0.18",
|
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.18.tgz",
|
|
||||||
"integrity": "sha512-sDQcW+6ck2m0pTIHW6BPHD7S125WD3qNkx/B8sEzJp/hurocmJ5Cni0ybExg6sQMGo+fr/GWOwpHF1cmCdg5rQ==",
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@ai-sdk/provider": "2.0.0",
|
|
||||||
"@ai-sdk/provider-utils": "3.0.18",
|
|
||||||
"@vercel/oidc": "3.0.5"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"zod": "^3.25.76 || ^4.1.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "6.12.6",
|
"version": "6.12.6",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
@@ -9791,7 +9623,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/cross-dirname": {
|
"node_modules/cross-dirname": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmmirror.com/cross-dirname/-/cross-dirname-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz",
|
||||||
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
|
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -10343,7 +10175,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/electron-builder-squirrel-windows": {
|
"node_modules/electron-builder-squirrel-windows": {
|
||||||
"version": "26.0.12",
|
"version": "26.0.12",
|
||||||
"resolved": "https://registry.npmmirror.com/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.0.12.tgz",
|
"resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.0.12.tgz",
|
||||||
"integrity": "sha512-kpwXM7c/ayRUbYVErQbsZ0nQZX4aLHQrPEG9C4h9vuJCXylwFH8a7Jgi2VpKIObzCXO7LKHiCw4KdioFLFOgqA==",
|
"integrity": "sha512-kpwXM7c/ayRUbYVErQbsZ0nQZX4aLHQrPEG9C4h9vuJCXylwFH8a7Jgi2VpKIObzCXO7LKHiCw4KdioFLFOgqA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -10456,7 +10288,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/electron-winstaller": {
|
"node_modules/electron-winstaller": {
|
||||||
"version": "5.4.0",
|
"version": "5.4.0",
|
||||||
"resolved": "https://registry.npmmirror.com/electron-winstaller/-/electron-winstaller-5.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz",
|
||||||
"integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==",
|
"integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
@@ -10478,7 +10310,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/electron-winstaller/node_modules/fs-extra": {
|
"node_modules/electron-winstaller/node_modules/fs-extra": {
|
||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/fs-extra/-/fs-extra-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
|
||||||
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -15552,6 +15384,35 @@
|
|||||||
"zod": "^4.0.16"
|
"zod": "^4.0.16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ollama-ai-provider-v2/node_modules/@ai-sdk/provider": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"json-schema": "^0.4.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ollama-ai-provider-v2/node_modules/@ai-sdk/provider-utils": {
|
||||||
|
"version": "3.0.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.19.tgz",
|
||||||
|
"integrity": "sha512-W41Wc9/jbUVXVwCN/7bWa4IKe8MtxO3EyA0Hfhx6grnmiYlCvpI8neSYWFE0zScXJkgA/YK3BRybzgyiXuu6JA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@ai-sdk/provider": "2.0.0",
|
||||||
|
"@standard-schema/spec": "^1.0.0",
|
||||||
|
"eventsource-parser": "^3.0.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^3.25.76 || ^4.1.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/once": {
|
"node_modules/once": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
|
||||||
@@ -16040,7 +15901,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/postject": {
|
"node_modules/postject": {
|
||||||
"version": "1.0.0-alpha.6",
|
"version": "1.0.0-alpha.6",
|
||||||
"resolved": "https://registry.npmmirror.com/postject/-/postject-1.0.0-alpha.6.tgz",
|
"resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz",
|
||||||
"integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==",
|
"integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -16058,7 +15919,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/postject/node_modules/commander": {
|
"node_modules/postject/node_modules/commander": {
|
||||||
"version": "9.5.0",
|
"version": "9.5.0",
|
||||||
"resolved": "https://registry.npmmirror.com/commander/-/commander-9.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz",
|
||||||
"integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
|
"integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -17872,7 +17733,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/temp": {
|
"node_modules/temp": {
|
||||||
"version": "0.9.4",
|
"version": "0.9.4",
|
||||||
"resolved": "https://registry.npmmirror.com/temp/-/temp-0.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz",
|
||||||
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
|
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -17936,7 +17797,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/temp/node_modules/mkdirp": {
|
"node_modules/temp/node_modules/mkdirp": {
|
||||||
"version": "0.5.6",
|
"version": "0.5.6",
|
||||||
"resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -17950,7 +17811,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/temp/node_modules/rimraf": {
|
"node_modules/temp/node_modules/rimraf": {
|
||||||
"version": "2.6.3",
|
"version": "2.6.3",
|
||||||
"resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-2.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
|
||||||
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
|
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
|
||||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
|||||||
27
package.json
27
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "next-ai-draw-io",
|
"name": "next-ai-draw-io",
|
||||||
"version": "0.4.5",
|
"version": "0.4.6",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "dist-electron/main/index.js",
|
"main": "dist-electron/main/index.js",
|
||||||
@@ -24,21 +24,21 @@
|
|||||||
"dist:all": "npm run electron:build && npm run electron:prepare && npx electron-builder --mac --win --linux"
|
"dist:all": "npm run electron:build && npm run electron:prepare && npx electron-builder --mac --win --linux"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/amazon-bedrock": "^3.0.70",
|
"@ai-sdk/amazon-bedrock": "^4.0.1",
|
||||||
"@ai-sdk/anthropic": "^2.0.44",
|
"@ai-sdk/anthropic": "^3.0.0",
|
||||||
"@ai-sdk/azure": "^2.0.69",
|
"@ai-sdk/azure": "^3.0.0",
|
||||||
"@ai-sdk/deepseek": "^1.0.30",
|
"@ai-sdk/deepseek": "^2.0.0",
|
||||||
"@ai-sdk/gateway": "^2.0.21",
|
"@ai-sdk/gateway": "^3.0.0",
|
||||||
"@ai-sdk/google": "^2.0.0",
|
"@ai-sdk/google": "^3.0.0",
|
||||||
"@ai-sdk/openai": "^2.0.19",
|
"@ai-sdk/openai": "^3.0.0",
|
||||||
"@ai-sdk/react": "^2.0.107",
|
"@ai-sdk/react": "^3.0.1",
|
||||||
"@aws-sdk/credential-providers": "^3.943.0",
|
"@aws-sdk/credential-providers": "^3.943.0",
|
||||||
"@formatjs/intl-localematcher": "^0.7.2",
|
"@formatjs/intl-localematcher": "^0.7.2",
|
||||||
"@langfuse/client": "^4.4.9",
|
"@langfuse/client": "^4.4.9",
|
||||||
"@langfuse/otel": "^4.4.4",
|
"@langfuse/otel": "^4.4.4",
|
||||||
"@langfuse/tracing": "^4.4.9",
|
"@langfuse/tracing": "^4.4.9",
|
||||||
"@next/third-parties": "^16.0.6",
|
"@next/third-parties": "^16.0.6",
|
||||||
"@openrouter/ai-sdk-provider": "^1.2.3",
|
"@openrouter/ai-sdk-provider": "^1.5.4",
|
||||||
"@opentelemetry/exporter-trace-otlp-http": "^0.208.0",
|
"@opentelemetry/exporter-trace-otlp-http": "^0.208.0",
|
||||||
"@opentelemetry/sdk-trace-node": "^2.2.0",
|
"@opentelemetry/sdk-trace-node": "^2.2.0",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
"@radix-ui/react-tooltip": "^1.1.8",
|
"@radix-ui/react-tooltip": "^1.1.8",
|
||||||
"@radix-ui/react-use-controllable-state": "^1.2.2",
|
"@radix-ui/react-use-controllable-state": "^1.2.2",
|
||||||
"@xmldom/xmldom": "^0.9.8",
|
"@xmldom/xmldom": "^0.9.8",
|
||||||
"ai": "^5.0.89",
|
"ai": "^6.0.1",
|
||||||
"base-64": "^1.0.0",
|
"base-64": "^1.0.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@@ -111,5 +111,10 @@
|
|||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"typescript": "^5",
|
"typescript": "^5",
|
||||||
"wait-on": "^9.0.3"
|
"wait-on": "^9.0.3"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"@openrouter/ai-sdk-provider": {
|
||||||
|
"ai": "^6.0.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user