Compare commits

..
Author SHA1 Message Date
Dayuan Jiang a6e812d3a5 fix(deps): update proxy-addr, shell-quote and wrangler to patched versions (#971)
npm audit fix in the root and in packages/mcp-server. proxy-addr 2.0.8 fixes
the IP spoofing through IPv4-mapped IPv6 trust subnets (GHSA-jqcg-44mw-7w3h),
shell-quote 1.12.0 fixes the command injection through a line terminator, and
wrangler 4.149.0 brings a miniflare that no longer bundles the vulnerable
sharp 0.35.4. No package is added; 56 lock entries for miniflare's sharp
binaries go away.
2026-10-10 19:53:55 +09:00
Dayuan Jiang 02eaa0bd22 ci: give the SignPath approval one hour instead of ten minutes (#968) 2026-10-10 12:57:21 +09:00
Dayuan Jiang 4cba863fd9 chore: bump version to 0.5.0 (#967) 2026-10-10 12:02:12 +09:00
Dayuan Jiang 7682e479e3 chore: replace the cat example with a new drawing (#966)
Update the README cat image and the cached example response for
"Draw a cat for me" with a new cat diagram.
2026-10-10 10:33:23 +09:00
Dayuan Jiang a7ae114edf fix(chat): keep the request alive while its answer streams, so a stop still reaches the provider (#965)
A Request's signal follows the signal it was created with through a weak
link inside undici: once the Request object is garbage collected, the link is
gone and request.signal never aborts (nodejs/undici#3644). The chat route
passes req.signal to streamText and holds nothing else from the request, so a
GC pause during a stream could leave the model call running after the client
stopped or disconnected, with no onAbort accounting.

The route now keeps each request in a WeakMap keyed by its response, which
Next holds while the body is piped.

This is also the cause of the unit test chat-route-abort hanging on CI (three
times this week, 60 ms locally): the test's Request is dropped as soon as the
route returns, and a GC in that window lost the abort. With a forced GC the
test hangs on Node 20 and 24 without this change and passes with it.
2026-10-10 09:24:25 +09:00
Dayuan Jiang e117095c7b feat(admin): rewrite the admin panel copy in plain words (#963)
Labels are two to four words, descriptions one short line, and the
wording follows the reader rather than the implementation: "服务商"
instead of "provider", "本页设置" instead of "后台设置" for the source
chip (it shared its name with the page title), "需重启" as a badge, a
verb on every switch ("访问内网"), "Langfuse 密钥" instead of "私钥".
Descriptions keep the facts the old ones had (where to change an
env-managed provider, that users must enter an access code), and the
same style is applied to the English, Japanese and Traditional Chinese
dictionaries. Five user-facing strings in the same files are aligned
with the new terms (服务商, token).
2026-10-10 08:38:26 +09:00
Dayuan Jiang f0f0bc0dc0 feat(ui): tidy the start screen and the chat panel details (#962)
Start screen
- The five examples are one row of picture tiles (title under the picture); the old cards cut their descriptions off with an ellipsis
- "Open .drawio file" leaves the example grid and sits with "Start with a blank canvas" below it, both as outlined buttons
- The "Paper to diagram" example gets a thumbnail of its result (public/paper_demo.svg) like the other four
- The caption says what happens: examples use saved answers, so they appear at once and do not call the AI
- Shorter lede

Chat panel
- The version card's "</>" icon button becomes a "Show XML" text link in the summary line; the summary runs under the title row, so Undo is the card's only button
- Settings uses the gear icon; new diagram uses a file-plus icon instead of the pen that reads as "edit"
- The message box has the same side padding as the message column

Copy
- The model picker says "Server model" instead of "Default" when the server's model is in use
- The theme description no longer claims the diagram always stays on a white sheet (the canvas follows the theme)
- en, zh, ja and zh-Hant dictionaries updated together
2026-10-10 08:22:36 +09:00
Dayuan Jiang d5096b9628 docs: restructure the README around the new promo video (#961)
* docs: restructure the README around the new promo video

The three READMEs now follow the reader's path: what it is (video and
two paragraphs), highlights grouped as draw / edit / use, examples, how
to use it (demo, desktop app, MCP), how to self-host (local run, a
one-click deploy table, models and providers), and support.

Removed the table of contents, the sponsor blocks, the "How It Works"
section, the duplicated demo button and the model recommendation list.
The provider list now matches env.example (24 providers). The video is
the new 30-second promo. Bold markers inside the HTML example table are
now <strong> tags, so they render.

* docs: keep the TrendShift badge under the language switch

* chore: remove the sponsor material for Doubao and Atlas Cloud

The about pages lose the "Sponsored by ByteDance Doubao" card, the
promotional links in the provider list and the thanks paragraph; the
"bring your own key" note stays. The quota toast no longer shows the
Volcengine sign-up paragraph, and its messages in the four languages no
longer mention a sponsor. The provider guides lose the "free tokens"
promo line. The logo and invite images used only by that material are
deleted. Doubao and Atlas Cloud remain supported providers.
2026-10-10 08:22:16 +09:00
17 changed files with 569 additions and 1658 deletions
+3
View File
@@ -86,6 +86,9 @@ jobs:
artifact-configuration-slug: 'windows-exe' artifact-configuration-slug: 'windows-exe'
github-artifact-id: ${{ steps.upload-unsigned.outputs.artifact-id }} github-artifact-id: ${{ steps.upload-unsigned.outputs.artifact-id }}
wait-for-completion: true wait-for-completion: true
# The release-signing policy needs a manual approval in SignPath;
# the default 600 s wait made the job fail before anyone could approve.
wait-for-completion-timeout-in-seconds: 3600
output-artifact-directory: release-signed output-artifact-directory: release-signed
- name: Upload signed artifacts to release - name: Upload signed artifacts to release
+6
View File
@@ -98,6 +98,11 @@ function createCachedStreamResponse(xml: string): Response {
// Responses streamed from the model, whose trace streamText's callbacks end // Responses streamed from the model, whose trace streamText's callbacks end
const modelStreamResponses = new WeakSet<Response>() const modelStreamResponses = new WeakSet<Response>()
// A Request's signal follows the client's disconnect only while the Request
// object itself is alive: once it is garbage collected, the abort is lost
// (nodejs/undici#3644) and a stopped chat would run on at the provider.
// Each request is kept as long as its answer streams.
const requestOfResponse = new WeakMap<Response, Request>()
// Inner handler function // Inner handler function
const DEBUG_LLM_PAYLOAD = process.env.DEBUG_LLM_PAYLOAD === "true" const DEBUG_LLM_PAYLOAD = process.env.DEBUG_LLM_PAYLOAD === "true"
@@ -787,6 +792,7 @@ Call this tool to get shape names and usage syntax for a specific library.`,
}, },
}) })
modelStreamResponses.add(response) modelStreamResponses.add(response)
requestOfResponse.set(response, req)
return response return response
} }
+5 -5
View File
@@ -1,10 +1,10 @@
import { import {
ChevronDown, ChevronDown,
Download, Download,
FilePlus,
MessageSquareDashed, MessageSquareDashed,
PanelRightClose, PanelRightClose,
Settings2, Settings,
SquarePen,
} from "lucide-react" } from "lucide-react"
import { useRef } from "react" import { useRef } from "react"
import { DevXmlSimulator } from "@/components/dev-xml-simulator" import { DevXmlSimulator } from "@/components/dev-xml-simulator"
@@ -139,14 +139,14 @@ export function ChatPanel({
disabled={engine.isBusy} disabled={engine.isBusy}
data-testid="new-chat-button" data-testid="new-chat-button"
> >
<SquarePen /> <FilePlus />
</IconButton> </IconButton>
<IconButton <IconButton
label={dict.nav.settings} label={dict.nav.settings}
onClick={() => openSettings()} onClick={() => openSettings()}
data-testid="settings-button" data-testid="settings-button"
> >
<Settings2 /> <Settings />
</IconButton> </IconButton>
{!mobile && ( {!mobile && (
<IconButton <IconButton
@@ -173,7 +173,7 @@ export function ChatPanel({
/> />
)} )}
<div className="shrink-0 p-2.5 pt-1"> <div className="shrink-0 px-4 pt-1 pb-3">
<Composer <Composer
placeholder={ placeholder={
engine.messages.length > 0 && engine.messages.length > 0 &&
+32 -42
View File
@@ -18,14 +18,18 @@ interface Example {
file?: { path: string; name: string; type: string } file?: { path: string; name: string; type: string }
} }
/** Secondary start buttons: outlined, so they read as clickable */
const startButtonClass =
"inline-flex h-8 items-center gap-1.5 rounded-lg border border-border bg-card px-3 text-[13px] text-foreground transition-colors hover:bg-accent [&_svg]:text-muted-foreground"
async function loadExampleFile(file: NonNullable<Example["file"]>) { async function loadExampleFile(file: NonNullable<Example["file"]>) {
const response = await fetch(getAssetUrl(file.path)) const response = await fetch(getAssetUrl(file.path))
const blob = await response.blob() const blob = await response.blob()
return new File([blob], file.name, { type: file.type }) return new File([blob], file.name, { type: file.type })
} }
/** One start-screen choice: a small picture, a title and one line */ /** One start-screen example: a small picture over its title */
function ExampleCard({ function ExampleTile({
onClick, onClick,
title, title,
description, description,
@@ -35,6 +39,7 @@ function ExampleCard({
}: { }: {
onClick: () => void onClick: () => void
title: string title: string
/** Shown as the tooltip: the title alone names the example */
description: string description: string
picture: React.ReactNode picture: React.ReactNode
/** An icon on a tinted tile instead of a diagram on paper */ /** An icon on a tinted tile instead of a diagram on paper */
@@ -45,24 +50,20 @@ function ExampleCard({
<button <button
type="button" type="button"
onClick={onClick} onClick={onClick}
className="group/example flex min-w-0 items-center gap-2.5 rounded-xl border border-border bg-card p-1.5 pr-2.5 text-left transition-colors hover:border-foreground/25" title={description}
className="group/example flex min-w-0 flex-col gap-2 rounded-xl border border-border bg-card p-1.5 pb-2.5 text-left transition-colors hover:border-foreground/25"
data-testid={testId} data-testid={testId}
> >
<span <span
className={cn( className={cn(
"flex h-12 w-16 shrink-0 items-center justify-center overflow-hidden rounded-lg", "flex h-16 w-full items-center justify-center overflow-hidden rounded-lg",
plain ? "bg-muted" : "sheet-light bg-white", plain ? "bg-muted" : "sheet-light bg-white",
)} )}
> >
{picture} {picture}
</span> </span>
<span className="min-w-0 flex-1"> <span className="px-1 text-[13px] leading-snug font-medium text-foreground">
<span className="line-clamp-2 text-[13px] leading-snug font-medium text-foreground"> {title}
{title}
</span>
<span className="mt-0.5 block truncate text-xs text-muted-foreground">
{description}
</span>
</span> </span>
</button> </button>
) )
@@ -92,6 +93,7 @@ export function LobbyHero({
key: "paper", key: "paper",
title: dict.examples.paperToDiagram, title: dict.examples.paperToDiagram,
description: dict.examples.paperDescription, description: dict.examples.paperDescription,
image: "/paper_demo.svg",
prompt: "Summarize this paper as a diagram", prompt: "Summarize this paper as a diagram",
file: { file: {
path: "/chain-of-thought.txt", path: "/chain-of-thought.txt",
@@ -185,10 +187,12 @@ export function LobbyHero({
placeholder={t.placeholder} placeholder={t.placeholder}
/> />
{/* Examples: one row of tiles; the ways to bring your own
diagram follow as a separate group */}
<div <div
className={cn( className={cn(
"mt-5 grid gap-2", "mt-5 grid gap-2",
compact ? "grid-cols-1" : "grid-cols-3", compact ? "grid-cols-1" : "grid-cols-5",
)} )}
> >
{examples.map((example) => {examples.map((example) =>
@@ -209,7 +213,7 @@ export function LobbyHero({
</span> </span>
</button> </button>
) : ( ) : (
<ExampleCard <ExampleTile
key={example.key} key={example.key}
onClick={() => pickExample(example)} onClick={() => pickExample(example)}
title={example.title} title={example.title}
@@ -221,7 +225,7 @@ export function LobbyHero({
<img <img
src={getAssetUrl(example.image)} src={getAssetUrl(example.image)}
alt="" alt=""
className="max-h-10 max-w-[56px] object-contain transition-transform duration-300 group-hover/example:scale-[1.06]" className="max-h-14 max-w-[88%] object-contain transition-transform duration-300 group-hover/example:scale-[1.06]"
/> />
) : ( ) : (
<FileText className="size-5 text-muted-foreground" /> <FileText className="size-5 text-muted-foreground" />
@@ -231,48 +235,34 @@ export function LobbyHero({
/> />
), ),
)} )}
{!compact && (
<ExampleCard
onClick={onOpenFile}
title={dict.workspace.openFile}
description={t.openFileHint}
picture={
<FileUp className="size-5 text-muted-foreground" />
}
plain
/>
)}
</div> </div>
<p className="mt-3 text-center text-xs text-faint"> <p className="mt-2.5 text-center text-xs text-faint">
{dict.examples.cachedNote} {dict.examples.cachedNote}
</p> </p>
<div className="mt-6 flex flex-wrap items-center justify-center gap-x-5 gap-y-2 text-[13px] text-muted-foreground"> {/* Ways to start without an example, as plain buttons */}
<div className="mt-6 flex flex-wrap items-center justify-center gap-2">
{engine.sessions.length > 0 && ( {engine.sessions.length > 0 && (
<SessionMenu align="center" onOpenFile={onOpenFile}> <SessionMenu align="center" onOpenFile={onOpenFile}>
<button <button type="button" className={startButtonClass}>
type="button"
className="inline-flex items-center gap-1.5 hover:text-foreground"
>
<History className="size-4" /> <History className="size-4" />
{t.recent} {t.recent}
</button> </button>
</SessionMenu> </SessionMenu>
)} )}
{compact && ( <button
<button type="button"
type="button" onClick={onOpenFile}
onClick={onOpenFile} className={startButtonClass}
className="inline-flex items-center gap-1.5 hover:text-foreground" data-testid="open-file"
> >
<FileUp className="size-4" /> <FileUp className="size-4" />
{dict.workspace.openFile} {dict.workspace.openFile}
</button> </button>
)}
<button <button
type="button" type="button"
onClick={onDrawYourself} onClick={onDrawYourself}
className="inline-flex items-center gap-1.5 hover:text-foreground" className={startButtonClass}
data-testid="draw-yourself" data-testid="draw-yourself"
> >
<Pencil className="size-4" /> <Pencil className="size-4" />
+26 -26
View File
@@ -2,7 +2,6 @@ import {
AlertCircle, AlertCircle,
Check, Check,
ChevronDown, ChevronDown,
Code2,
Copy, Copy,
Library, Library,
PenTool, PenTool,
@@ -180,17 +179,23 @@ function VersionCard({
</button> </button>
) )
// What the AI wrote, on every card (main showed it on every tool call) // What the AI wrote: a text link in the summary line, so the card's
const codeButton = ( // only button is the action (undo or restore)
<button const summaryLine = (
type="button" <div className="text-xs text-muted-foreground">
onClick={() => setShowCode((v) => !v)} {summary}
aria-label={showCode ? t.hideCode : t.showCode} {/* The link wraps as one unit with its separator */}
title={showCode ? t.hideCode : t.showCode} <span className="whitespace-nowrap">
className="inline-flex size-7 shrink-0 items-center justify-center rounded-lg text-muted-foreground hover:bg-accent hover:text-foreground" <span aria-hidden> · </span>
> <button
<Code2 className="size-4" /> type="button"
</button> onClick={() => setShowCode((v) => !v)}
className="underline decoration-foreground/30 underline-offset-2 hover:text-foreground hover:decoration-foreground"
>
{showCode ? t.hideCode : t.showCode}
</button>
</span>
</div>
) )
const codePanel = showCode && ( const codePanel = showCode && (
<div className="relative border-t border-border bg-surface-1 px-3 py-2.5"> <div className="relative border-t border-border bg-surface-1 px-3 py-2.5">
@@ -231,15 +236,12 @@ function VersionCard({
/> />
</button> </button>
{badge} {badge}
<div className="min-w-0 flex-1" title={summary}> <div className="min-w-0 flex-1">
<div className="truncate text-[13px] text-foreground"> <div className="truncate text-[13px] text-foreground">
{title} {title}
</div> </div>
<div className="truncate text-xs text-muted-foreground"> {summaryLine}
{summary}
</div>
</div> </div>
{codeButton}
{restoreButton} {restoreButton}
</div> </div>
{codePanel} {codePanel}
@@ -271,17 +273,12 @@ function VersionCard({
{t.compare} {t.compare}
</span> </span>
</button> </button>
<div className="flex items-center gap-2.5 px-3 py-2.5"> {/* Title row with the action; the summary runs under both */}
<div className="grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-x-2.5 px-3 py-2.5">
{badge} {badge}
<div className="min-w-0 flex-1" title={summary}> <div className="truncate text-[13px] font-medium text-foreground">
<div className="truncate text-[13px] font-medium text-foreground"> {title}
{title}
</div>
<div className="line-clamp-2 text-xs text-muted-foreground">
{summary}
</div>
</div> </div>
{codeButton}
{canUndo ? ( {canUndo ? (
<button <button
type="button" type="button"
@@ -300,6 +297,9 @@ function VersionCard({
) : ( ) : (
restoreButton restoreButton
)} )}
<div className="col-span-2 col-start-2 mt-0.5">
{summaryLine}
</div>
</div> </div>
{codePanel} {codePanel}
</div> </div>
+2 -2
View File
@@ -220,14 +220,14 @@ export function ModelSelector({
<span className="min-w-0 truncate text-xs"> <span className="min-w-0 truncate text-xs">
{selectedModel {selectedModel
? shortModelName(selectedModel.modelId) ? shortModelName(selectedModel.modelId)
: dict.modelConfig.default} : dict.modelConfig.serverDefault}
</span> </span>
) : ( ) : (
// Keep an sr-only label for screen readers when hidden // Keep an sr-only label for screen readers when hidden
<span className="sr-only"> <span className="sr-only">
{selectedModel {selectedModel
? selectedModel.modelId ? selectedModel.modelId
: dict.modelConfig.default} : dict.modelConfig.serverDefault}
</span> </span>
)} )}
<ChevronDown className="h-3 w-3 flex-shrink-0 text-muted-foreground" /> <ChevronDown className="h-3 w-3 flex-shrink-0 text-muted-foreground" />
+2 -2
View File
@@ -1,6 +1,6 @@
"use client" "use client"
import { MessageSquare, PanelRightOpen, Settings2, Shapes } from "lucide-react" import { MessageSquare, PanelRightOpen, Settings, Shapes } from "lucide-react"
import { usePathname, useRouter } from "next/navigation" import { usePathname, useRouter } from "next/navigation"
import { useEffect, useRef, useState } from "react" import { useEffect, useRef, useState } from "react"
import { CanvasStage } from "@/components/canvas/canvas-stage" import { CanvasStage } from "@/components/canvas/canvas-stage"
@@ -100,7 +100,7 @@ function StartBar({
onClick={() => openSettings()} onClick={() => openSettings()}
data-testid="settings-button" data-testid="settings-button"
> >
<Settings2 /> <Settings />
</IconButton> </IconButton>
</div> </div>
) )
+133 -125
View File
@@ -740,146 +740,154 @@ export const CACHED_EXAMPLE_RESPONSES: CachedResponse[] = [
{ {
promptText: "Draw a cat for me", promptText: "Draw a cat for me",
hasImage: false, hasImage: false,
xml: `<mxCell id="2" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#FFE6CC;strokeColor=#000000;strokeWidth=2;" vertex="1" parent="1"> xml: `<mxCell id="2" edge="1" parent="1" style="curved=1;endArrow=none;html=1;strokeWidth=14;strokeColor=#F4A460;" value="">
<mxGeometry x="300" y="150" width="120" height="120" as="geometry"/> <mxGeometry relative="1" as="geometry">
</mxCell>
<mxCell id="3" value="" style="triangle;whiteSpace=wrap;html=1;fillColor=#FFE6CC;strokeColor=#000000;strokeWidth=2;rotation=30;" vertex="1" parent="1">
<mxGeometry x="280" y="120" width="50" height="60" as="geometry"/>
</mxCell>
<mxCell id="4" value="" style="triangle;whiteSpace=wrap;html=1;fillColor=#FFE6CC;strokeColor=#000000;strokeWidth=2;rotation=-30;" vertex="1" parent="1">
<mxGeometry x="390" y="120" width="50" height="60" as="geometry"/>
</mxCell>
<mxCell id="5" value="" style="triangle;whiteSpace=wrap;html=1;fillColor=#FFB6C1;strokeColor=none;rotation=30;" vertex="1" parent="1">
<mxGeometry x="290" y="135" width="30" height="35" as="geometry"/>
</mxCell>
<mxCell id="6" value="" style="triangle;whiteSpace=wrap;html=1;fillColor=#FFB6C1;strokeColor=none;rotation=-30;" vertex="1" parent="1">
<mxGeometry x="400" y="135" width="30" height="35" as="geometry"/>
</mxCell>
<mxCell id="7" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#000000;strokeColor=#000000;" vertex="1" parent="1">
<mxGeometry x="325" y="185" width="15" height="15" as="geometry"/>
</mxCell>
<mxCell id="8" value="" style="ellipse;whiteSpace=wrap;html=1;aspect=fixed;fillColor=#000000;strokeColor=#000000;" vertex="1" parent="1">
<mxGeometry x="380" y="185" width="15" height="15" as="geometry"/>
</mxCell>
<mxCell id="9" value="" style="triangle;whiteSpace=wrap;html=1;fillColor=#FFB6C1;strokeColor=#000000;strokeWidth=1;rotation=180;" vertex="1" parent="1">
<mxGeometry x="350" y="210" width="20" height="15" as="geometry"/>
</mxCell>
<mxCell id="10" value="" style="curved=1;endArrow=none;html=1;strokeColor=#000000;strokeWidth=2;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="360" y="220" as="sourcePoint"/>
<mxPoint x="340" y="235" as="targetPoint"/>
<Array as="points"> <Array as="points">
<mxPoint x="355" y="230"/> <mxPoint x="580" y="480"/>
<mxPoint x="600" y="380"/>
</Array> </Array>
<mxPoint x="490" y="480" as="sourcePoint"/>
<mxPoint x="560" y="330" as="targetPoint"/>
</mxGeometry> </mxGeometry>
</mxCell> </mxCell>
<mxCell id="11" value="" style="curved=1;endArrow=none;html=1;strokeColor=#000000;strokeWidth=2;" edge="1" parent="1"> <mxCell id="3" parent="1" style="ellipse;whiteSpace=wrap;html=1;fillColor=#F4A460;strokeColor=#8B5A2B;strokeWidth=2;" value="" vertex="1">
<mxGeometry width="50" height="50" relative="1" as="geometry"> <mxGeometry height="190" width="200" x="320" y="320" as="geometry"/>
<mxPoint x="360" y="220" as="sourcePoint"/> </mxCell>
<mxPoint x="380" y="235" as="targetPoint"/>
<mxCell id="4" parent="1" style="ellipse;whiteSpace=wrap;html=1;fillColor=#FFE4C4;strokeColor=none;" value="" vertex="1">
<mxGeometry height="120" width="100" x="370" y="360" as="geometry"/>
</mxCell>
<mxCell id="5" parent="1" style="ellipse;whiteSpace=wrap;html=1;fillColor=#F4A460;strokeColor=#8B5A2B;strokeWidth=2;" value="" vertex="1">
<mxGeometry height="40" width="60" x="350" y="480" as="geometry"/>
</mxCell>
<mxCell id="6" parent="1" style="ellipse;whiteSpace=wrap;html=1;fillColor=#F4A460;strokeColor=#8B5A2B;strokeWidth=2;" value="" vertex="1">
<mxGeometry height="40" width="60" x="430" y="480" as="geometry"/>
</mxCell>
<mxCell id="7" parent="1" style="triangle;direction=north;whiteSpace=wrap;html=1;fillColor=#F4A460;strokeColor=#8B5A2B;strokeWidth=2;rotation=-15;" value="" vertex="1">
<mxGeometry height="80" width="70" x="310" y="110" as="geometry"/>
</mxCell>
<mxCell id="8" parent="1" style="triangle;direction=north;whiteSpace=wrap;html=1;fillColor=#F4A460;strokeColor=#8B5A2B;strokeWidth=2;rotation=15;" value="" vertex="1">
<mxGeometry height="80" width="70" x="460" y="110" as="geometry"/>
</mxCell>
<mxCell id="9" parent="1" style="triangle;direction=north;whiteSpace=wrap;html=1;fillColor=#FFB6C1;strokeColor=none;rotation=-15;" value="" vertex="1">
<mxGeometry height="45" width="36" x="327" y="135" as="geometry"/>
</mxCell>
<mxCell id="10" parent="1" style="triangle;direction=north;whiteSpace=wrap;html=1;fillColor=#FFB6C1;strokeColor=none;rotation=15;" value="" vertex="1">
<mxGeometry height="45" width="36" x="477" y="135" as="geometry"/>
</mxCell>
<mxCell id="11" parent="1" style="ellipse;whiteSpace=wrap;html=1;fillColor=#F4A460;strokeColor=#8B5A2B;strokeWidth=2;" value="" vertex="1">
<mxGeometry height="200" width="240" x="300" y="140" as="geometry"/>
</mxCell>
<mxCell id="12" parent="1" style="ellipse;whiteSpace=wrap;html=1;fillColor=#333333;strokeColor=none;" value="" vertex="1">
<mxGeometry height="44" width="36" x="355" y="200" as="geometry"/>
</mxCell>
<mxCell id="13" parent="1" style="ellipse;whiteSpace=wrap;html=1;fillColor=#333333;strokeColor=none;" value="" vertex="1">
<mxGeometry height="44" width="36" x="449" y="200" as="geometry"/>
</mxCell>
<mxCell id="14" parent="1" style="ellipse;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=none;" value="" vertex="1">
<mxGeometry height="13" width="13" x="363" y="207" as="geometry"/>
</mxCell>
<mxCell id="15" parent="1" style="ellipse;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=none;" value="" vertex="1">
<mxGeometry height="13" width="13" x="457" y="207" as="geometry"/>
</mxCell>
<mxCell id="16" parent="1" style="ellipse;whiteSpace=wrap;html=1;fillColor=#FFB6C1;strokeColor=none;opacity=70;" value="" vertex="1">
<mxGeometry height="22" width="40" x="325" y="255" as="geometry"/>
</mxCell>
<mxCell id="17" parent="1" style="ellipse;whiteSpace=wrap;html=1;fillColor=#FFB6C1;strokeColor=none;opacity=70;" value="" vertex="1">
<mxGeometry height="22" width="40" x="475" y="255" as="geometry"/>
</mxCell>
<mxCell id="18" parent="1" style="triangle;direction=south;whiteSpace=wrap;html=1;fillColor=#FF69B4;strokeColor=none;" value="" vertex="1">
<mxGeometry height="16" width="24" x="408" y="252" as="geometry"/>
</mxCell>
<mxCell id="19" edge="1" parent="1" style="curved=1;endArrow=none;html=1;strokeWidth=2;strokeColor=#333333;" value="">
<mxGeometry relative="1" as="geometry">
<Array as="points"> <Array as="points">
<mxPoint x="365" y="230"/> <mxPoint x="410" y="288"/>
</Array> </Array>
<mxPoint x="400" y="278" as="sourcePoint"/>
<mxPoint x="420" y="270" as="targetPoint"/>
</mxGeometry> </mxGeometry>
</mxCell> </mxCell>
<mxCell id="12" value="" style="endArrow=none;html=1;strokeColor=#000000;strokeWidth=1.5;" edge="1" parent="1"> <mxCell id="20" edge="1" parent="1" style="curved=1;endArrow=none;html=1;strokeWidth=2;strokeColor=#333333;" value="">
<mxGeometry width="50" height="50" relative="1" as="geometry"> <mxGeometry relative="1" as="geometry">
<mxPoint x="310" y="200" as="sourcePoint"/>
<mxPoint x="260" y="195" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="13" value="" style="endArrow=none;html=1;strokeColor=#000000;strokeWidth=1.5;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="310" y="210" as="sourcePoint"/>
<mxPoint x="260" y="210" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="14" value="" style="endArrow=none;html=1;strokeColor=#000000;strokeWidth=1.5;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="310" y="220" as="sourcePoint"/>
<mxPoint x="260" y="225" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="15" value="" style="endArrow=none;html=1;strokeColor=#000000;strokeWidth=1.5;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="410" y="200" as="sourcePoint"/>
<mxPoint x="460" y="195" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="16" value="" style="endArrow=none;html=1;strokeColor=#000000;strokeWidth=1.5;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="410" y="210" as="sourcePoint"/>
<mxPoint x="460" y="210" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="17" value="" style="endArrow=none;html=1;strokeColor=#000000;strokeWidth=1.5;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="410" y="220" as="sourcePoint"/>
<mxPoint x="460" y="225" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="18" value="" style="ellipse;whiteSpace=wrap;html=1;fillColor=#FFE6CC;strokeColor=#000000;strokeWidth=2;" vertex="1" parent="1">
<mxGeometry x="285" y="250" width="150" height="180" as="geometry"/>
</mxCell>
<mxCell id="19" value="" style="ellipse;whiteSpace=wrap;html=1;fillColor=#FFFFFF;strokeColor=none;" vertex="1" parent="1">
<mxGeometry x="315" y="280" width="90" height="120" as="geometry"/>
</mxCell>
<mxCell id="20" value="" style="ellipse;whiteSpace=wrap;html=1;fillColor=#FFE6CC;strokeColor=#000000;strokeWidth=2;" vertex="1" parent="1">
<mxGeometry x="300" y="410" width="40" height="50" as="geometry"/>
</mxCell>
<mxCell id="21" value="" style="ellipse;whiteSpace=wrap;html=1;fillColor=#FFE6CC;strokeColor=#000000;strokeWidth=2;" vertex="1" parent="1">
<mxGeometry x="380" y="410" width="40" height="50" as="geometry"/>
</mxCell>
<mxCell id="22" value="" style="curved=1;endArrow=none;html=1;strokeColor=#000000;strokeWidth=3;fillColor=#FFE6CC;" edge="1" parent="1">
<mxGeometry width="50" height="50" relative="1" as="geometry">
<mxPoint x="285" y="340" as="sourcePoint"/>
<mxPoint x="240" y="260" as="targetPoint"/>
<Array as="points"> <Array as="points">
<mxPoint x="260" y="350"/> <mxPoint x="430" y="288"/>
<mxPoint x="240" y="320"/>
<mxPoint x="235" y="290"/>
</Array> </Array>
<mxPoint x="420" y="270" as="sourcePoint"/>
<mxPoint x="440" y="278" as="targetPoint"/>
</mxGeometry> </mxGeometry>
</mxCell>
<mxCell id="21" edge="1" parent="1" style="endArrow=none;html=1;strokeWidth=2;strokeColor=#5C3A1A;" value="">
<mxGeometry relative="1" as="geometry">
<mxPoint x="370" y="262" as="sourcePoint"/>
<mxPoint x="270" y="245" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="22" edge="1" parent="1" style="endArrow=none;html=1;strokeWidth=2;strokeColor=#5C3A1A;" value="">
<mxGeometry relative="1" as="geometry">
<mxPoint x="370" y="272" as="sourcePoint"/>
<mxPoint x="268" y="278" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="23" edge="1" parent="1" style="endArrow=none;html=1;strokeWidth=2;strokeColor=#5C3A1A;" value="">
<mxGeometry relative="1" as="geometry">
<mxPoint x="470" y="262" as="sourcePoint"/>
<mxPoint x="570" y="245" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="24" edge="1" parent="1" style="endArrow=none;html=1;strokeWidth=2;strokeColor=#5C3A1A;" value="">
<mxGeometry relative="1" as="geometry">
<mxPoint x="470" y="272" as="sourcePoint"/>
<mxPoint x="572" y="278" as="targetPoint"/>
</mxGeometry>
</mxCell>
<mxCell id="25" parent="1" style="text;html=1;align=center;verticalAlign=middle;fontSize=22;fontStyle=1;fontColor=#FF69B4;" value="Meow~ ♥" vertex="1">
<mxGeometry height="40" width="120" x="530" y="120" as="geometry"/>
</mxCell>`, </mxCell>`,
}, },
] ]
+66 -66
View File
@@ -72,7 +72,7 @@
"replicateDescription": "Upload and replicate an existing flowchart", "replicateDescription": "Upload and replicate an existing flowchart",
"creativeDrawing": "Creative drawing", "creativeDrawing": "Creative drawing",
"creativeDescription": "Draw something fun and creative", "creativeDescription": "Draw something fun and creative",
"cachedNote": "Examples open instantly and don't use your model.", "cachedNote": "Examples use saved answers: they appear instantly and don't call the AI.",
"mcpServer": "MCP Server", "mcpServer": "MCP Server",
"mcpDescription": "Use in Claude Desktop, VS Code & Cursor" "mcpDescription": "Use in Claude Desktop, VS Code & Cursor"
}, },
@@ -96,7 +96,7 @@
"language": "Language", "language": "Language",
"languageDescription": "Choose your interface language.", "languageDescription": "Choose your interface language.",
"theme": "Theme", "theme": "Theme",
"themeDescription": "Light or dark interface. The diagram always stays on a white sheet, the way it exports.", "themeDescription": "Light or dark interface. The canvas follows the same theme.",
"diagramStyle": "Diagram style", "diagramStyle": "Diagram style",
"diagramStyleDescription": "How the AI draws new diagrams: Styled uses colors and icons, Minimal keeps shapes plain.", "diagramStyleDescription": "How the AI draws new diagrams: Styled uses colors and icons, Minimal keeps shapes plain.",
"sendShortcut": "Send shortcut", "sendShortcut": "Send shortcut",
@@ -118,7 +118,7 @@
"customSystemMessage": "Custom instructions", "customSystemMessage": "Custom instructions",
"customSystemMessageDescription": "Add custom instructions appended to the AI's system prompt.", "customSystemMessageDescription": "Add custom instructions appended to the AI's system prompt.",
"customSystemMessagePlaceholder": "e.g., Always use blue color scheme for diagrams...", "customSystemMessagePlaceholder": "e.g., Always use blue color scheme for diagrams...",
"maxOutputTokens": "Max output tokens", "maxOutputTokens": "Output token limit",
"maxOutputTokensDescription": "Budget for one reply, shared by thinking and the diagram XML. Raise it if the AI keeps thinking and no diagram appears. Leave empty for the default.", "maxOutputTokensDescription": "Budget for one reply, shared by thinking and the diagram XML. Raise it if the AI keeps thinking and no diagram appears. Leave empty for the default.",
"tabModels": "Models", "tabModels": "Models",
"tabAbout": "About", "tabAbout": "About",
@@ -399,7 +399,7 @@
"searchModels": "Search models...", "searchModels": "Search models...",
"noModelsFound": "No models found.", "noModelsFound": "No models found.",
"default": "Default", "default": "Default",
"serverDefault": "Server Default", "serverDefault": "Server model",
"serverModels": "Server Models", "serverModels": "Server Models",
"userModels": "User Models", "userModels": "User Models",
"configureModels": "Configure Models...", "configureModels": "Configure Models...",
@@ -482,46 +482,46 @@
}, },
"admin": { "admin": {
"title": "Admin Settings", "title": "Admin Settings",
"loginPrompt": "Enter the admin password (the ADMIN_PASSWORD environment variable) to manage server settings.", "loginPrompt": "Enter the admin password (the ADMIN_PASSWORD environment variable)",
"password": "Password", "password": "Password",
"signIn": "Sign In", "signIn": "Sign In",
"signingIn": "Signing In…", "signingIn": "Signing in…",
"loginFailed": "Login failed", "loginFailed": "Login failed",
"precedence": "File overrides env · env overrides defaults", "precedence": "Priority: this page > environment variables > defaults",
"notWritable": "The settings file is not writable on this deployment (serverless platforms have no persistent disk). Settings are shown read-only — configure via environment variables instead.", "notWritable": "Settings can't be saved on this deployment, only viewed; use environment variables instead",
"settingGroups": "Setting groups", "settingGroups": "Setting groups",
"enabled": "Enabled", "enabled": "On",
"disabled": "Disabled", "disabled": "Off",
"enableGroup": "Enable {group}", "enableGroup": "Turn on {group}",
"unsavedChanges": "Unsaved changes", "unsavedChanges": "Unsaved changes",
"saved": "Settings saved. Changes apply immediately.", "saved": "Saved. Changes apply right away, except items marked \"Needs restart\"",
"saveFailed": "Save failed. Check your connection and try again.", "saveFailed": "Save failed. Check your connection and try again",
"invalidSettings": "Some settings are invalid.", "invalidSettings": "Some settings are invalid; see the notes under them",
"discard": "Discard", "discard": "Discard changes",
"saveChanges": "Save Changes", "saveChanges": "Save changes",
"saving": "Saving…", "saving": "Saving…",
"sourceSaved": "Saved", "sourceSaved": "This page",
"sourceEnv": "Env", "sourceEnv": "Env",
"sourceSavedTitle": "Set in the admin settings file", "sourceSavedTitle": "Set on this page",
"sourceEnvTitle": "Set by an environment variable", "sourceEnvTitle": "From an environment variable",
"restartRequired": "Restart Required", "restartRequired": "Needs restart",
"modified": "Modified", "modified": "Modified",
"notSet": "Not set", "notSet": "Not set",
"savedReplace": "Saved ({hint}) — type to replace", "savedReplace": "Saved ({hint}); type a new value to replace it",
"showValue": "Show value", "showValue": "Show",
"hideValue": "Hide value", "hideValue": "Hide",
"removeValue": "Remove value", "removeValue": "Clear",
"removeValueTitle": "Remove the stored value", "removeValueTitle": "Clear the saved value",
"resetToDefault": "Reset to default", "resetToDefault": "Reset to default",
"models": "Models", "models": "Models",
"modelsDescription": "Server-side providers and models available to all users — no personal API key needed. The default provider's first model is used when users don't pick one.", "modelsDescription": "Models every user can use, no personal API key needed",
"addProviderHint": "Add a provider to offer server-side models to all users.", "addProviderHint": "Add a provider to offer models to all users",
"selectProviderHint": "Select or add a provider to configure its credentials and models.", "selectProviderHint": "Select or add a provider, then enter its key and models",
"addProviderToOfferModels": "Add at least one model to expose this provider to users.", "addProviderToOfferModels": "Add at least one model before users can see this provider",
"managedViaEnv": "(managed via env)", "managedViaEnv": "(environment variables)",
"envReadOnly": "Defined in AI_MODELS_CONFIG / ai-models.json — read-only here. Edit the environment configuration to change it.", "envReadOnly": "Defined in AI_MODELS_CONFIG / ai-models.json; view only here, edit it there",
"defaultModel": "Default Model", "defaultModel": "Default Model",
"noModelsConfigured": "No models configured", "noModelsConfigured": "No models yet",
"modelCount": "{count} model", "modelCount": "{count} model",
"modelCountPlural": "{count} models", "modelCountPlural": "{count} models",
"default": "Default", "default": "Default",
@@ -531,65 +531,65 @@
"addModel": "Add model", "addModel": "Add model",
"suggested": "Suggested", "suggested": "Suggested",
"test": "Test", "test": "Test",
"testOk": "OK ({ms}ms)", "testOk": "Passed ({ms} ms)",
"testFailed": "Failed", "testFailed": "Failed",
"removeModel": "Remove {model}", "removeModel": "Delete {model}",
"deleteProviderTitle": "Delete {name}?", "deleteProviderTitle": "Delete {name}?",
"deleteProviderDesc": "Its credentials and models will be removed from the server after you save.", "deleteProviderDesc": "After you save, its key and models are deleted from the server",
"cancel": "Cancel", "cancel": "Cancel",
"delete": "Delete", "delete": "Delete",
"groups": { "groups": {
"generation": { "generation": {
"title": "Generation", "title": "Replies",
"description": "Output parameters applied to all chat requests." "description": "How the AI replies, for every conversation"
}, },
"access": { "access": {
"title": "Access Control", "title": "Access",
"description": "Restrict who can use this deployment." "description": "Limit who can use this site"
}, },
"features": { "features": {
"title": "Features", "title": "Optional Features",
"description": "Optional features and security toggles." "description": "Extra features and safety options"
}, },
"observability": { "observability": {
"title": "Observability", "title": "Call Logs",
"description": "Langfuse tracing for LLM calls." "description": "Record model calls with Langfuse"
}, },
"quota": { "quota": {
"title": "Quota & Rate Limits", "title": "Usage Limits",
"description": "Per-IP usage limits. Enforcement requires a DynamoDB table." "description": "Per-IP limits; they take effect only with a DynamoDB table"
} }
}, },
"settings": { "settings": {
"TEMPERATURE": { "TEMPERATURE": {
"label": "Temperature", "label": "Temperature",
"description": "Leave unset for reasoning models that reject temperature." "description": "Higher is more random; leave empty for reasoning models that don't support it"
}, },
"MAX_OUTPUT_TOKENS": { "MAX_OUTPUT_TOKENS": {
"label": "Max Output Tokens" "label": "Output Token Limit"
}, },
"ACCESS_CODE_LIST": { "ACCESS_CODE_LIST": {
"label": "Access Codes", "label": "Access Codes",
"description": "Comma-separated list. Users must enter one to chat. Empty = open access." "description": "Separate several with commas; users must enter one to chat; empty means no limit"
}, },
"ENABLE_VLM_VALIDATION": { "ENABLE_VLM_VALIDATION": {
"label": "VLM Diagram Validation", "label": "Diagram Check",
"description": "Visually validate generated diagrams with a vision model." "description": "After drawing, a model that can see images checks a screenshot"
}, },
"VALIDATION_MODEL": { "VALIDATION_MODEL": {
"label": "Validation Model", "label": "Check Model",
"description": "Falls back to the default AI model when empty." "description": "Empty means the default model"
}, },
"VALIDATION_TIMEOUT": { "VALIDATION_TIMEOUT": {
"label": "Validation Timeout (ms)" "label": "Check Timeout (ms)"
}, },
"ENABLE_HISTORY_XML_REPLACE": { "ENABLE_HISTORY_XML_REPLACE": {
"label": "History XML Compression", "label": "Trim Old Diagrams",
"description": "Replace old diagram XML in history with placeholders." "description": "Old diagrams in the chat history are kept as a short marker"
}, },
"ALLOW_PRIVATE_URLS": { "ALLOW_PRIVATE_URLS": {
"label": "Allow Private URLs", "label": "Allow Private URLs",
"description": "Turn off to block requests to private IPs and internal hostnames (SSRF protection)." "description": "When off, custom model URLs can't point to private networks; safer"
}, },
"LANGFUSE_PUBLIC_KEY": { "LANGFUSE_PUBLIC_KEY": {
"label": "Langfuse Public Key" "label": "Langfuse Public Key"
@@ -598,29 +598,29 @@
"label": "Langfuse Secret Key" "label": "Langfuse Secret Key"
}, },
"LANGFUSE_BASEURL": { "LANGFUSE_BASEURL": {
"label": "Langfuse Base URL" "label": "Langfuse URL"
}, },
"DAILY_REQUEST_LIMIT": { "DAILY_REQUEST_LIMIT": {
"label": "Daily Request Limit", "label": "Daily Requests",
"description": "Per IP per day." "description": "Requests per IP per day"
}, },
"DAILY_TOKEN_LIMIT": { "DAILY_TOKEN_LIMIT": {
"label": "Daily Token Limit", "label": "Daily Tokens",
"description": "Per IP per day." "description": "Tokens per IP per day"
}, },
"TPM_LIMIT": { "TPM_LIMIT": {
"label": "Tokens Per Minute" "label": "Tokens Per Minute"
}, },
"DYNAMODB_QUOTA_TABLE": { "DYNAMODB_QUOTA_TABLE": {
"label": "DynamoDB Table", "label": "DynamoDB Table Name",
"description": "Quota enforcement is disabled when empty." "description": "Empty means no usage limits"
}, },
"DYNAMODB_REGION": { "DYNAMODB_REGION": {
"label": "DynamoDB Region" "label": "DynamoDB Region"
}, },
"QUOTA_TIMEZONE": { "QUOTA_TIMEZONE": {
"label": "Quota Timezone", "label": "Time Zone",
"description": "Timezone for the daily reset boundary." "description": "Daily usage resets at midnight in this time zone"
} }
} }
}, },
@@ -694,7 +694,7 @@
}, },
"lobby": { "lobby": {
"title": "What would you like to draw?", "title": "What would you like to draw?",
"lede": "Describe a diagram, or upload a screenshot or PDF. You get a regular draw.io diagram that you can keep editing and export.", "lede": "Describe a diagram or upload a screenshot or PDF. You get a draw.io diagram you can keep editing and export.",
"placeholder": "For example: a login sequence diagram with SMS codes and retries", "placeholder": "For example: a login sequence diagram with SMS codes and retries",
"recent": "Recent diagrams", "recent": "Recent diagrams",
"drawYourself": "Start with a blank canvas", "drawYourself": "Start with a blank canvas",
+66 -66
View File
@@ -72,7 +72,7 @@
"replicateDescription": "既存のフローチャートをアップロードして複製", "replicateDescription": "既存のフローチャートをアップロードして複製",
"creativeDrawing": "自由なイラスト", "creativeDrawing": "自由なイラスト",
"creativeDescription": "楽しくてクリエイティブなものを描く", "creativeDescription": "楽しくてクリエイティブなものを描く",
"cachedNote": "例はすぐに開き、モデルは使いません。", "cachedNote": "例は保存済みの回答を使うので、すぐに表示され、AI を呼び出しません。",
"mcpServer": "MCP サーバー", "mcpServer": "MCP サーバー",
"mcpDescription": "Claude Desktop、VS Code、Cursor で使用" "mcpDescription": "Claude Desktop、VS Code、Cursor で使用"
}, },
@@ -96,7 +96,7 @@
"language": "言語", "language": "言語",
"languageDescription": "インターフェース言語を選択します。", "languageDescription": "インターフェース言語を選択します。",
"theme": "テーマ", "theme": "テーマ",
"themeDescription": "画面をライトにするかダークにするか。図はエクスポートしたときと同じく、常に白い用紙の上に表示されます。", "themeDescription": "画面をライトにするかダークにするか。キャンバスも同じテーマになります。",
"diagramStyle": "図のスタイル", "diagramStyle": "図のスタイル",
"diagramStyleDescription": "AI が新しい図を描くときのスタイル。スタイル付きは色やアイコンを使い、ミニマルは素朴な図形だけにします。", "diagramStyleDescription": "AI が新しい図を描くときのスタイル。スタイル付きは色やアイコンを使い、ミニマルは素朴な図形だけにします。",
"sendShortcut": "送信のショートカット", "sendShortcut": "送信のショートカット",
@@ -118,7 +118,7 @@
"customSystemMessage": "カスタム指示", "customSystemMessage": "カスタム指示",
"customSystemMessageDescription": "AIのシステムプロンプトに追加されるカスタム指示を入力します。", "customSystemMessageDescription": "AIのシステムプロンプトに追加されるカスタム指示を入力します。",
"customSystemMessagePlaceholder": "例:ダイアグラムには常に青色のカラースキームを使用...", "customSystemMessagePlaceholder": "例:ダイアグラムには常に青色のカラースキームを使用...",
"maxOutputTokens": "最大出力トークン数", "maxOutputTokens": "出力トークン上限",
"maxOutputTokensDescription": "1回の応答の予算で、思考過程とダイアグラムの XML が共有します。AI が考え続けてダイアグラムが生成されない場合は大きくしてください。空欄ならデフォルト値を使います。", "maxOutputTokensDescription": "1回の応答の予算で、思考過程とダイアグラムの XML が共有します。AI が考え続けてダイアグラムが生成されない場合は大きくしてください。空欄ならデフォルト値を使います。",
"tabModels": "モデル", "tabModels": "モデル",
"tabAbout": "このアプリについて", "tabAbout": "このアプリについて",
@@ -354,7 +354,7 @@
"searchModels": "モデルを検索...", "searchModels": "モデルを検索...",
"noModelsFound": "モデルが見つかりません。", "noModelsFound": "モデルが見つかりません。",
"default": "デフォルト", "default": "デフォルト",
"serverDefault": "サーバーデフォルト", "serverDefault": "サーバーのモデル",
"serverModels": "サーバーモデル", "serverModels": "サーバーモデル",
"userModels": "ユーザーモデル", "userModels": "ユーザーモデル",
"configureModels": "モデルを設定...", "configureModels": "モデルを設定...",
@@ -482,114 +482,114 @@
}, },
"admin": { "admin": {
"title": "管理者設定", "title": "管理者設定",
"loginPrompt": "サーバー設定を管理するには、管理者パスワード(ADMIN_PASSWORD 環境変数)を入力してください。", "loginPrompt": "管理者パスワード(環境変数 ADMIN_PASSWORD)を入力してください",
"password": "パスワード", "password": "パスワード",
"signIn": "ログイン", "signIn": "ログイン",
"signingIn": "ログイン中…", "signingIn": "ログイン中…",
"loginFailed": "ログインに失敗しました", "loginFailed": "ログインに失敗しました",
"precedence": "ファイルが環境変数を上書き · 環境変数がデフォルトを上書き", "precedence": "優先順位:このページ > 環境変数 > 既定値",
"notWritable": "このデプロイ環境では設定ファイルに書き込めません(サーバーレス環境には永続ディスクがありません)。設定は読み取り専用で表示されます——代わりに環境変数で構成してください。", "notWritable": "この環境では設定を保存できず、表示のみです。環境変数で設定してください",
"settingGroups": "設定グループ", "settingGroups": "設定の分類",
"enabled": "有効", "enabled": "オン",
"disabled": "無効", "disabled": "オフ",
"enableGroup": "{group} を有効化", "enableGroup": "{group} をオンにする",
"unsavedChanges": "未保存の変更があります", "unsavedChanges": "未保存の変更があります",
"saved": "設定を保存しました。変更は即座に反映されます。", "saved": "保存しました。「再起動が必要」の項目以外はすぐに反映されます",
"saveFailed": "保存に失敗しました。接続を確認して再試行してください。", "saveFailed": "保存に失敗しました。接続を確認してもう一度お試しください",
"invalidSettings": "一部の設定が無効です。", "invalidSettings": "入力に誤りがある設定があります。項目の下の説明を確認してください",
"discard": "破棄", "discard": "変更を破棄",
"saveChanges": "変更を保存", "saveChanges": "変更を保存",
"saving": "保存中…", "saving": "保存中…",
"sourceSaved": "保存済み", "sourceSaved": "このページ",
"sourceEnv": "環境変数", "sourceEnv": "環境変数",
"sourceSavedTitle": "管理者設定ファイルで設定", "sourceSavedTitle": "このページで設定した値",
"sourceEnvTitle": "環境変数で設定", "sourceEnvTitle": "環境変数による設定",
"restartRequired": "再起動が必要", "restartRequired": "再起動が必要",
"modified": "変更済み", "modified": "変更済み",
"notSet": "未設定", "notSet": "未設定",
"savedReplace": "保存済み({hint})——入力して置き換え", "savedReplace": "保存済み({hint})。新しい値を入力すると置き換わります",
"showValue": "値を表示", "showValue": "表示",
"hideValue": "値を非表示", "hideValue": "非表示",
"removeValue": "値を削除", "removeValue": "クリア",
"removeValueTitle": "保存された値を削除", "removeValueTitle": "保存した値をクリア",
"resetToDefault": "デフォルトに戻す", "resetToDefault": "デフォルトに戻す",
"models": "モデル", "models": "モデル",
"modelsDescription": "全ユーザーが利用できるサーバー側のプロバイダーとモデル——個人の API キーは不要です。ユーザーがモデルを選択しない場合、デフォルトプロバイダーの最初のモデルが使用されます。", "modelsDescription": "全ユーザーが使えるモデル。個人の API キーは不要です",
"addProviderHint": "プロバイダーを追加して、全ユーザーにサーバー側モデルを提供します。", "addProviderHint": "プロバイダーを追加して、全ユーザーにモデルを提供します",
"selectProviderHint": "プロバイダーを選択または追加して、その資格情報とモデルを構成します。", "selectProviderHint": "プロバイダーを選択または追加し、キーとモデルを入力します",
"addProviderToOfferModels": "ユーザーにこのプロバイダーを公開するには、モデルを少なくとも 1 つ追加してください。", "addProviderToOfferModels": "モデルを 1 つ以上追加すると、ユーザーにこのプロバイダーが表示されます",
"managedViaEnv": "(環境変数で管理)", "managedViaEnv": "(環境変数)",
"envReadOnly": "AI_MODELS_CONFIG / ai-models.json で定義——ここでは読み取り専用です。変更するには環境構成を編集してください。", "envReadOnly": "AI_MODELS_CONFIG / ai-models.json で定義されています。ここでは表示のみで、変更はそちらで行ってください",
"defaultModel": "デフォルトモデル", "defaultModel": "デフォルトモデル",
"noModelsConfigured": "モデルが構成されていません", "noModelsConfigured": "モデルはまだありません",
"modelCount": "{count} 個のモデル", "modelCount": "{count} 個のモデル",
"modelCountPlural": "{count} 個のモデル", "modelCountPlural": "{count} 個のモデル",
"default": "デフォルト", "default": "デフォルト",
"setAsDefault": "デフォルトプロバイダーに設定", "setAsDefault": "既定のプロバイダーにする",
"defaultProvider": "デフォルトプロバイダー", "defaultProvider": "既定のプロバイダー",
"modelIdPlaceholder": "モデル ID…", "modelIdPlaceholder": "モデル ID…",
"addModel": "モデルを追加", "addModel": "モデルを追加",
"suggested": "おすすめ", "suggested": "おすすめ",
"test": "テスト", "test": "テスト",
"testOk": "正常({ms}ms)", "testOk": "成功({ms} ms)",
"testFailed": "失敗", "testFailed": "失敗",
"removeModel": "{model} を削除", "removeModel": "{model} を削除",
"deleteProviderTitle": "{name} を削除しますか?", "deleteProviderTitle": "{name} を削除しますか?",
"deleteProviderDesc": "保存後、その資格情報とモデルはサーバーから削除されます。", "deleteProviderDesc": "保存すると、このプロバイダーのキーとモデルがサーバーから削除されます",
"cancel": "キャンセル", "cancel": "キャンセル",
"delete": "削除", "delete": "削除",
"groups": { "groups": {
"generation": { "generation": {
"title": "生成", "title": "応答の設定",
"description": "すべてのチャットリクエストに適用される出力パラメーター。" "description": "AI の応答のしかたを決めます。すべての会話に適用されます"
}, },
"access": { "access": {
"title": "アクセス制御", "title": "アクセス権",
"description": "このデプロイを使用できるユーザーを制限します。" "description": "このサイトを使える人を制限します"
}, },
"features": { "features": {
"title": "機能", "title": "オプション機能",
"description": "オプション機能とセキュリティの切り替え。" "description": "追加機能と安全のための設定"
}, },
"observability": { "observability": {
"title": "オブザーバビリティ", "title": "呼び出し記録",
"description": "LLM 呼び出しの Langfuse トレース。" "description": "モデルの呼び出しを Langfuse に記録します"
}, },
"quota": { "quota": {
"title": "クォータとレート制限", "title": "使用量の制限",
"description": "IP ごとの使用制限。強制には DynamoDB テーブルが必要です。" "description": "IP ごとの制限。DynamoDB テーブルがあるときだけ有効です"
} }
}, },
"settings": { "settings": {
"TEMPERATURE": { "TEMPERATURE": {
"label": "温度", "label": "温度",
"description": "温度を受け付けない推論モデルの場合は未設定のままにしてください。" "description": "高いほどランダムになります。温度に対応しない推論モデルでは空にしてください"
}, },
"MAX_OUTPUT_TOKENS": { "MAX_OUTPUT_TOKENS": {
"label": "最大出力トークン数" "label": "出力トークン上限"
}, },
"ACCESS_CODE_LIST": { "ACCESS_CODE_LIST": {
"label": "アクセスコード", "label": "アクセスコード",
"description": "カンマ区切りのリスト。チャットにはいずれかの入力が必要です。空 = オープンアクセス。" "description": "複数はカンマ区切り。いずれかを入力しないと使えません。空なら制限なし"
}, },
"ENABLE_VLM_VALIDATION": { "ENABLE_VLM_VALIDATION": {
"label": "VLM 図検証", "label": "図のチェック",
"description": "ビジョンモデルで生成された図を視覚的に検証します。" "description": "描画後、画像を読めるモデルがスクリーンショットをチェックします"
}, },
"VALIDATION_MODEL": { "VALIDATION_MODEL": {
"label": "検証モデル", "label": "チェック用モデル",
"description": "空の場合はデフォルトの AI モデルにフォールバックします。" "description": "空なら既定のモデルを使います"
}, },
"VALIDATION_TIMEOUT": { "VALIDATION_TIMEOUT": {
"label": "検証タイムアウト(ms)" "label": "チェックのタイムアウト(ms)"
}, },
"ENABLE_HISTORY_XML_REPLACE": { "ENABLE_HISTORY_XML_REPLACE": {
"label": "履歴 XML 圧縮", "label": "古い図を省略",
"description": "履歴内の古い図 XML をプレースホルダーで置き換えます。" "description": "会話履歴の古い図は短い印だけ残します"
}, },
"ALLOW_PRIVATE_URLS": { "ALLOW_PRIVATE_URLS": {
"label": "プライベート URL を許可", "label": "内部ネットワークへのアクセス",
"description": "オフにすると、プライベート IP や内部ホスト名へのリクエストをブロックします(SSRF 保護)。" "description": "オフにすると、ユーザーが指定するモデルの URL に内部アドレスを使えなくなり、安全です"
}, },
"LANGFUSE_PUBLIC_KEY": { "LANGFUSE_PUBLIC_KEY": {
"label": "Langfuse Public Key" "label": "Langfuse Public Key"
@@ -598,29 +598,29 @@
"label": "Langfuse Secret Key" "label": "Langfuse Secret Key"
}, },
"LANGFUSE_BASEURL": { "LANGFUSE_BASEURL": {
"label": "Langfuse Base URL" "label": "Langfuse の URL"
}, },
"DAILY_REQUEST_LIMIT": { "DAILY_REQUEST_LIMIT": {
"label": "1 日あたりのリクエスト上限", "label": "1 日のリクエスト数",
"description": "IP ごと 1 日あたり。" "description": "IP ごとの 1 日あたりのリクエスト数の上限"
}, },
"DAILY_TOKEN_LIMIT": { "DAILY_TOKEN_LIMIT": {
"label": "1 日あたりのトークン上限", "label": "1 日のトークン数",
"description": "IP ごと 1 日あたり。" "description": "IP ごとの 1 日あたりのトークン使用量の上限"
}, },
"TPM_LIMIT": { "TPM_LIMIT": {
"label": "1 分あたりのトークン数" "label": "1 分あたりのトークン数"
}, },
"DYNAMODB_QUOTA_TABLE": { "DYNAMODB_QUOTA_TABLE": {
"label": "DynamoDB テーブル", "label": "DynamoDB テーブル名",
"description": "空の場合、クォータの強制は無効になります。" "description": "空なら使用量を制限しません"
}, },
"DYNAMODB_REGION": { "DYNAMODB_REGION": {
"label": "DynamoDB リージョン" "label": "DynamoDB リージョン"
}, },
"QUOTA_TIMEZONE": { "QUOTA_TIMEZONE": {
"label": "クォータタイムゾーン", "label": "タイムゾーン",
"description": "1 日のリセット境界に使用するタイムゾーン。" "description": "1 日の使用量はこのタイムゾーンの 0 時にリセットされます"
} }
} }
}, },
@@ -694,7 +694,7 @@
}, },
"lobby": { "lobby": {
"title": "どんな図を描きますか?", "title": "どんな図を描きますか?",
"lede": "図の内容を説明するか、スクリーンショットや PDF をアップロードしてください。できあがるのは通常の draw.io の図なので、続けて編集もエクスポートもできます。", "lede": "図を説明するか、スクリーンショットや PDF をアップロードしてください。できあがるのは編集もエクスポートもできる draw.io の図です。",
"placeholder": "例:SMS 認証とリトライを含むログインのシーケンス図", "placeholder": "例:SMS 認証とリトライを含むログインのシーケンス図",
"recent": "最近の図", "recent": "最近の図",
"drawYourself": "白紙のキャンバスから始める", "drawYourself": "白紙のキャンバスから始める",
+80 -80
View File
@@ -72,7 +72,7 @@
"replicateDescription": "上傳並複製現有流程圖", "replicateDescription": "上傳並複製現有流程圖",
"creativeDrawing": "創意繪畫", "creativeDrawing": "創意繪畫",
"creativeDescription": "繪製有趣且富有創意的內容", "creativeDescription": "繪製有趣且富有創意的內容",
"cachedNote": "範例會立即開啟,不呼叫模型。", "cachedNote": "範例使用保存好的答案,立即顯示,不呼叫 AI。",
"mcpServer": "MCP 伺服器", "mcpServer": "MCP 伺服器",
"mcpDescription": "在 Claude Desktop、VS Code 和 Cursor 中使用" "mcpDescription": "在 Claude Desktop、VS Code 和 Cursor 中使用"
}, },
@@ -82,9 +82,9 @@
"accessCode": "存取碼", "accessCode": "存取碼",
"accessCodePlaceholder": "輸入存取碼", "accessCodePlaceholder": "輸入存取碼",
"accessCodeDescription": "使用此應用程式需要存取碼。", "accessCodeDescription": "使用此應用程式需要存取碼。",
"aiProvider": "AI 提供商設定", "aiProvider": "AI 服務商設定",
"aiProviderDescription": "使用您自己的 API 金鑰來繞過使用限制。您的金鑰僅儲存在瀏覽器本機,不會儲存在伺服器上。", "aiProviderDescription": "使用您自己的 API 金鑰來繞過使用限制。您的金鑰僅儲存在瀏覽器本機,不會儲存在伺服器上。",
"provider": "提供商", "provider": "服務商",
"modelId": "模型 ID", "modelId": "模型 ID",
"apiKey": "API 金鑰", "apiKey": "API 金鑰",
"apiKeyPlaceholder": "您的 API 金鑰", "apiKeyPlaceholder": "您的 API 金鑰",
@@ -96,7 +96,7 @@
"language": "語言", "language": "語言",
"languageDescription": "選擇介面語言。", "languageDescription": "選擇介面語言。",
"theme": "主題", "theme": "主題",
"themeDescription": "介面用淺色還是深色。圖紙始終是白底,和匯出後的樣子一致。", "themeDescription": "介面用淺色還是深色,畫布跟隨同一主題。",
"diagramStyle": "圖形風格", "diagramStyle": "圖形風格",
"diagramStyleDescription": "AI 畫新圖時的風格:精緻會用顏色和圖示,簡約只用樸素的圖形。", "diagramStyleDescription": "AI 畫新圖時的風格:精緻會用顏色和圖示,簡約只用樸素的圖形。",
"sendShortcut": "傳送快速鍵", "sendShortcut": "傳送快速鍵",
@@ -118,7 +118,7 @@
"customSystemMessage": "自訂指令", "customSystemMessage": "自訂指令",
"customSystemMessageDescription": "新增自訂指示,將附加到 AI 的系統提示末尾。", "customSystemMessageDescription": "新增自訂指示,將附加到 AI 的系統提示末尾。",
"customSystemMessagePlaceholder": "例如:圖表始終使用藍色配色方案...", "customSystemMessagePlaceholder": "例如:圖表始終使用藍色配色方案...",
"maxOutputTokens": "最大輸出 token 數", "maxOutputTokens": "輸出 token 上限",
"maxOutputTokensDescription": "單次回覆的額度,思考過程與圖表 XML 共用。若 AI 一直在思考卻沒有產生圖表,請將它調大。留空則使用預設值。", "maxOutputTokensDescription": "單次回覆的額度,思考過程與圖表 XML 共用。若 AI 一直在思考卻沒有產生圖表,請將它調大。留空則使用預設值。",
"tabModels": "模型", "tabModels": "模型",
"tabAbout": "關於", "tabAbout": "關於",
@@ -207,10 +207,10 @@
}, },
"quota": { "quota": {
"dailyLimit": "已達每日配額", "dailyLimit": "已達每日配額",
"tokenLimit": "已達每日令牌限制", "tokenLimit": "已達每日 token 上限",
"tpmLimit": "速率限制", "tpmLimit": "速率限制",
"tpmMessage": "請求過多。請稍等片刻。", "tpmMessage": "請求過多。請稍等片刻。",
"tpmMessageDetailed": "達到速率限制({limit} 令牌/分鐘)。請等待 {seconds} 秒後再傳送請求。", "tpmMessageDetailed": "達到速率限制(每分鐘 {limit} token)。請等待 {seconds} 秒後再傳送請求。",
"messageApi": "看來您今天的體驗次數已達上限。很高興您玩得開心;為了讓大家都能公平使用,示範站對用量做了限制。", "messageApi": "看來您今天的體驗次數已達上限。很高興您玩得開心;為了讓大家都能公平使用,示範站對用量做了限制。",
"messageApiSelfHosted": null, "messageApiSelfHosted": null,
"messageToken": "看來您今天的 token 用量已達上限。很高興您玩得開心;為了讓大家都能公平使用,示範站對用量做了限制。", "messageToken": "看來您今天的 token 用量已達上限。很高興您玩得開心;為了讓大家都能公平使用,示範站對用量做了限制。",
@@ -399,7 +399,7 @@
"searchModels": "搜尋模型...", "searchModels": "搜尋模型...",
"noModelsFound": "未找到模型。", "noModelsFound": "未找到模型。",
"default": "預設", "default": "預設",
"serverDefault": "伺服器預設", "serverDefault": "伺服器模型",
"serverModels": "伺服器模型", "serverModels": "伺服器模型",
"userModels": "使用者模型", "userModels": "使用者模型",
"configureModels": "配置模型...", "configureModels": "配置模型...",
@@ -481,146 +481,146 @@
"removeAccessKeys": "刪除存取金鑰" "removeAccessKeys": "刪除存取金鑰"
}, },
"admin": { "admin": {
"title": "管理員設定", "title": "後台設定",
"loginPrompt": "輸入管理員密碼(即 ADMIN_PASSWORD 環境變數)以管理伺服器設定。", "loginPrompt": "輸入管理員密碼(環境變數 ADMIN_PASSWORD)",
"password": "密碼", "password": "密碼",
"signIn": "登入", "signIn": "登入",
"signingIn": "正在登入…", "signingIn": "登入中…",
"loginFailed": "登入失敗", "loginFailed": "登入失敗",
"precedence": "檔案覆蓋環境變數 · 環境變數覆蓋預設值", "precedence": "優先順序:本頁設定 > 環境變數 > 預設值",
"notWritable": "此部署環境下設定檔不可寫入(無伺服器平台沒有持久化磁碟)。設定以唯讀方式顯示——請改用環境變數進行設定。", "notWritable": "這裡無法儲存設定,只能檢視;請改用環境變數",
"settingGroups": "設定分組", "settingGroups": "設定分類",
"enabled": "已啟用", "enabled": "已開啟",
"disabled": "已停用", "disabled": "已關閉",
"enableGroup": "啟用 {group}", "enableGroup": "開啟{group}",
"unsavedChanges": "有未儲存的變更", "unsavedChanges": "有未儲存的修改",
"saved": "設定已儲存,變更立即生效。", "saved": "已儲存,立即生效(需重啟的項目除外)",
"saveFailed": "儲存失敗。請檢查網路連線後重試。", "saveFailed": "儲存失敗,請檢查網路後重試",
"invalidSettings": "部分設定無效。", "invalidSettings": "有設定填寫有誤,請依提示修改",
"discard": "捨棄", "discard": "捨棄修改",
"saveChanges": "儲存變更", "saveChanges": "儲存修改",
"saving": "正在儲存…", "saving": "儲存中…",
"sourceSaved": "已儲存", "sourceSaved": "本頁設定",
"sourceEnv": "環境變數", "sourceEnv": "環境變數",
"sourceSavedTitle": "在管理員設定檔中設定", "sourceSavedTitle": "來自本頁設定",
"sourceEnvTitle": "透過環境變數設定", "sourceEnvTitle": "來自環境變數",
"restartRequired": "需要重新啟動", "restartRequired": "需重啟",
"modified": "已修改", "modified": "已修改",
"notSet": "未設定", "notSet": "未設定",
"savedReplace": "已儲存({hint})——輸入以取代", "savedReplace": "已儲存({hint}),輸入新值即可取代",
"showValue": "顯示值", "showValue": "顯示",
"hideValue": "隱藏值", "hideValue": "隱藏",
"removeValue": "移除值", "removeValue": "清除",
"removeValueTitle": "移除已儲存的值", "removeValueTitle": "清除已儲存的值",
"resetToDefault": "重設為預設", "resetToDefault": "重設為預設",
"models": "模型", "models": "模型",
"modelsDescription": "面向所有使用者的伺服器端 provider 與模型——無需個人 API 金鑰。當使用者未選擇模型時,使用預設 provider 的第一個模型。", "modelsDescription": "給所有使用者用的模型,無需自備金鑰",
"addProviderHint": "新增一個 provider,為所有使用者提供伺服器端模型。", "addProviderHint": "新增服務商,為所有使用者提供模型",
"selectProviderHint": "選擇或新增一個 provider 以設定其憑證和模型。", "selectProviderHint": "選擇或新增服務商,填寫金鑰和模型",
"addProviderToOfferModels": "至少新增一個模型,才能向使用者開放此 provider。", "addProviderToOfferModels": "至少新增一個模型,使用者才能看到這個服務商",
"managedViaEnv": "(透過環境變數管理)", "managedViaEnv": "(環境變數)",
"envReadOnly": "在 AI_MODELS_CONFIG / ai-models.json 中定義——此處唯讀。請編輯環境設定以變更。", "envReadOnly": "由 AI_MODELS_CONFIG / ai-models.json 定義,這裡只能檢視,請到那裡修改",
"defaultModel": "預設模型", "defaultModel": "預設模型",
"noModelsConfigured": "未設定模型", "noModelsConfigured": "尚無模型",
"modelCount": "{count} 個模型", "modelCount": "{count} 個模型",
"modelCountPlural": "{count} 個模型", "modelCountPlural": "{count} 個模型",
"default": "預設", "default": "預設",
"setAsDefault": "設為預設 provider", "setAsDefault": "設為預設服務商",
"defaultProvider": "預設 provider", "defaultProvider": "預設服務商",
"modelIdPlaceholder": "模型 ID…", "modelIdPlaceholder": "模型 ID…",
"addModel": "新增模型", "addModel": "新增模型",
"suggested": "推薦", "suggested": "推薦",
"test": "測試", "test": "測試",
"testOk": "正常({ms} 毫秒)", "testOk": "通過({ms} 毫秒)",
"testFailed": "失敗", "testFailed": "失敗",
"removeModel": "移除 {model}", "removeModel": "刪除 {model}",
"deleteProviderTitle": "刪除 {name}?", "deleteProviderTitle": "刪除 {name}?",
"deleteProviderDesc": "儲存後,其憑證和模型將從伺服器上移除。", "deleteProviderDesc": "儲存後,它的金鑰和模型會從伺服器刪除",
"cancel": "取消", "cancel": "取消",
"delete": "刪除", "delete": "刪除",
"groups": { "groups": {
"generation": { "generation": {
"title": "生成", "title": "回覆設定",
"description": "套用於所有聊天請求的輸出參數。" "description": "控制 AI 怎麼回覆,對所有對話生效"
}, },
"access": { "access": {
"title": "存取控制", "title": "存取權限",
"description": "限制誰可以使用此部署。" "description": "限制誰可以使用這個網站"
}, },
"features": { "features": {
"title": "功能", "title": "選用功能",
"description": "選用功能和安全開關。" "description": "附加功能和安全選項"
}, },
"observability": { "observability": {
"title": "可觀測性", "title": "呼叫記錄",
"description": "對 LLM 呼叫進行 Langfuse 追蹤。" "description": "用 Langfuse 記錄模型呼叫"
}, },
"quota": { "quota": {
"title": "配額與速率限制", "title": "用量限制",
"description": "按 IP 的用量限制。強制執行需要 DynamoDB 表。" "description": "按 IP 限制用量,需要 DynamoDB 資料表才生效"
} }
}, },
"settings": { "settings": {
"TEMPERATURE": { "TEMPERATURE": {
"label": "溫度", "label": "溫度",
"description": "對於拒絕溫度參數的推理模型,請留空。" "description": "越高越隨機;不支援溫度的推理模型請留空"
}, },
"MAX_OUTPUT_TOKENS": { "MAX_OUTPUT_TOKENS": {
"label": "最大輸出 token 數" "label": "輸出 token 上限"
}, },
"ACCESS_CODE_LIST": { "ACCESS_CODE_LIST": {
"label": "存取碼", "label": "存取碼",
"description": "以逗號分隔的清單。使用者需輸入其中之一才能聊天。留空 = 開放存取。" "description": "逗號分隔多個;輸入其中一個才能用;留空則不限"
}, },
"ENABLE_VLM_VALIDATION": { "ENABLE_VLM_VALIDATION": {
"label": "VLM 圖表驗證", "label": "圖表檢查",
"description": "使用視覺模型對產生的圖表進行視覺化驗證。" "description": "畫完後讓能看圖的模型檢查截圖"
}, },
"VALIDATION_MODEL": { "VALIDATION_MODEL": {
"label": "驗證模型", "label": "檢查模型",
"description": "留空時回退到預設 AI 模型。" "description": "留空則用預設模型"
}, },
"VALIDATION_TIMEOUT": { "VALIDATION_TIMEOUT": {
"label": "驗證逾時(毫秒)" "label": "檢查逾時(毫秒)"
}, },
"ENABLE_HISTORY_XML_REPLACE": { "ENABLE_HISTORY_XML_REPLACE": {
"label": "歷史 XML 壓縮", "label": "精簡舊圖",
"description": "用占位符取代歷史記錄中的舊圖表 XML。" "description": "對話歷史裡的舊圖表只留簡短標記"
}, },
"ALLOW_PRIVATE_URLS": { "ALLOW_PRIVATE_URLS": {
"label": "允許私有 URL", "label": "存取內網",
"description": "關閉以阻擋對私有 IP 和內部主機名的請求(SSRF 防護)。" "description": "關閉後,自訂模型位址不能指向內網,更安全"
}, },
"LANGFUSE_PUBLIC_KEY": { "LANGFUSE_PUBLIC_KEY": {
"label": "Langfuse Public Key" "label": "Langfuse 公鑰"
}, },
"LANGFUSE_SECRET_KEY": { "LANGFUSE_SECRET_KEY": {
"label": "Langfuse Secret Key" "label": "Langfuse 金鑰"
}, },
"LANGFUSE_BASEURL": { "LANGFUSE_BASEURL": {
"label": "Langfuse Base URL" "label": "Langfuse 位址"
}, },
"DAILY_REQUEST_LIMIT": { "DAILY_REQUEST_LIMIT": {
"label": "每日請求上限", "label": "每日請求",
"description": "每個 IP 每天。" "description": "每個 IP 每天的請求次數上限"
}, },
"DAILY_TOKEN_LIMIT": { "DAILY_TOKEN_LIMIT": {
"label": "每日 token 上限", "label": "每日 token",
"description": "每個 IP 每天。" "description": "每個 IP 每天的 token 用量上限"
}, },
"TPM_LIMIT": { "TPM_LIMIT": {
"label": "每分鐘 token 數" "label": "每分鐘 token"
}, },
"DYNAMODB_QUOTA_TABLE": { "DYNAMODB_QUOTA_TABLE": {
"label": "DynamoDB 表", "label": "DynamoDB 資料表名稱",
"description": "留空時配額強制執行被停用。" "description": "留空則不限制用量"
}, },
"DYNAMODB_REGION": { "DYNAMODB_REGION": {
"label": "DynamoDB 區域" "label": "DynamoDB 區域"
}, },
"QUOTA_TIMEZONE": { "QUOTA_TIMEZONE": {
"label": "配額時區", "label": "時區",
"description": "每日重置邊界所用的時區。" "description": "每日用量按這個時區的零點重置"
} }
} }
}, },
@@ -694,7 +694,7 @@
}, },
"lobby": { "lobby": {
"title": "說說你要畫什麼", "title": "說說你要畫什麼",
"lede": "描述一張圖,或上傳截圖、PDF。畫出來的是標準的 draw.io 圖表,可以接著改,也可以匯出。", "lede": "描述一張圖,或上傳截圖、PDF。得到的是可以繼續編輯和匯出的 draw.io 圖。",
"placeholder": "例如:使用者登入的時序圖,包含簡訊驗證碼和失敗重試", "placeholder": "例如:使用者登入的時序圖,包含簡訊驗證碼和失敗重試",
"recent": "最近的圖紙", "recent": "最近的圖紙",
"drawYourself": "從空白畫布開始", "drawYourself": "從空白畫布開始",
+80 -80
View File
@@ -72,7 +72,7 @@
"replicateDescription": "上传并复制现有流程图", "replicateDescription": "上传并复制现有流程图",
"creativeDrawing": "创意绘画", "creativeDrawing": "创意绘画",
"creativeDescription": "绘制有趣且富有创意的内容", "creativeDescription": "绘制有趣且富有创意的内容",
"cachedNote": "示例会立即打开,不调用模型。", "cachedNote": "示例使用保存好的答案,立即显示,不调用 AI。",
"mcpServer": "MCP 服务器", "mcpServer": "MCP 服务器",
"mcpDescription": "在 Claude Desktop、VS Code 和 Cursor 中使用" "mcpDescription": "在 Claude Desktop、VS Code 和 Cursor 中使用"
}, },
@@ -82,9 +82,9 @@
"accessCode": "访问码", "accessCode": "访问码",
"accessCodePlaceholder": "输入访问码", "accessCodePlaceholder": "输入访问码",
"accessCodeDescription": "使用此应用程序需要访问码。", "accessCodeDescription": "使用此应用程序需要访问码。",
"aiProvider": "AI 提供商设置", "aiProvider": "AI 服务商设置",
"aiProviderDescription": "使用您自己的 API 密钥来绕过使用限制。您的密钥仅存储在浏览器本地,不会存储在服务器上。", "aiProviderDescription": "使用您自己的 API 密钥来绕过使用限制。您的密钥仅存储在浏览器本地,不会存储在服务器上。",
"provider": "提供商", "provider": "服务商",
"modelId": "模型 ID", "modelId": "模型 ID",
"apiKey": "API 密钥", "apiKey": "API 密钥",
"apiKeyPlaceholder": "您的 API 密钥", "apiKeyPlaceholder": "您的 API 密钥",
@@ -96,7 +96,7 @@
"language": "语言", "language": "语言",
"languageDescription": "选择界面语言。", "languageDescription": "选择界面语言。",
"theme": "主题", "theme": "主题",
"themeDescription": "界面用浅色还是深色。图纸始终是白底,和导出后的样子一致。", "themeDescription": "界面用浅色还是深色,画布跟随同一主题。",
"diagramStyle": "图形风格", "diagramStyle": "图形风格",
"diagramStyleDescription": "AI 画新图时的风格:精致会用颜色和图标,简约只用朴素的图形。", "diagramStyleDescription": "AI 画新图时的风格:精致会用颜色和图标,简约只用朴素的图形。",
"sendShortcut": "发送快捷键", "sendShortcut": "发送快捷键",
@@ -118,7 +118,7 @@
"customSystemMessage": "自定义指令", "customSystemMessage": "自定义指令",
"customSystemMessageDescription": "添加自定义指令,将附加到 AI 的系统提示末尾。", "customSystemMessageDescription": "添加自定义指令,将附加到 AI 的系统提示末尾。",
"customSystemMessagePlaceholder": "例如:图表始终使用蓝色配色方案...", "customSystemMessagePlaceholder": "例如:图表始终使用蓝色配色方案...",
"maxOutputTokens": "最大输出 token 数", "maxOutputTokens": "输出 token 上限",
"maxOutputTokensDescription": "单次回复的额度,思考过程和图表 XML 共用。如果 AI 一直在思考却没有生成图表,请把它调大。留空则使用默认值。", "maxOutputTokensDescription": "单次回复的额度,思考过程和图表 XML 共用。如果 AI 一直在思考却没有生成图表,请把它调大。留空则使用默认值。",
"tabModels": "模型", "tabModels": "模型",
"tabAbout": "关于", "tabAbout": "关于",
@@ -207,10 +207,10 @@
}, },
"quota": { "quota": {
"dailyLimit": "已达每日配额", "dailyLimit": "已达每日配额",
"tokenLimit": "已达每日令牌限制", "tokenLimit": "已达每日 token 上限",
"tpmLimit": "速率限制", "tpmLimit": "速率限制",
"tpmMessage": "请求过多。请稍等片刻。", "tpmMessage": "请求过多。请稍等片刻。",
"tpmMessageDetailed": "达到速率限制({limit} 令牌/分钟)。请等待 {seconds} 秒后再发送请求。", "tpmMessageDetailed": "达到速率限制(每分钟 {limit} token)。请等待 {seconds} 秒后再发送请求。",
"messageApi": "看来您今天的体验次数已达上限。很高兴您玩得开心;为了让大家都能公平使用,演示站对用量做了限制。", "messageApi": "看来您今天的体验次数已达上限。很高兴您玩得开心;为了让大家都能公平使用,演示站对用量做了限制。",
"messageApiSelfHosted": null, "messageApiSelfHosted": null,
"messageToken": "看来您今天的 token 用量已达上限。很高兴您玩得开心;为了让大家都能公平使用,演示站对用量做了限制。", "messageToken": "看来您今天的 token 用量已达上限。很高兴您玩得开心;为了让大家都能公平使用,演示站对用量做了限制。",
@@ -399,7 +399,7 @@
"searchModels": "搜索模型...", "searchModels": "搜索模型...",
"noModelsFound": "未找到模型。", "noModelsFound": "未找到模型。",
"default": "默认", "default": "默认",
"serverDefault": "服务器默认", "serverDefault": "服务器模型",
"serverModels": "服务器模型", "serverModels": "服务器模型",
"userModels": "用户模型", "userModels": "用户模型",
"configureModels": "配置模型...", "configureModels": "配置模型...",
@@ -481,146 +481,146 @@
"removeAccessKeys": "删除访问密钥" "removeAccessKeys": "删除访问密钥"
}, },
"admin": { "admin": {
"title": "管理员设置", "title": "后台设置",
"loginPrompt": "输入管理员密码(即 ADMIN_PASSWORD 环境变量)以管理服务器设置。", "loginPrompt": "输入管理员密码(环境变量 ADMIN_PASSWORD)",
"password": "密码", "password": "密码",
"signIn": "登录", "signIn": "登录",
"signingIn": "正在登录…", "signingIn": "登录中…",
"loginFailed": "登录失败", "loginFailed": "登录失败",
"precedence": "文件覆盖环境变量 · 环境变量覆盖默认值", "precedence": "优先级:本页设置 > 环境变量 > 默认值",
"notWritable": "此部署环境下设置文件不可写(无服务器平台没有持久化磁盘)。设置以只读方式显示——请改用环境变量进行配置。", "notWritable": "这里无法保存设置,只能查看;请改用环境变量",
"settingGroups": "设置分组", "settingGroups": "设置分类",
"enabled": "已启用", "enabled": "已开启",
"disabled": "已禁用", "disabled": "已关闭",
"enableGroup": "启用 {group}", "enableGroup": "开启{group}",
"unsavedChanges": "有未保存的更改", "unsavedChanges": "有未保存的修改",
"saved": "设置已保存,更改立即生效。", "saved": "已保存,立即生效(需重启的项除外)",
"saveFailed": "保存失败。请检查网络连接后重试。", "saveFailed": "保存失败,请检查网络后重试",
"invalidSettings": "部分设置无效。", "invalidSettings": "有设置填写有误,请按提示修改",
"discard": "放弃", "discard": "放弃修改",
"saveChanges": "保存更改", "saveChanges": "保存修改",
"saving": "正在保存…", "saving": "保存中…",
"sourceSaved": "已保存", "sourceSaved": "本页设置",
"sourceEnv": "环境变量", "sourceEnv": "环境变量",
"sourceSavedTitle": "在管理员设置文件中设置", "sourceSavedTitle": "来自本页设置",
"sourceEnvTitle": "通过环境变量设置", "sourceEnvTitle": "来自环境变量",
"restartRequired": "需要重启", "restartRequired": "需重启",
"modified": "已修改", "modified": "已修改",
"notSet": "未设置", "notSet": "未设置",
"savedReplace": "已保存({hint})——输入以替换", "savedReplace": "已保存({hint}),输入新值可替换",
"showValue": "显示值", "showValue": "显示",
"hideValue": "隐藏值", "hideValue": "隐藏",
"removeValue": "移除值", "removeValue": "清除",
"removeValueTitle": "移除已保存的值", "removeValueTitle": "清除已保存的值",
"resetToDefault": "恢复默认", "resetToDefault": "恢复默认",
"models": "模型", "models": "模型",
"modelsDescription": "面向所有用户的服务端 provider 和模型——无需个人 API 密钥。当用户未选择模型时,使用默认 provider 的第一个模型。", "modelsDescription": "给所有用户用的模型,无需自带密钥",
"addProviderHint": "添加一个 provider,为所有用户提供服务端模型。", "addProviderHint": "添加服务商,为所有用户提供模型",
"selectProviderHint": "选择或添加一个 provider 以配置其凭证和模型。", "selectProviderHint": "选择或添加服务商,填写密钥和模型",
"addProviderToOfferModels": "至少添加一个模型,才能向用户开放此 provider。", "addProviderToOfferModels": "至少添加一个模型,用户才能看到这个服务商",
"managedViaEnv": "(通过环境变量管理)", "managedViaEnv": "(环境变量)",
"envReadOnly": "在 AI_MODELS_CONFIG / ai-models.json 中定义——此处只读。请编辑环境配置以更改。", "envReadOnly": "由 AI_MODELS_CONFIG / ai-models.json 定义,这里只能查看,请到那里修改",
"defaultModel": "默认模型", "defaultModel": "默认模型",
"noModelsConfigured": "未配置模型", "noModelsConfigured": "暂无模型",
"modelCount": "{count} 个模型", "modelCount": "{count} 个模型",
"modelCountPlural": "{count} 个模型", "modelCountPlural": "{count} 个模型",
"default": "默认", "default": "默认",
"setAsDefault": "设为默认 provider", "setAsDefault": "设为默认服务商",
"defaultProvider": "默认 provider", "defaultProvider": "默认服务商",
"modelIdPlaceholder": "模型 ID…", "modelIdPlaceholder": "模型 ID…",
"addModel": "添加模型", "addModel": "添加模型",
"suggested": "推荐", "suggested": "推荐",
"test": "测试", "test": "测试",
"testOk": "正常({ms} 毫秒)", "testOk": "通过({ms} 毫秒)",
"testFailed": "失败", "testFailed": "失败",
"removeModel": "移除 {model}", "removeModel": "删除 {model}",
"deleteProviderTitle": "删除 {name}?", "deleteProviderTitle": "删除 {name}?",
"deleteProviderDesc": "保存后,其凭证和模型将从服务器上移除。", "deleteProviderDesc": "保存后,它的密钥和模型会从服务器删除",
"cancel": "取消", "cancel": "取消",
"delete": "删除", "delete": "删除",
"groups": { "groups": {
"generation": { "generation": {
"title": "生成", "title": "回复设置",
"description": "应用于所有聊天请求的输出参数。" "description": "控制 AI 怎么回复,对所有对话生效"
}, },
"access": { "access": {
"title": "访问控制", "title": "访问权限",
"description": "限制谁可以使用此部署。" "description": "限制谁可以使用这个站点"
}, },
"features": { "features": {
"title": "功能", "title": "可选功能",
"description": "可选功能和安全开关。" "description": "附加功能和安全选项"
}, },
"observability": { "observability": {
"title": "可观测性", "title": "调用记录",
"description": "对 LLM 调用进行 Langfuse 追踪。" "description": "用 Langfuse 记录模型调用"
}, },
"quota": { "quota": {
"title": "配额与速率限制", "title": "用量限制",
"description": "按 IP 的用量限制。强制执行需要 DynamoDB 表。" "description": "按 IP 限制用量,需要 DynamoDB 表才生效"
} }
}, },
"settings": { "settings": {
"TEMPERATURE": { "TEMPERATURE": {
"label": "温度", "label": "温度",
"description": "对于拒绝温度参数的推理模型,请留空。" "description": "越高越随机;不支持温度的推理模型请留空"
}, },
"MAX_OUTPUT_TOKENS": { "MAX_OUTPUT_TOKENS": {
"label": "最大输出 token 数" "label": "输出 token 上限"
}, },
"ACCESS_CODE_LIST": { "ACCESS_CODE_LIST": {
"label": "访问码", "label": "访问码",
"description": "以逗号分隔的列表。用户需输入其中之一才能聊天。留空 = 开放访问。" "description": "逗号分隔多个;输入其中一个才能用;留空则不限"
}, },
"ENABLE_VLM_VALIDATION": { "ENABLE_VLM_VALIDATION": {
"label": "VLM 图表验证", "label": "图表检查",
"description": "使用视觉模型对生成的图表进行可视化验证。" "description": "画完后让能看图的模型检查截图"
}, },
"VALIDATION_MODEL": { "VALIDATION_MODEL": {
"label": "验证模型", "label": "检查模型",
"description": "留空时回退到默认 AI 模型。" "description": "留空则用默认模型"
}, },
"VALIDATION_TIMEOUT": { "VALIDATION_TIMEOUT": {
"label": "验证超时(毫秒)" "label": "检查超时(毫秒)"
}, },
"ENABLE_HISTORY_XML_REPLACE": { "ENABLE_HISTORY_XML_REPLACE": {
"label": "历史 XML 压缩", "label": "精简旧图",
"description": "用占位符替换历史记录中的旧图表 XML。" "description": "对话历史里的旧图表只留简短标记"
}, },
"ALLOW_PRIVATE_URLS": { "ALLOW_PRIVATE_URLS": {
"label": "允许私有 URL", "label": "访问内网",
"description": "关闭以阻止对私有 IP 和内部主机名的请求(SSRF 防护)。" "description": "关闭后,自定义模型地址不能指向内网,更安全"
}, },
"LANGFUSE_PUBLIC_KEY": { "LANGFUSE_PUBLIC_KEY": {
"label": "Langfuse Public Key" "label": "Langfuse 公钥"
}, },
"LANGFUSE_SECRET_KEY": { "LANGFUSE_SECRET_KEY": {
"label": "Langfuse Secret Key" "label": "Langfuse 密钥"
}, },
"LANGFUSE_BASEURL": { "LANGFUSE_BASEURL": {
"label": "Langfuse Base URL" "label": "Langfuse 地址"
}, },
"DAILY_REQUEST_LIMIT": { "DAILY_REQUEST_LIMIT": {
"label": "每日请求上限", "label": "每日请求",
"description": "每个 IP 每天。" "description": "每个 IP 每天的请求次数上限"
}, },
"DAILY_TOKEN_LIMIT": { "DAILY_TOKEN_LIMIT": {
"label": "每日 token 上限", "label": "每日 token",
"description": "每个 IP 每天。" "description": "每个 IP 每天的 token 用量上限"
}, },
"TPM_LIMIT": { "TPM_LIMIT": {
"label": "每分钟 token 数" "label": "每分钟 token"
}, },
"DYNAMODB_QUOTA_TABLE": { "DYNAMODB_QUOTA_TABLE": {
"label": "DynamoDB 表", "label": "DynamoDB 表名",
"description": "留空时配额强制执行被禁用。" "description": "留空则不限制用量"
}, },
"DYNAMODB_REGION": { "DYNAMODB_REGION": {
"label": "DynamoDB 区域" "label": "DynamoDB 区域"
}, },
"QUOTA_TIMEZONE": { "QUOTA_TIMEZONE": {
"label": "配额时区", "label": "时区",
"description": "每日重置边界所用的时区。" "description": "每日用量按这个时区的零点重置"
} }
} }
}, },
@@ -694,7 +694,7 @@
}, },
"lobby": { "lobby": {
"title": "说说你要画什么", "title": "说说你要画什么",
"lede": "描述一张图,或者上传截图、PDF。画出来的是标准的 draw.io 图表,可以接着改,也可以导出。", "lede": "描述一张图,或上传截图、PDF。得到的是可以继续编辑和导出的 draw.io 图。",
"placeholder": "比如:用户登录的时序图,包含短信验证码和失败重试", "placeholder": "比如:用户登录的时序图,包含短信验证码和失败重试",
"recent": "最近的图纸", "recent": "最近的图纸",
"drawYourself": "从空白画布开始", "drawYourself": "从空白画布开始",
+58 -1156
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "next-ai-draw-io", "name": "next-ai-draw-io",
"version": "0.4.16", "version": "0.5.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"private": true, "private": true,
"main": "dist-electron/main/index.js", "main": "dist-electron/main/index.js",
+7 -3
View File
@@ -2746,9 +2746,9 @@
} }
}, },
"node_modules/proxy-addr": { "node_modules/proxy-addr": {
"version": "2.0.7", "version": "2.0.8",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz",
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"forwarded": "0.2.0", "forwarded": "0.2.0",
@@ -2756,6 +2756,10 @@
}, },
"engines": { "engines": {
"node": ">= 0.10" "node": ">= 0.10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
} }
}, },
"node_modules/qs": { "node_modules/qs": {
+1 -4
View File
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 67 KiB