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