mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-01-02 22:32:27 +08:00
Compare commits
5 Commits
fix/deepse
...
docs/i18n-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5df0050334 | ||
|
|
f63ed75465 | ||
|
|
dcd1ee9a2e | ||
|
|
7b18bcaf68 | ||
|
|
454b86d898 |
@@ -72,6 +72,28 @@ export default function AboutCN() {
|
||||
<p className="text-xl text-gray-600 font-medium">
|
||||
AI驱动的图表创建工具 - 对话、绘制、可视化
|
||||
</p>
|
||||
<div className="flex justify-center gap-4 mt-4 text-sm">
|
||||
<Link
|
||||
href="/about"
|
||||
className="text-gray-600 hover:text-blue-600"
|
||||
>
|
||||
English
|
||||
</Link>
|
||||
<span className="text-gray-400">|</span>
|
||||
<Link
|
||||
href="/about/cn"
|
||||
className="text-blue-600 font-semibold"
|
||||
>
|
||||
中文
|
||||
</Link>
|
||||
<span className="text-gray-400">|</span>
|
||||
<Link
|
||||
href="/about/ja"
|
||||
className="text-gray-600 hover:text-blue-600"
|
||||
>
|
||||
日本語
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative mb-8 rounded-2xl bg-gradient-to-br from-amber-50 via-orange-50 to-yellow-50 p-[1px] shadow-lg">
|
||||
|
||||
@@ -80,6 +80,28 @@ export default function AboutJA() {
|
||||
AI搭載のダイアグラム作成ツール -
|
||||
チャット、描画、可視化
|
||||
</p>
|
||||
<div className="flex justify-center gap-4 mt-4 text-sm">
|
||||
<Link
|
||||
href="/about"
|
||||
className="text-gray-600 hover:text-blue-600"
|
||||
>
|
||||
English
|
||||
</Link>
|
||||
<span className="text-gray-400">|</span>
|
||||
<Link
|
||||
href="/about/cn"
|
||||
className="text-gray-600 hover:text-blue-600"
|
||||
>
|
||||
中文
|
||||
</Link>
|
||||
<span className="text-gray-400">|</span>
|
||||
<Link
|
||||
href="/about/ja"
|
||||
className="text-blue-600 font-semibold"
|
||||
>
|
||||
日本語
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative mb-8 rounded-2xl bg-gradient-to-br from-amber-50 via-orange-50 to-yellow-50 p-[1px] shadow-lg">
|
||||
|
||||
@@ -80,6 +80,28 @@ export default function About() {
|
||||
AI-Powered Diagram Creation Tool - Chat, Draw,
|
||||
Visualize
|
||||
</p>
|
||||
<div className="flex justify-center gap-4 mt-4 text-sm">
|
||||
<Link
|
||||
href="/about"
|
||||
className="text-blue-600 font-semibold"
|
||||
>
|
||||
English
|
||||
</Link>
|
||||
<span className="text-gray-400">|</span>
|
||||
<Link
|
||||
href="/about/cn"
|
||||
className="text-gray-600 hover:text-blue-600"
|
||||
>
|
||||
中文
|
||||
</Link>
|
||||
<span className="text-gray-400">|</span>
|
||||
<Link
|
||||
href="/about/ja"
|
||||
className="text-gray-600 hover:text-blue-600"
|
||||
>
|
||||
日本語
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative mb-8 rounded-2xl bg-gradient-to-br from-amber-50 via-orange-50 to-yellow-50 p-[1px] shadow-lg">
|
||||
|
||||
@@ -230,12 +230,6 @@ export function ChatMessageDisplay({
|
||||
const [expandedTools, setExpandedTools] = useState<Record<string, boolean>>(
|
||||
{},
|
||||
)
|
||||
const [copiedToolCallId, setCopiedToolCallId] = useState<string | null>(
|
||||
null,
|
||||
)
|
||||
const [copyFailedToolCallId, setCopyFailedToolCallId] = useState<
|
||||
string | null
|
||||
>(null)
|
||||
const [copiedMessageId, setCopiedMessageId] = useState<string | null>(null)
|
||||
const [copyFailedMessageId, setCopyFailedMessageId] = useState<
|
||||
string | null
|
||||
@@ -251,38 +245,12 @@ export function ChatMessageDisplay({
|
||||
Record<string, boolean>
|
||||
>({})
|
||||
|
||||
const setCopyState = (
|
||||
messageId: string,
|
||||
isToolCall: boolean,
|
||||
isSuccess: boolean,
|
||||
) => {
|
||||
if (isSuccess) {
|
||||
if (isToolCall) {
|
||||
setCopiedToolCallId(messageId)
|
||||
setTimeout(() => setCopiedToolCallId(null), 2000)
|
||||
} else {
|
||||
setCopiedMessageId(messageId)
|
||||
setTimeout(() => setCopiedMessageId(null), 2000)
|
||||
}
|
||||
} else {
|
||||
if (isToolCall) {
|
||||
setCopyFailedToolCallId(messageId)
|
||||
setTimeout(() => setCopyFailedToolCallId(null), 2000)
|
||||
} else {
|
||||
setCopyFailedMessageId(messageId)
|
||||
setTimeout(() => setCopyFailedMessageId(null), 2000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const copyMessageToClipboard = async (
|
||||
messageId: string,
|
||||
text: string,
|
||||
isToolCall = false,
|
||||
) => {
|
||||
const copyMessageToClipboard = async (messageId: string, text: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
setCopyState(messageId, isToolCall, true)
|
||||
|
||||
setCopiedMessageId(messageId)
|
||||
setTimeout(() => setCopiedMessageId(null), 2000)
|
||||
} catch (err) {
|
||||
// Fallback for non-secure contexts (HTTP) or permission denied
|
||||
const textarea = document.createElement("textarea")
|
||||
@@ -298,11 +266,13 @@ export function ChatMessageDisplay({
|
||||
if (!success) {
|
||||
throw new Error("Copy command failed")
|
||||
}
|
||||
setCopyState(messageId, isToolCall, true)
|
||||
setCopiedMessageId(messageId)
|
||||
setTimeout(() => setCopiedMessageId(null), 2000)
|
||||
} catch (fallbackErr) {
|
||||
console.error("Failed to copy message:", fallbackErr)
|
||||
toast.error(dict.chat.failedToCopyDetail)
|
||||
setCopyState(messageId, isToolCall, false)
|
||||
setCopyFailedMessageId(messageId)
|
||||
setTimeout(() => setCopyFailedMessageId(null), 2000)
|
||||
} finally {
|
||||
document.body.removeChild(textarea)
|
||||
}
|
||||
@@ -671,7 +641,6 @@ export function ChatMessageDisplay({
|
||||
const { state, input, output } = part
|
||||
const isExpanded = expandedTools[callId] ?? true
|
||||
const toolName = part.type?.replace("tool-", "")
|
||||
const isCopied = copiedToolCallId === callId
|
||||
|
||||
const toggleExpanded = () => {
|
||||
setExpandedTools((prev) => ({
|
||||
@@ -693,35 +662,6 @@ export function ChatMessageDisplay({
|
||||
}
|
||||
}
|
||||
|
||||
const handleCopy = () => {
|
||||
let textToCopy = ""
|
||||
|
||||
if (input && typeof input === "object") {
|
||||
if (input.xml) {
|
||||
textToCopy = input.xml
|
||||
} else if (
|
||||
input.operations &&
|
||||
Array.isArray(input.operations)
|
||||
) {
|
||||
textToCopy = JSON.stringify(input.operations, null, 2)
|
||||
} else if (Object.keys(input).length > 0) {
|
||||
textToCopy = JSON.stringify(input, null, 2)
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
output &&
|
||||
toolName === "get_shape_library" &&
|
||||
typeof output === "string"
|
||||
) {
|
||||
textToCopy = output
|
||||
}
|
||||
|
||||
if (textToCopy) {
|
||||
copyMessageToClipboard(callId, textToCopy, true)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={callId}
|
||||
@@ -741,32 +681,9 @@ export function ChatMessageDisplay({
|
||||
<div className="h-4 w-4 border-2 border-primary border-t-transparent rounded-full animate-spin" />
|
||||
)}
|
||||
{state === "output-available" && (
|
||||
<>
|
||||
<span className="text-xs font-medium text-green-600 bg-green-50 px-2 py-0.5 rounded-full">
|
||||
{dict.tools.complete}
|
||||
</span>
|
||||
{isExpanded && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleCopy}
|
||||
className="p-1 rounded hover:bg-muted transition-colors"
|
||||
title={
|
||||
copiedToolCallId === callId
|
||||
? dict.chat.copied
|
||||
: copyFailedToolCallId ===
|
||||
callId
|
||||
? dict.chat.failedToCopy
|
||||
: dict.chat.copyResponse
|
||||
}
|
||||
>
|
||||
{isCopied ? (
|
||||
<Check className="w-4 h-4 text-green-600" />
|
||||
) : (
|
||||
<Copy className="w-4 h-4 text-muted-foreground" />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
<span className="text-xs font-medium text-green-600 bg-green-50 px-2 py-0.5 rounded-full">
|
||||
Complete
|
||||
</span>
|
||||
)}
|
||||
{state === "output-error" &&
|
||||
(() => {
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
import { useChat } from "@ai-sdk/react"
|
||||
import { DefaultChatTransport } from "ai"
|
||||
import {
|
||||
AlertTriangle,
|
||||
MessageSquarePlus,
|
||||
PanelRightClose,
|
||||
PanelRightOpen,
|
||||
Settings,
|
||||
} from "lucide-react"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import type React from "react"
|
||||
import { useCallback, useEffect, useRef, useState } from "react"
|
||||
import { flushSync } from "react-dom"
|
||||
@@ -335,10 +337,7 @@ export default function ChatPanel({
|
||||
}
|
||||
|
||||
// Translate image not supported error
|
||||
if (
|
||||
friendlyMessage.includes("image content block") ||
|
||||
friendlyMessage.toLowerCase().includes("image_url")
|
||||
) {
|
||||
if (friendlyMessage.includes("image content block")) {
|
||||
friendlyMessage = "This model doesn't support image input."
|
||||
}
|
||||
|
||||
@@ -953,6 +952,32 @@ export default function ChatPanel({
|
||||
Next AI Drawio
|
||||
</h1>
|
||||
</div>
|
||||
{!isMobile && (
|
||||
<Link
|
||||
href="/about"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm text-muted-foreground hover:text-foreground transition-colors ml-2"
|
||||
>
|
||||
{dict.nav.about}
|
||||
</Link>
|
||||
)}
|
||||
{!isMobile && (
|
||||
<Link
|
||||
href="/about"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<ButtonWithTooltip
|
||||
tooltipContent={dict.nav.sponsorTooltip}
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-6 w-6 text-amber-500 hover:text-amber-600"
|
||||
>
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
</ButtonWithTooltip>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-1 justify-end overflow-visible">
|
||||
<ButtonWithTooltip
|
||||
|
||||
@@ -395,13 +395,13 @@ function SettingsContent({
|
||||
<>
|
||||
<span className="text-muted-foreground">·</span>
|
||||
<a
|
||||
href={`/${currentLang}/about${currentLang === "zh" ? "/cn" : currentLang === "ja" ? "/ja" : ""}`}
|
||||
href="/about"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1"
|
||||
>
|
||||
<Info className="h-3 w-3" />
|
||||
{dict.nav.about}
|
||||
About
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
"settings": "Settings",
|
||||
"hidePanel": "Hide chat panel (Ctrl+B)",
|
||||
"showPanel": "Show chat panel (Ctrl+B)",
|
||||
"aiChat": "AI Chat"
|
||||
"aiChat": "AI Chat",
|
||||
"sponsorTooltip": "Sponsored by ByteDance Doubao K2-thinking. See About page for details."
|
||||
},
|
||||
"providers": {
|
||||
"useServerDefault": "Use Server Default",
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
"settings": "設定",
|
||||
"hidePanel": "チャットパネルを非表示 (Ctrl+B)",
|
||||
"showPanel": "チャットパネルを表示 (Ctrl+B)",
|
||||
"aiChat": "AI チャット"
|
||||
"aiChat": "AI チャット",
|
||||
"sponsorTooltip": "ByteDance Doubao K2-thinking によるスポンサー。詳細は概要ページをご覧ください。"
|
||||
},
|
||||
"providers": {
|
||||
"useServerDefault": "サーバーデフォルトを使用",
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
"settings": "设置",
|
||||
"hidePanel": "隐藏聊天面板 (Ctrl+B)",
|
||||
"showPanel": "显示聊天面板 (Ctrl+B)",
|
||||
"aiChat": "AI 聊天"
|
||||
"aiChat": "AI 聊天",
|
||||
"sponsorTooltip": "由字节跳动豆包 K2-thinking 赞助。详情请参阅关于页面。"
|
||||
},
|
||||
"providers": {
|
||||
"useServerDefault": "使用服务器默认值",
|
||||
|
||||
Reference in New Issue
Block a user