mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-03 16:27:47 +08:00
Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43161977a5 | ||
|
|
f09d2c100a | ||
|
|
03e2d9da9d | ||
|
|
bd1e5cc237 | ||
|
|
edc10803e4 | ||
|
|
b074cee95c | ||
|
|
121b6fc99f | ||
|
|
0e1bed39a4 | ||
|
|
d5e2885623 | ||
|
|
7662b00bb3 | ||
|
|
8a93d4ea9f | ||
|
|
add9111c82 | ||
|
|
1c1c6a4d38 |
@@ -19,7 +19,7 @@ English | [中文](./docs/cn/README_CN.md) | [日本語](./docs/ja/README_JA.md)
|
|||||||
|
|
||||||
A Next.js web application that integrates AI capabilities with draw.io diagrams. Create, modify, and enhance diagrams through natural language commands and AI-assisted visualization.
|
A Next.js web application that integrates AI capabilities with draw.io diagrams. Create, modify, and enhance diagrams through natural language commands and AI-assisted visualization.
|
||||||
|
|
||||||
> Note: Thanks to <img src="https://raw.githubusercontent.com/DayuanJiang/next-ai-draw-io/main/public/doubao-color.png" alt="" height="20" /> [ByteDance Doubao](https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project) sponsorship, the demo site now uses the powerful K2-thinking model!
|
> Note: Thanks to <img src="https://raw.githubusercontent.com/DayuanJiang/next-ai-draw-io/main/public/doubao-color.png" alt="" height="20" /> [ByteDance Doubao](https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project) sponsorship, the demo site now uses the powerful K2-thinking model!
|
||||||
|
|
||||||
|
|
||||||
https://github.com/user-attachments/assets/9d60a3e8-4a1c-4b5e-acbb-26af2d3eabd1
|
https://github.com/user-attachments/assets/9d60a3e8-4a1c-4b5e-acbb-26af2d3eabd1
|
||||||
@@ -202,7 +202,7 @@ See the [Next.js deployment documentation](https://nextjs.org/docs/app/building-
|
|||||||
|
|
||||||
## Multi-Provider Support
|
## Multi-Provider Support
|
||||||
|
|
||||||
- [ByteDance Doubao](https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project)
|
- [ByteDance Doubao](https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project)
|
||||||
- AWS Bedrock (default)
|
- AWS Bedrock (default)
|
||||||
- OpenAI
|
- OpenAI
|
||||||
- Anthropic
|
- Anthropic
|
||||||
@@ -244,7 +244,7 @@ Diagrams are represented as XML that can be rendered in draw.io. The AI processe
|
|||||||
|
|
||||||
## Support & Contact
|
## Support & Contact
|
||||||
|
|
||||||
**Special thanks to [ByteDance Doubao](https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project) for sponsoring the API token usage of the demo site!** Register on the ARK platform to get 500K free tokens for all models!
|
**Special thanks to [ByteDance Doubao](https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project) for sponsoring the API token usage of the demo site!** Register on the ARK platform to get 500K free tokens for all models!
|
||||||
|
|
||||||
If you find this project useful, please consider [sponsoring](https://github.com/sponsors/DayuanJiang) to help me host the live demo site!
|
If you find this project useful, please consider [sponsoring](https://github.com/sponsors/DayuanJiang) to help me host the live demo site!
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export default function AboutCN() {
|
|||||||
<p>
|
<p>
|
||||||
好消息!感谢{" "}
|
好消息!感谢{" "}
|
||||||
<a
|
<a
|
||||||
href="https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project"
|
href="https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="font-semibold text-blue-600 hover:underline"
|
className="font-semibold text-blue-600 hover:underline"
|
||||||
@@ -277,7 +277,7 @@ export default function AboutCN() {
|
|||||||
<ul className="list-disc pl-6 text-gray-700 space-y-1">
|
<ul className="list-disc pl-6 text-gray-700 space-y-1">
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project"
|
href="https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-blue-600 hover:underline"
|
className="text-blue-600 hover:underline"
|
||||||
@@ -312,7 +312,7 @@ export default function AboutCN() {
|
|||||||
<p className="text-gray-700 mb-4 font-semibold">
|
<p className="text-gray-700 mb-4 font-semibold">
|
||||||
特别感谢{" "}
|
特别感谢{" "}
|
||||||
<a
|
<a
|
||||||
href="https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project"
|
href="https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-blue-600 hover:underline"
|
className="text-blue-600 hover:underline"
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export default function AboutJA() {
|
|||||||
<p>
|
<p>
|
||||||
朗報です!
|
朗報です!
|
||||||
<a
|
<a
|
||||||
href="https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project"
|
href="https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="font-semibold text-blue-600 hover:underline"
|
className="font-semibold text-blue-600 hover:underline"
|
||||||
@@ -292,7 +292,7 @@ export default function AboutJA() {
|
|||||||
<ul className="list-disc pl-6 text-gray-700 space-y-1">
|
<ul className="list-disc pl-6 text-gray-700 space-y-1">
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project"
|
href="https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-blue-600 hover:underline"
|
className="text-blue-600 hover:underline"
|
||||||
@@ -327,7 +327,7 @@ export default function AboutJA() {
|
|||||||
<p className="text-gray-700 mb-4 font-semibold">
|
<p className="text-gray-700 mb-4 font-semibold">
|
||||||
デモサイトのAPIトークン使用を支援してくださった{" "}
|
デモサイトのAPIトークン使用を支援してくださった{" "}
|
||||||
<a
|
<a
|
||||||
href="https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project"
|
href="https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-blue-600 hover:underline"
|
className="text-blue-600 hover:underline"
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export default function About() {
|
|||||||
Great news! Thanks to the generous
|
Great news! Thanks to the generous
|
||||||
sponsorship from{" "}
|
sponsorship from{" "}
|
||||||
<a
|
<a
|
||||||
href="https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project"
|
href="https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="font-semibold text-blue-600 hover:underline"
|
className="font-semibold text-blue-600 hover:underline"
|
||||||
@@ -311,7 +311,7 @@ export default function About() {
|
|||||||
<ul className="list-disc pl-6 text-gray-700 space-y-1">
|
<ul className="list-disc pl-6 text-gray-700 space-y-1">
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project"
|
href="https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-blue-600 hover:underline"
|
className="text-blue-600 hover:underline"
|
||||||
@@ -348,7 +348,7 @@ export default function About() {
|
|||||||
<p className="text-gray-700 mb-4 font-semibold">
|
<p className="text-gray-700 mb-4 font-semibold">
|
||||||
Special thanks to{" "}
|
Special thanks to{" "}
|
||||||
<a
|
<a
|
||||||
href="https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project"
|
href="https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-blue-600 hover:underline"
|
className="text-blue-600 hover:underline"
|
||||||
|
|||||||
@@ -449,7 +449,6 @@ ${userInputText}
|
|||||||
|
|
||||||
const result = streamText({
|
const result = streamText({
|
||||||
model,
|
model,
|
||||||
abortSignal: req.signal,
|
|
||||||
...(process.env.MAX_OUTPUT_TOKENS && {
|
...(process.env.MAX_OUTPUT_TOKENS && {
|
||||||
maxOutputTokens: parseInt(process.env.MAX_OUTPUT_TOKENS, 10),
|
maxOutputTokens: parseInt(process.env.MAX_OUTPUT_TOKENS, 10),
|
||||||
}),
|
}),
|
||||||
|
|||||||
+18
-27
@@ -7,7 +7,6 @@ import {
|
|||||||
Link,
|
Link,
|
||||||
Loader2,
|
Loader2,
|
||||||
Send,
|
Send,
|
||||||
Square,
|
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import type React from "react"
|
import type React from "react"
|
||||||
import {
|
import {
|
||||||
@@ -155,7 +154,6 @@ interface ChatInputProps {
|
|||||||
status: "submitted" | "streaming" | "ready" | "error"
|
status: "submitted" | "streaming" | "ready" | "error"
|
||||||
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void
|
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void
|
||||||
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
|
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
|
||||||
onStop?: () => void
|
|
||||||
files?: File[]
|
files?: File[]
|
||||||
onFileChange?: (files: File[]) => void
|
onFileChange?: (files: File[]) => void
|
||||||
pdfData?: Map<
|
pdfData?: Map<
|
||||||
@@ -184,7 +182,6 @@ export const ChatInput = forwardRef<ChatInputRef, ChatInputProps>(
|
|||||||
status,
|
status,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onChange,
|
onChange,
|
||||||
onStop,
|
|
||||||
files = [],
|
files = [],
|
||||||
onFileChange = () => {},
|
onFileChange = () => {},
|
||||||
pdfData = new Map(),
|
pdfData = new Map(),
|
||||||
@@ -555,30 +552,24 @@ export const ChatInput = forwardRef<ChatInputRef, ChatInputProps>(
|
|||||||
showUnvalidatedModels={showUnvalidatedModels}
|
showUnvalidatedModels={showUnvalidatedModels}
|
||||||
/>
|
/>
|
||||||
<div className="w-px h-5 bg-border mx-1" />
|
<div className="w-px h-5 bg-border mx-1" />
|
||||||
{(status === "streaming" || status === "submitted") &&
|
<Button
|
||||||
onStop ? (
|
type="submit"
|
||||||
<Button
|
disabled={isDisabled || !input.trim()}
|
||||||
type="button"
|
size="sm"
|
||||||
onClick={onStop}
|
className="h-8 px-4 rounded-xl font-medium shadow-sm"
|
||||||
size="sm"
|
aria-label={
|
||||||
variant="destructive"
|
isDisabled ? dict.chat.sending : dict.chat.send
|
||||||
className="h-8 w-8 p-0 rounded-xl shadow-sm"
|
}
|
||||||
aria-label={dict.chat.stopGeneration}
|
>
|
||||||
>
|
{isDisabled ? (
|
||||||
<Square className="h-4 w-4" />
|
<Loader2 className="h-4 w-4 animate-spin" />
|
||||||
</Button>
|
) : (
|
||||||
) : (
|
<>
|
||||||
<Button
|
<Send className="h-4 w-4 mr-1.5" />
|
||||||
type="submit"
|
{dict.chat.send}
|
||||||
disabled={isDisabled || !input.trim()}
|
</>
|
||||||
size="sm"
|
)}
|
||||||
className="h-8 px-4 rounded-xl font-medium shadow-sm"
|
</Button>
|
||||||
aria-label={dict.chat.send}
|
|
||||||
>
|
|
||||||
<Send className="h-4 w-4 mr-1.5" />
|
|
||||||
{dict.chat.send}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<HistoryDialog
|
<HistoryDialog
|
||||||
|
|||||||
+134
-162
@@ -340,155 +340,151 @@ export default function ChatPanel({
|
|||||||
onValidationStateChange: handleValidationStateChange,
|
onValidationStateChange: handleValidationStateChange,
|
||||||
})
|
})
|
||||||
|
|
||||||
const {
|
const { messages, sendMessage, addToolOutput, status, error, setMessages } =
|
||||||
messages,
|
useChat({
|
||||||
sendMessage,
|
transport: new DefaultChatTransport({
|
||||||
addToolOutput,
|
api: getApiEndpoint("/api/chat"),
|
||||||
status,
|
}),
|
||||||
error,
|
onToolCall: async ({ toolCall }) => {
|
||||||
setMessages,
|
await handleToolCall({ toolCall }, addToolOutput)
|
||||||
stop,
|
},
|
||||||
} = useChat({
|
onError: (error) => {
|
||||||
transport: new DefaultChatTransport({
|
// Handle server-side quota limit (429 response)
|
||||||
api: getApiEndpoint("/api/chat"),
|
// AI SDK puts the full response body in error.message for non-OK responses
|
||||||
}),
|
try {
|
||||||
onToolCall: async ({ toolCall }) => {
|
const data = JSON.parse(error.message)
|
||||||
await handleToolCall({ toolCall }, addToolOutput)
|
if (data.type === "request") {
|
||||||
},
|
quotaManager.showQuotaLimitToast(data.used, data.limit)
|
||||||
onError: (error) => {
|
return
|
||||||
// Handle server-side quota limit (429 response)
|
}
|
||||||
// AI SDK puts the full response body in error.message for non-OK responses
|
if (data.type === "token") {
|
||||||
try {
|
quotaManager.showTokenLimitToast(data.used, data.limit)
|
||||||
const data = JSON.parse(error.message)
|
return
|
||||||
if (data.type === "request") {
|
}
|
||||||
quotaManager.showQuotaLimitToast(data.used, data.limit)
|
if (data.type === "tpm") {
|
||||||
|
quotaManager.showTPMLimitToast(data.limit)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Not JSON, fall through to string matching for backwards compatibility
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to string matching
|
||||||
|
if (error.message.includes("Daily request limit")) {
|
||||||
|
quotaManager.showQuotaLimitToast()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (data.type === "token") {
|
if (error.message.includes("Daily token limit")) {
|
||||||
quotaManager.showTokenLimitToast(data.used, data.limit)
|
quotaManager.showTokenLimitToast()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (data.type === "tpm") {
|
|
||||||
quotaManager.showTPMLimitToast(data.limit)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Not JSON, fall through to string matching for backwards compatibility
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to string matching
|
|
||||||
if (error.message.includes("Daily request limit")) {
|
|
||||||
quotaManager.showQuotaLimitToast()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (error.message.includes("Daily token limit")) {
|
|
||||||
quotaManager.showTokenLimitToast()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
error.message.includes("Rate limit exceeded") ||
|
|
||||||
error.message.includes("tokens per minute")
|
|
||||||
) {
|
|
||||||
quotaManager.showTPMLimitToast()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Silence access code error in console since it's handled by UI
|
|
||||||
if (!error.message.includes("Invalid or missing access code")) {
|
|
||||||
console.error("Chat error:", error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Translate technical errors into user-friendly messages
|
|
||||||
// The server now handles detailed error messages, so we can display them directly.
|
|
||||||
// But we still handle connection/network errors that happen before reaching the server.
|
|
||||||
let friendlyMessage = error.message
|
|
||||||
|
|
||||||
// Simple check for network errors if message is generic
|
|
||||||
if (friendlyMessage === "Failed to fetch") {
|
|
||||||
friendlyMessage = "Network error. Please check your connection."
|
|
||||||
}
|
|
||||||
|
|
||||||
// Truncated tool input error (model output limit too low)
|
|
||||||
if (friendlyMessage.includes("toolUse.input is invalid")) {
|
|
||||||
friendlyMessage =
|
|
||||||
"Output was truncated before the diagram could be generated. Try a simpler request or increase the maxOutputLength."
|
|
||||||
}
|
|
||||||
|
|
||||||
// Translate image not supported error
|
|
||||||
if (
|
|
||||||
friendlyMessage.includes("image content block") ||
|
|
||||||
friendlyMessage.toLowerCase().includes("image_url")
|
|
||||||
) {
|
|
||||||
friendlyMessage = "This model doesn't support image input."
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add system message for error so it can be cleared
|
|
||||||
setMessages((currentMessages) => {
|
|
||||||
const errorMessage = {
|
|
||||||
id: `error-${Date.now()}`,
|
|
||||||
role: "system" as const,
|
|
||||||
content: friendlyMessage,
|
|
||||||
parts: [{ type: "text" as const, text: friendlyMessage }],
|
|
||||||
}
|
|
||||||
return [...currentMessages, errorMessage]
|
|
||||||
})
|
|
||||||
|
|
||||||
if (error.message.includes("Invalid or missing access code")) {
|
|
||||||
// Show settings dialog to help user fix it
|
|
||||||
setShowSettingsDialog(true)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onFinish: () => {},
|
|
||||||
sendAutomaticallyWhen: ({ messages }) => {
|
|
||||||
const isInContinuationMode = partialXmlRef.current.length > 0
|
|
||||||
|
|
||||||
const shouldRetry = hasToolErrors(
|
|
||||||
messages as unknown as ChatMessage[],
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!shouldRetry) {
|
|
||||||
// No error, reset retry count and clear state
|
|
||||||
autoRetryCountRef.current = 0
|
|
||||||
continuationRetryCountRef.current = 0
|
|
||||||
partialXmlRef.current = ""
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Continuation mode: limited retries for truncation handling
|
|
||||||
if (isInContinuationMode) {
|
|
||||||
if (
|
if (
|
||||||
continuationRetryCountRef.current >=
|
error.message.includes("Rate limit exceeded") ||
|
||||||
MAX_CONTINUATION_RETRY_COUNT
|
error.message.includes("tokens per minute")
|
||||||
) {
|
) {
|
||||||
toast.error(
|
quotaManager.showTPMLimitToast()
|
||||||
formatMessage(dict.errors.continuationRetryLimit, {
|
return
|
||||||
max: MAX_CONTINUATION_RETRY_COUNT,
|
}
|
||||||
}),
|
|
||||||
)
|
// Silence access code error in console since it's handled by UI
|
||||||
|
if (!error.message.includes("Invalid or missing access code")) {
|
||||||
|
console.error("Chat error:", error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate technical errors into user-friendly messages
|
||||||
|
// The server now handles detailed error messages, so we can display them directly.
|
||||||
|
// But we still handle connection/network errors that happen before reaching the server.
|
||||||
|
let friendlyMessage = error.message
|
||||||
|
|
||||||
|
// Simple check for network errors if message is generic
|
||||||
|
if (friendlyMessage === "Failed to fetch") {
|
||||||
|
friendlyMessage =
|
||||||
|
"Network error. Please check your connection."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncated tool input error (model output limit too low)
|
||||||
|
if (friendlyMessage.includes("toolUse.input is invalid")) {
|
||||||
|
friendlyMessage =
|
||||||
|
"Output was truncated before the diagram could be generated. Try a simpler request or increase the maxOutputLength."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate image not supported error
|
||||||
|
if (
|
||||||
|
friendlyMessage.includes("image content block") ||
|
||||||
|
friendlyMessage.toLowerCase().includes("image_url")
|
||||||
|
) {
|
||||||
|
friendlyMessage = "This model doesn't support image input."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add system message for error so it can be cleared
|
||||||
|
setMessages((currentMessages) => {
|
||||||
|
const errorMessage = {
|
||||||
|
id: `error-${Date.now()}`,
|
||||||
|
role: "system" as const,
|
||||||
|
content: friendlyMessage,
|
||||||
|
parts: [
|
||||||
|
{ type: "text" as const, text: friendlyMessage },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
return [...currentMessages, errorMessage]
|
||||||
|
})
|
||||||
|
|
||||||
|
if (error.message.includes("Invalid or missing access code")) {
|
||||||
|
// Show settings dialog to help user fix it
|
||||||
|
setShowSettingsDialog(true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFinish: () => {},
|
||||||
|
sendAutomaticallyWhen: ({ messages }) => {
|
||||||
|
const isInContinuationMode = partialXmlRef.current.length > 0
|
||||||
|
|
||||||
|
const shouldRetry = hasToolErrors(
|
||||||
|
messages as unknown as ChatMessage[],
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!shouldRetry) {
|
||||||
|
// No error, reset retry count and clear state
|
||||||
|
autoRetryCountRef.current = 0
|
||||||
continuationRetryCountRef.current = 0
|
continuationRetryCountRef.current = 0
|
||||||
partialXmlRef.current = ""
|
partialXmlRef.current = ""
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
continuationRetryCountRef.current++
|
|
||||||
} else {
|
|
||||||
// Regular error: check retry count limit
|
|
||||||
if (autoRetryCountRef.current >= MAX_AUTO_RETRY_COUNT) {
|
|
||||||
toast.error(
|
|
||||||
formatMessage(dict.errors.retryLimit, {
|
|
||||||
max: MAX_AUTO_RETRY_COUNT,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
autoRetryCountRef.current = 0
|
|
||||||
partialXmlRef.current = ""
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Increment retry count for actual errors
|
|
||||||
autoRetryCountRef.current++
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
// Continuation mode: limited retries for truncation handling
|
||||||
},
|
if (isInContinuationMode) {
|
||||||
})
|
if (
|
||||||
|
continuationRetryCountRef.current >=
|
||||||
|
MAX_CONTINUATION_RETRY_COUNT
|
||||||
|
) {
|
||||||
|
toast.error(
|
||||||
|
formatMessage(dict.errors.continuationRetryLimit, {
|
||||||
|
max: MAX_CONTINUATION_RETRY_COUNT,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
continuationRetryCountRef.current = 0
|
||||||
|
partialXmlRef.current = ""
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
continuationRetryCountRef.current++
|
||||||
|
} else {
|
||||||
|
// Regular error: check retry count limit
|
||||||
|
if (autoRetryCountRef.current >= MAX_AUTO_RETRY_COUNT) {
|
||||||
|
toast.error(
|
||||||
|
formatMessage(dict.errors.retryLimit, {
|
||||||
|
max: MAX_AUTO_RETRY_COUNT,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
autoRetryCountRef.current = 0
|
||||||
|
partialXmlRef.current = ""
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// Increment retry count for actual errors
|
||||||
|
autoRetryCountRef.current++
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
// Store sendMessage in ref for use in callbacks (like handleImproveWithSuggestions)
|
// Store sendMessage in ref for use in callbacks (like handleImproveWithSuggestions)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -997,29 +993,6 @@ export default function ChatPanel({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle stop button click
|
|
||||||
const handleStop = useCallback(() => {
|
|
||||||
const lastMessage = messages[messages.length - 1]
|
|
||||||
const toolParts = lastMessage?.parts?.filter(
|
|
||||||
(part: any) =>
|
|
||||||
part.type?.startsWith("tool-") &&
|
|
||||||
part.state === "input-streaming",
|
|
||||||
)
|
|
||||||
|
|
||||||
toolParts?.forEach((part: any) => {
|
|
||||||
if (part.toolCallId) {
|
|
||||||
addToolOutput({
|
|
||||||
tool: part.type.replace("tool-", ""),
|
|
||||||
toolCallId: part.toolCallId,
|
|
||||||
state: "output-error",
|
|
||||||
errorText: "Stopped by user",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
stop()
|
|
||||||
}, [messages, addToolOutput, stop])
|
|
||||||
|
|
||||||
// Send chat message with headers
|
// Send chat message with headers
|
||||||
const sendChatMessage = (
|
const sendChatMessage = (
|
||||||
parts: any,
|
parts: any,
|
||||||
@@ -1386,7 +1359,6 @@ export default function ChatPanel({
|
|||||||
status={status}
|
status={status}
|
||||||
onSubmit={onFormSubmit}
|
onSubmit={onFormSubmit}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
onStop={handleStop}
|
|
||||||
files={files}
|
files={files}
|
||||||
onFileChange={handleFileChange}
|
onFileChange={handleFileChange}
|
||||||
pdfData={pdfData}
|
pdfData={pdfData}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export function QuotaLimitToast({
|
|||||||
<p
|
<p
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: formatMessage(dict.quota.doubaoSponsorship, {
|
__html: formatMessage(dict.quota.doubaoSponsorship, {
|
||||||
link: "https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project",
|
link: "https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project",
|
||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
一个集成了AI功能的Next.js网页应用,与draw.io图表无缝结合。通过自然语言命令和AI辅助可视化来创建、修改和增强图表。
|
一个集成了AI功能的Next.js网页应用,与draw.io图表无缝结合。通过自然语言命令和AI辅助可视化来创建、修改和增强图表。
|
||||||
|
|
||||||
> 注:感谢 <img src="https://raw.githubusercontent.com/DayuanJiang/next-ai-draw-io/main/public/doubao-color.png" alt="" height="20" /> [字节跳动豆包](https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project) 的赞助支持,本项目的 Demo 现已接入强大的 K2-thinking 模型!
|
> 注:感谢 <img src="https://raw.githubusercontent.com/DayuanJiang/next-ai-draw-io/main/public/doubao-color.png" alt="" height="20" /> [字节跳动豆包](https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project) 的赞助支持,本项目的 Demo 现已接入强大的 K2-thinking 模型!
|
||||||
|
|
||||||
https://github.com/user-attachments/assets/b2eef5f3-b335-4e71-a755-dc2e80931979
|
https://github.com/user-attachments/assets/b2eef5f3-b335-4e71-a755-dc2e80931979
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ npm run dev
|
|||||||
|
|
||||||
## 多提供商支持
|
## 多提供商支持
|
||||||
|
|
||||||
- [字节跳动豆包](https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project)
|
- [字节跳动豆包](https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project)
|
||||||
- AWS Bedrock(默认)
|
- AWS Bedrock(默认)
|
||||||
- OpenAI
|
- OpenAI
|
||||||
- Anthropic
|
- Anthropic
|
||||||
@@ -236,7 +236,7 @@ npm run dev
|
|||||||
|
|
||||||
## 支持与联系
|
## 支持与联系
|
||||||
|
|
||||||
**特别感谢[字节跳动豆包](https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project)赞助演示站点的 API Token 使用!** 注册火山引擎 ARK 平台即可获得50万免费Token!
|
**特别感谢[字节跳动豆包](https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project)赞助演示站点的 API Token 使用!** 注册火山引擎 ARK 平台即可获得50万免费Token!
|
||||||
|
|
||||||
如果您觉得这个项目有用,请考虑[赞助](https://github.com/sponsors/DayuanJiang)来帮助我托管在线演示站点!
|
如果您觉得这个项目有用,请考虑[赞助](https://github.com/sponsors/DayuanJiang)来帮助我托管在线演示站点!
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
### 豆包 (字节跳动火山引擎)
|
### 豆包 (字节跳动火山引擎)
|
||||||
|
|
||||||
> **免费 Token**:在 [火山引擎 ARK 平台](https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project) 注册,即可获得所有模型 50 万免费 Token!
|
> **免费 Token**:在 [火山引擎 ARK 平台](https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project) 注册,即可获得所有模型 50 万免费 Token!
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
DOUBAO_API_KEY=your_api_key
|
DOUBAO_API_KEY=your_api_key
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ This guide explains how to configure different AI model providers for next-ai-dr
|
|||||||
|
|
||||||
### Doubao (ByteDance Volcengine)
|
### Doubao (ByteDance Volcengine)
|
||||||
|
|
||||||
> **Free tokens**: Register on the [Volcengine ARK platform](https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project) to get 500K free tokens for all models!
|
> **Free tokens**: Register on the [Volcengine ARK platform](https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project) to get 500K free tokens for all models!
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
DOUBAO_API_KEY=your_api_key
|
DOUBAO_API_KEY=your_api_key
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
AI機能とdraw.ioダイアグラムを統合したNext.jsウェブアプリケーションです。自然言語コマンドとAI支援の可視化により、ダイアグラムを作成、修正、強化できます。
|
AI機能とdraw.ioダイアグラムを統合したNext.jsウェブアプリケーションです。自然言語コマンドとAI支援の可視化により、ダイアグラムを作成、修正、強化できます。
|
||||||
|
|
||||||
> 注:<img src="https://raw.githubusercontent.com/DayuanJiang/next-ai-draw-io/main/public/doubao-color.png" alt="" height="20" /> [ByteDance Doubao](https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project) のご支援により、デモサイトに強力な K2-thinking モデルを導入しました!
|
> 注:<img src="https://raw.githubusercontent.com/DayuanJiang/next-ai-draw-io/main/public/doubao-color.png" alt="" height="20" /> [ByteDance Doubao](https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project) のご支援により、デモサイトに強力な K2-thinking モデルを導入しました!
|
||||||
|
|
||||||
https://github.com/user-attachments/assets/b2eef5f3-b335-4e71-a755-dc2e80931979
|
https://github.com/user-attachments/assets/b2eef5f3-b335-4e71-a755-dc2e80931979
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ Next.jsアプリをデプロイする最も簡単な方法は、Next.jsの作成
|
|||||||
|
|
||||||
## マルチプロバイダーサポート
|
## マルチプロバイダーサポート
|
||||||
|
|
||||||
- [ByteDance Doubao](https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project)
|
- [ByteDance Doubao](https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project)
|
||||||
- AWS Bedrock(デフォルト)
|
- AWS Bedrock(デフォルト)
|
||||||
- OpenAI
|
- OpenAI
|
||||||
- Anthropic
|
- Anthropic
|
||||||
@@ -237,7 +237,7 @@ AWS BedrockとOpenRouter以外のすべてのプロバイダーはカスタム
|
|||||||
|
|
||||||
## サポート&お問い合わせ
|
## サポート&お問い合わせ
|
||||||
|
|
||||||
**デモサイトのAPIトークン使用を支援してくださった[ByteDance Doubao](https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project)に特別な感謝を申し上げます!** ARKプラットフォームに登録すると、50万トークンが無料でもらえます!
|
**デモサイトのAPIトークン使用を支援してくださった[ByteDance Doubao](https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project)に特別な感謝を申し上げます!** ARKプラットフォームに登録すると、50万トークンが無料でもらえます!
|
||||||
|
|
||||||
このプロジェクトが役に立ったら、ライブデモサイトのホスティングを支援するために[スポンサー](https://github.com/sponsors/DayuanJiang)をご検討ください!
|
このプロジェクトが役に立ったら、ライブデモサイトのホスティングを支援するために[スポンサー](https://github.com/sponsors/DayuanJiang)をご検討ください!
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
### Doubao (ByteDance Volcengine)
|
### Doubao (ByteDance Volcengine)
|
||||||
|
|
||||||
> **無料トークン**: [Volcengine ARK プラットフォーム](https://www.volcengine.com/activity/newyear-referral?utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project)に登録すると、すべてのモデルで使える50万トークンが無料で入手できます!
|
> **無料トークン**: [Volcengine ARK プラットフォーム](https://console.volcengine.com/ark/region:ark+cn-beijing/overview?briefPage=0&briefType=introduce&type=new&utm_campaign=doubao&utm_content=aidrawio&utm_medium=github&utm_source=coopensrc&utm_term=project)に登録すると、すべてのモデルで使える50万トークンが無料で入手できます!
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
DOUBAO_API_KEY=your_api_key
|
DOUBAO_API_KEY=your_api_key
|
||||||
|
|||||||
+2
-9
@@ -1161,15 +1161,8 @@ export function supportsImageInput(modelId: string): boolean {
|
|||||||
lowerModelId.includes("vision") || lowerModelId.includes("vl")
|
lowerModelId.includes("vision") || lowerModelId.includes("vl")
|
||||||
|
|
||||||
// Models that DON'T support image/vision input (unless vision variant)
|
// Models that DON'T support image/vision input (unless vision variant)
|
||||||
// Kimi K2 doesn't support images, but K2.5 does
|
// Kimi K2 models don't support images
|
||||||
// Only block kimi-k2 specifically, not other Kimi models
|
if (lowerModelId.includes("kimi") && !hasVisionIndicator) {
|
||||||
if (
|
|
||||||
(lowerModelId.includes("kimi-k2") ||
|
|
||||||
lowerModelId.includes("kimi_k2")) &&
|
|
||||||
!hasVisionIndicator &&
|
|
||||||
!lowerModelId.includes("2.5") &&
|
|
||||||
!lowerModelId.includes("k2.5")
|
|
||||||
) {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
"chat": {
|
"chat": {
|
||||||
"placeholder": "Describe your diagram or upload a file...",
|
"placeholder": "Describe your diagram or upload a file...",
|
||||||
"send": "Send",
|
"send": "Send",
|
||||||
"stopGeneration": "Stop generation",
|
"sending": "Sending...",
|
||||||
"sendMessage": "Send message",
|
"sendMessage": "Send message",
|
||||||
"clearConversation": "Clear conversation",
|
"clearConversation": "Clear conversation",
|
||||||
"diagramHistory": "Diagram history",
|
"diagramHistory": "Diagram history",
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
"chat": {
|
"chat": {
|
||||||
"placeholder": "ダイアグラムを説明するか、ファイルをアップロード...",
|
"placeholder": "ダイアグラムを説明するか、ファイルをアップロード...",
|
||||||
"send": "送信",
|
"send": "送信",
|
||||||
"stopGeneration": "生成を停止",
|
"sending": "送信中...",
|
||||||
"sendMessage": "メッセージを送信",
|
"sendMessage": "メッセージを送信",
|
||||||
"clearConversation": "会話をクリア",
|
"clearConversation": "会話をクリア",
|
||||||
"diagramHistory": "ダイアグラム履歴",
|
"diagramHistory": "ダイアグラム履歴",
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
"chat": {
|
"chat": {
|
||||||
"placeholder": "描述您的圖表或上傳檔案...",
|
"placeholder": "描述您的圖表或上傳檔案...",
|
||||||
"send": "傳送",
|
"send": "傳送",
|
||||||
"stopGeneration": "停止產生",
|
"sending": "傳送中...",
|
||||||
"sendMessage": "傳送訊息",
|
"sendMessage": "傳送訊息",
|
||||||
"clearConversation": "清除對話",
|
"clearConversation": "清除對話",
|
||||||
"diagramHistory": "圖表歷史",
|
"diagramHistory": "圖表歷史",
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
"chat": {
|
"chat": {
|
||||||
"placeholder": "描述您的图表或上传文件...",
|
"placeholder": "描述您的图表或上传文件...",
|
||||||
"send": "发送",
|
"send": "发送",
|
||||||
"stopGeneration": "停止生成",
|
"sending": "发送中...",
|
||||||
"sendMessage": "发送消息",
|
"sendMessage": "发送消息",
|
||||||
"clearConversation": "清除对话",
|
"clearConversation": "清除对话",
|
||||||
"diagramHistory": "图表历史",
|
"diagramHistory": "图表历史",
|
||||||
|
|||||||
@@ -168,11 +168,6 @@ describe("supportsImageInput", () => {
|
|||||||
expect(supportsImageInput("moonshot/kimi-k2")).toBe(false)
|
expect(supportsImageInput("moonshot/kimi-k2")).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns true for Kimi K2.5 models (supports vision)", () => {
|
|
||||||
expect(supportsImageInput("kimi-k2.5")).toBe(true)
|
|
||||||
expect(supportsImageInput("moonshotai/kimi-k2.5")).toBe(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("returns false for DeepSeek text models", () => {
|
it("returns false for DeepSeek text models", () => {
|
||||||
expect(supportsImageInput("deepseek-chat")).toBe(false)
|
expect(supportsImageInput("deepseek-chat")).toBe(false)
|
||||||
expect(supportsImageInput("deepseek-coder")).toBe(false)
|
expect(supportsImageInput("deepseek-coder")).toBe(false)
|
||||||
|
|||||||
Reference in New Issue
Block a user