Merge branch 'DayuanJiang:main' into feat/add-stop-generation-button

This commit is contained in:
broBinChen
2026-01-30 09:34:45 +08:00
committed by GitHub
14 changed files with 285 additions and 325 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
import type { Metadata } from "next"
import Image from "next/image"
import Link from "next/link"
import { FaGithub } from "react-icons/fa"
import Image from "@/components/image-with-basepath"
export const metadata: Metadata = {
title: "关于 - Next AI Draw.io",
+1 -1
View File
@@ -1,7 +1,7 @@
import type { Metadata } from "next"
import Image from "next/image"
import Link from "next/link"
import { FaGithub } from "react-icons/fa"
import Image from "@/components/image-with-basepath"
export const metadata: Metadata = {
title: "概要 - Next AI Draw.io",
+1 -1
View File
@@ -1,7 +1,7 @@
import type { Metadata } from "next"
import Image from "next/image"
import Link from "next/link"
import { FaGithub } from "react-icons/fa"
import Image from "@/components/image-with-basepath"
export const metadata: Metadata = {
title: "About - Next AI Draw.io",
+1 -1
View File
@@ -16,7 +16,6 @@ import {
ThumbsUp,
X,
} from "lucide-react"
import Image from "next/image"
import type { MutableRefObject } from "react"
import { useCallback, useEffect, useRef, useState } from "react"
import ReactMarkdown from "react-markdown"
@@ -31,6 +30,7 @@ import { ToolCallCard } from "@/components/chat/ToolCallCard"
import type { DiagramOperation, ToolPartLike } from "@/components/chat/types"
import type { ValidationState } from "@/components/chat/ValidationCard"
import { ValidationCard } from "@/components/chat/ValidationCard"
import Image from "@/components/image-with-basepath"
import { ScrollArea } from "@/components/ui/scroll-area"
import { useDictionary } from "@/hooks/use-dictionary"
import { getApiEndpoint } from "@/lib/base-path"
+7 -5
View File
@@ -8,8 +8,7 @@ import {
PanelRightOpen,
Settings,
} from "lucide-react"
import Image from "next/image"
import { useRouter, useSearchParams } from "next/navigation"
import { usePathname, useRouter, useSearchParams } from "next/navigation"
import type React from "react"
import {
useCallback,
@@ -22,6 +21,7 @@ import { flushSync } from "react-dom"
import { Toaster, toast } from "sonner"
import { ButtonWithTooltip } from "@/components/button-with-tooltip"
import { ChatInput } from "@/components/chat-input"
import Image from "@/components/image-with-basepath"
import { ModelConfigDialog } from "@/components/model-config-dialog"
import { SettingsDialog } from "@/components/settings-dialog"
import { useDiagram } from "@/contexts/diagram-context"
@@ -131,6 +131,7 @@ export default function ChatPanel({
const dict = useDictionary()
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
const urlSessionId = searchParams.get("session")
@@ -905,10 +906,10 @@ export default function ChatPanel({
if (result.wasCurrentSession) {
// Deleted current session - clear UI and URL
syncUIWithSession(null)
router.replace(window.location.pathname, { scroll: false })
router.replace(pathname, { scroll: false })
}
},
[sessionManager, syncUIWithSession, router],
[sessionManager, syncUIWithSession, router, pathname],
)
const handleNewChat = useCallback(async () => {
@@ -941,7 +942,7 @@ export default function ChatPanel({
toast.success(dict.dialogs.clearSuccess)
// Clear URL param to show blank state
router.replace(window.location.pathname, { scroll: false })
router.replace(pathname, { scroll: false })
// After starting a fresh chat, move focus back to the chat input
setShouldFocusInput(true)
@@ -956,6 +957,7 @@ export default function ChatPanel({
dict.dialogs.clearSuccess,
buildSessionData,
setDiagramHistory,
pathname,
])
const handleInputChange = (
+1 -1
View File
@@ -8,9 +8,9 @@ import {
Trash2,
X,
} from "lucide-react"
import Image from "next/image"
import { useState } from "react"
import ExamplePanel from "@/components/chat-example-panel"
import Image from "@/components/image-with-basepath"
import {
AlertDialog,
AlertDialogAction,
+1 -1
View File
@@ -10,8 +10,8 @@ import {
RefreshCw,
X,
} from "lucide-react"
import Image from "next/image"
import { useState } from "react"
import Image from "@/components/image-with-basepath"
import { useDictionary } from "@/hooks/use-dictionary"
import type { ValidationResult } from "@/lib/diagram-validator"
+1 -1
View File
@@ -1,8 +1,8 @@
"use client"
import { FileCode, FileText, Link, Loader2, X } from "lucide-react"
import Image from "next/image"
import { useEffect, useRef, useState } from "react"
import Image from "@/components/image-with-basepath"
import { useDictionary } from "@/hooks/use-dictionary"
import { isPdfFile, isTextFile } from "@/lib/pdf-utils"
+1 -1
View File
@@ -1,7 +1,7 @@
"use client"
import Image from "next/image"
import { useState } from "react"
import Image from "@/components/image-with-basepath"
import { Button } from "@/components/ui/button"
import {
Dialog,
+16
View File
@@ -0,0 +1,16 @@
import NextImage, { type ImageProps } from "next/image"
import { forwardRef } from "react"
import { getAssetUrl } from "@/lib/base-path"
export default forwardRef<HTMLImageElement, ImageProps>(
function Image(props, ref) {
const src =
typeof props.src === "string" &&
props.src.startsWith("/") &&
!props.src.startsWith("//")
? getAssetUrl(props.src)
: props.src
return <NextImage {...props} src={src} ref={ref} />
},
)
+2 -1
View File
@@ -1,6 +1,7 @@
"use client"
import { useCallback, useEffect, useState } from "react"
import { getApiEndpoint } from "@/lib/base-path"
import type { FlattenedServerModel } from "@/lib/server-model-config"
import { STORAGE_KEYS } from "@/lib/storage"
import {
@@ -147,7 +148,7 @@ export function useModelConfig(): UseModelConfigReturn {
useEffect(() => {
if (typeof window === "undefined") return
fetch("/api/server-models")
fetch(getApiEndpoint("/api/server-models"))
.then((res) => {
if (!res.ok) {
console.error(
+10 -8
View File
@@ -39,7 +39,7 @@ export interface ClientOverrides {
baseUrlEnv?: string
}
// Providers that can be used with client-provided API keys
// Providers that can be selected from client settings
const ALLOWED_CLIENT_PROVIDERS: ProviderName[] = [
"openai",
"anthropic",
@@ -53,6 +53,7 @@ const ALLOWED_CLIENT_PROVIDERS: ProviderName[] = [
"sglang",
"gateway",
"edgeone",
"ollama",
"doubao",
"modelscope",
]
@@ -572,12 +573,13 @@ export function getAIModel(overrides?: ClientOverrides): ModelConfig {
// SECURITY: Prevent SSRF attacks (GHSA-9qf7-mprq-9qgm)
// If a custom baseUrl is provided, an API key MUST also be provided.
// This prevents attackers from redirecting server API keys to malicious endpoints.
// Exception: EdgeOne provider doesn't require API key (uses Edge AI runtime)
// Exception: EdgeOne and Ollama providers don't require API keys
if (
overrides?.baseUrl &&
!overrides?.apiKey &&
!(overrides?.provider === "vertexai" && overrides?.vertexApiKey) &&
overrides?.provider !== "edgeone"
overrides?.provider !== "edgeone" &&
overrides?.provider !== "ollama"
) {
throw new Error(
`API key is required when using a custom base URL. ` +
@@ -836,16 +838,16 @@ export function getAIModel(overrides?: ClientOverrides): ModelConfig {
break
}
case "ollama":
if (process.env.OLLAMA_BASE_URL) {
const customOllama = createOllama({
baseURL: process.env.OLLAMA_BASE_URL,
})
case "ollama": {
const baseURL = overrides?.baseUrl || process.env.OLLAMA_BASE_URL
if (baseURL) {
const customOllama = createOllama({ baseURL })
model = customOllama(modelId)
} else {
model = ollama(modelId)
}
break
}
case "openrouter": {
const apiKey = resolveApiKey(overrides, "OPENROUTER_API_KEY")
+2 -1
View File
@@ -1,4 +1,5 @@
import { z } from "zod"
import { getApiEndpoint } from "@/lib/base-path"
export interface UrlData {
url: string
@@ -15,7 +16,7 @@ const UrlResponseSchema = z.object({
})
export async function extractUrlContent(url: string): Promise<UrlData> {
const response = await fetch("/api/parse-url", {
const response = await fetch(getApiEndpoint("/api/parse-url"), {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url }),
+240 -302
View File
File diff suppressed because it is too large Load Diff