Compare commits

..

5 Commits

Author SHA1 Message Date
dayuan.jiang
5df0050334 docs: sync CN/JA READMEs with EN structure and fix all paths 2025-12-30 23:40:00 +09:00
dayuan.jiang
f63ed75465 docs: fix broken import statements in cloudflare deploy guides 2025-12-30 23:35:38 +09:00
dayuan.jiang
dcd1ee9a2e docs: update README and provider docs 2025-12-30 23:12:02 +09:00
dayuan.jiang
7b18bcaf68 docs: fix links to new docs folder structure 2025-12-30 22:52:40 +09:00
dayuan.jiang
454b86d898 docs: reorganize docs into en/cn/ja folders
- Move documentation files into language-specific folders (en, cn, ja)
- Add Chinese and Japanese translations for all docs
- Extract Docker section from README to separate doc file
- Update README to link to new doc locations
2025-12-30 22:50:51 +09:00
9 changed files with 114 additions and 103 deletions

View File

@@ -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">

View File

@@ -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">

View File

@@ -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">

View File

@@ -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" &&
(() => {

View File

@@ -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

View File

@@ -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>
</>
)}

View File

@@ -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",

View File

@@ -18,7 +18,8 @@
"settings": "設定",
"hidePanel": "チャットパネルを非表示 (Ctrl+B)",
"showPanel": "チャットパネルを表示 (Ctrl+B)",
"aiChat": "AI チャット"
"aiChat": "AI チャット",
"sponsorTooltip": "ByteDance Doubao K2-thinking によるスポンサー。詳細は概要ページをご覧ください。"
},
"providers": {
"useServerDefault": "サーバーデフォルトを使用",

View File

@@ -18,7 +18,8 @@
"settings": "设置",
"hidePanel": "隐藏聊天面板 (Ctrl+B)",
"showPanel": "显示聊天面板 (Ctrl+B)",
"aiChat": "AI 聊天"
"aiChat": "AI 聊天",
"sponsorTooltip": "由字节跳动豆包 K2-thinking 赞助。详情请参阅关于页面。"
},
"providers": {
"useServerDefault": "使用服务器默认值",