- 等待接手 {formatDuration(workerHealth.oldestQueuedWaitMs) ?? "<1 秒"}
+ Waiting to take over {formatDuration(workerHealth.oldestQueuedWaitMs) ?? "<1 second"}
diff --git a/client/src/components/autoDirector/AutoDirectorApprovalPointMultiSelect.tsx b/client/src/components/autoDirector/AutoDirectorApprovalPointMultiSelect.tsx
index b4afaacdc..c217c2e51 100644
--- a/client/src/components/autoDirector/AutoDirectorApprovalPointMultiSelect.tsx
+++ b/client/src/components/autoDirector/AutoDirectorApprovalPointMultiSelect.tsx
@@ -17,9 +17,9 @@ interface AutoDirectorApprovalPointMultiSelectProps {
}
function riskLabel(riskLevel: DirectorAutoApprovalPoint["riskLevel"]): string {
- if (riskLevel === "high") return "高风险";
- if (riskLevel === "medium") return "中风险";
- return "低风险";
+ if (riskLevel === "high") return "high risk";
+ if (riskLevel === "medium") return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
function riskClassName(riskLevel: DirectorAutoApprovalPoint["riskLevel"]): string {
@@ -41,7 +41,7 @@ function toggleCodes(current: string[], targetCodes: string[], checked: boolean)
export function summarizeDirectorAutoApprovalPoints(codes: string[]): string {
const normalized = normalizeDirectorAutoApprovalPointCodes(codes, []);
if (normalized.length === 0) {
- return "不会自动通过审批点";
+ return "Will not automatically pass the approval point";
}
const labels: string[] = normalized
.map((code) => DIRECTOR_AUTO_APPROVAL_POINTS.find((item) => item.code === code)?.label)
@@ -49,7 +49,7 @@ export function summarizeDirectorAutoApprovalPoints(codes: string[]): string {
if (labels.length <= 2) {
return labels.join("、");
}
- return `${labels.slice(0, 2).join("、")} 等 ${labels.length} 项`;
+ return `${labels.slice(0, 2).join("、")} and ${labels.length} items`;
}
export default function AutoDirectorApprovalPointMultiSelect({
diff --git a/client/src/components/autoDirector/AutoDirectorApprovalStrategyPanel.tsx b/client/src/components/autoDirector/AutoDirectorApprovalStrategyPanel.tsx
index ce9a29816..a557c0732 100644
--- a/client/src/components/autoDirector/AutoDirectorApprovalStrategyPanel.tsx
+++ b/client/src/components/autoDirector/AutoDirectorApprovalStrategyPanel.tsx
@@ -26,7 +26,7 @@ export default function AutoDirectorApprovalStrategyPanel({
}: AutoDirectorApprovalStrategyPanelProps) {
return (
-
自动推进方式
+
Automatic propulsion method
{enabled
- ? "自动推进:系统会在目标范围内自动确认规划、章节执行、质量修复和必要重规划。"
+ ? "Automatic advancement: The system will automatically confirm planning, chapter execution, quality repair and necessary re-planning within the target range."
: `副驾确认边界:${summarizeDirectorAutoApprovalPoints(approvalPointCodes)}。未包含的审批点会等待你确认。`}
Currently using automatic rules: If there are entity-bound documents, the bound documents will be used first; otherwise, it will fall back to all documents being enabled.
@@ -126,8 +118,8 @@ export default function KnowledgeDocumentPicker(props: KnowledgeDocumentPickerPr
})}
- 已选择 {selectedIds.length} 个文档。保持为空会显式关闭知识库检索。
-
+ Selected {selectedIds.length} documents. Leave empty to explicitly turn off knowledge base retrieval.
+
>
)}
diff --git a/client/src/components/layout/AppVersionBadge.tsx b/client/src/components/layout/AppVersionBadge.tsx
index 066fc9946..fe300e9e7 100644
--- a/client/src/components/layout/AppVersionBadge.tsx
+++ b/client/src/components/layout/AppVersionBadge.tsx
@@ -21,13 +21,13 @@ export default function AppVersionBadge({ className }: AppVersionBadgeProps) {
const isDownloading = updater.status === "downloading";
const isChecking = updater.status === "checking";
const label = isDownloaded
- ? "重启更新"
+ ? "Restart update"
: isAvailable
? `可更新 ${formatDesktopVersion(updater.availableVersion ?? "")}`
: isDownloading
? `更新 ${Math.round(updater.progressPercent ?? 0)}%`
: isChecking
- ? "检查更新"
+ ? "Check for updates"
: currentDesktopVersion;
const Icon = isDownloaded ? RotateCw : isAvailable || isDownloading ? Download : isChecking ? RefreshCw : null;
@@ -43,7 +43,7 @@ export default function AppVersionBadge({ className }: AppVersionBadgeProps) {
: "border-border/70 bg-muted/60 text-muted-foreground hover:bg-muted hover:text-foreground",
className,
)}
- title="打开版本与更新"
+ title="Open versions and updates"
aria-label={`打开版本与更新,${label}`}
>
{Icon ? : null}
diff --git a/client/src/components/layout/DesktopBootstrapShell.tsx b/client/src/components/layout/DesktopBootstrapShell.tsx
index 0619e02e7..b3d7760ae 100644
--- a/client/src/components/layout/DesktopBootstrapShell.tsx
+++ b/client/src/components/layout/DesktopBootstrapShell.tsx
@@ -23,15 +23,15 @@ interface DesktopBootstrapShellProps {
function resolveStateLabel(snapshot: DesktopBootstrapSnapshot): string {
switch (snapshot.state) {
case "launching":
- return "准备中";
+ return "In preparation";
case "starting-server":
- return "启动本地引擎";
+ return "Start local engine";
case "loading-ui":
- return "加载工作区";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
case "ready":
- return "已就绪";
+ return "Ready";
case "error":
- return "启动受阻";
+ return "Start blocked";
default:
return snapshot.state;
}
@@ -40,21 +40,21 @@ function resolveStateLabel(snapshot: DesktopBootstrapSnapshot): string {
function resolveStageLabel(snapshot: DesktopBootstrapSnapshot): string {
switch (snapshot.stage) {
case "launching":
- return "准备启动";
+ return "ready to start";
case "app-ready":
- return "应用已就绪";
+ return "Application is ready";
case "splash-shown":
- return "启动页已显示";
+ return "Start page is shown";
case "server-starting":
- return "本地服务启动中";
+ return "Local service starting";
case "server-healthy":
- return "本地服务已就绪";
+ return "Local service is ready";
case "renderer-ready":
- return "界面已准备";
+ return "The interface is ready";
case "main-window-shown":
- return "主窗口已显示";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
case "error":
- return "启动失败";
+ return "Startup failed";
default:
return snapshot.stage;
}
@@ -63,15 +63,15 @@ function resolveStageLabel(snapshot: DesktopBootstrapSnapshot): string {
function resolveProgressHint(snapshot: DesktopBootstrapSnapshot): string {
switch (snapshot.state) {
case "launching":
- return "正在准备桌面运行时和启动资源。";
+ return "Preparing desktop runtime and startup resources.";
case "starting-server":
- return "桌面版需要先拉起本地服务,随后才会进入主工作区。";
+ return "The desktop version needs to start the local service first, and then enter the main workspace.";
case "loading-ui":
- return "本地服务已经可用,正在切入主工作台。";
+ return "The local service is already available and is being cut into the main workbench.";
case "ready":
- return "启动链路已经完成。";
+ return "Starting the link has been completed.";
case "error":
- return "启动过程中遇到问题,建议先查看日志再重试。";
+ return "If you encounter a problem during the startup process, it is recommended to check the logs and try again.";
default:
return snapshot.detail;
}
@@ -80,21 +80,21 @@ function resolveProgressHint(snapshot: DesktopBootstrapSnapshot): string {
function resolveUpdaterStatusLabel(status: DesktopUpdaterSnapshot["status"]): string {
switch (status) {
case "disabled":
- return "不可用";
+ return "Not available";
case "idle":
- return "待检查";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
case "checking":
- return "检查中";
+ return "Under inspection";
case "update-available":
- return "发现更新";
+ return "Discover updates";
case "downloading":
- return "下载中";
+ return "Downloading";
case "downloaded":
- return "待安装";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
case "not-available":
- return "无需更新";
+ return "No update required";
case "error":
- return "检查失败";
+ return "Check failed";
default:
return status;
}
@@ -103,11 +103,11 @@ function resolveUpdaterStatusLabel(status: DesktopUpdaterSnapshot["status"]): st
function resolveUpdaterHint(updater: DesktopUpdaterSnapshot, bootstrapState: DesktopBootstrapSnapshot["state"]): string {
if (!updater.isSupported) {
if (updater.isPortable) {
- return "便携版需要下载新版安装包后手动替换。";
+ return "The portable version needs to download the new version installation package and replace it manually.";
}
if (!updater.isPackaged) {
- return "开发运行不会连接发布更新通道,打包安装版会自动检查桌面版本。";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
return updater.message;
@@ -116,20 +116,20 @@ function resolveUpdaterHint(updater: DesktopUpdaterSnapshot, bootstrapState: Des
switch (updater.status) {
case "idle":
return bootstrapState === "error"
- ? "启动受阻时会同步检查桌面版本,方便先安装可用修复。"
- : "进入工作区前会检查桌面版本,有可用版本时会在这里提示。";
+ ? "When startup is blocked, the desktop version will be checked simultaneously to install available fixes first."
+ : "The desktop version will be checked before entering the workspace, and you will be prompted here if there is an available version.";
case "checking":
- return "版本检查中,有可用版本时会提示下载。";
+ return "During version checking, you will be prompted to download when a version is available.";
case "update-available":
- return `桌面版 ${updater.availableVersion ?? "新版本"} 可用,建议先下载更新包。`;
+ return `桌面版 ${updater.availableVersion ?? "new version"} 可用,建议先下载更新包。`;
case "downloading":
- return "更新包下载中,请保持应用打开。";
+ return "The update package is being downloaded, please keep the app open.";
case "downloaded":
- return "更新包已下载,重启应用后完成安装。";
+ return "The update package has been downloaded and the installation is completed after restarting the application.";
case "not-available":
- return "本机安装版本与发布通道保持同步。";
+ return "Natively installed versions are kept in sync with release channels.";
case "error":
- return updater.message || "版本检查失败,可以稍后重试。";
+ return updater.message || "Version check failed, you can try again later.";
default:
return updater.message;
}
@@ -204,7 +204,7 @@ function DesktopBootstrapUpdatePanel({ snapshot }: { snapshot: DesktopBootstrapS
)}
>
-
版本检查
+
Version check
- 本机版本
+ Native version{updater.currentVersion}
- 可用版本
+ Available versions{updater.availableVersion ?? "-"}
@@ -294,13 +294,13 @@ export default function DesktopBootstrapShell({ snapshot, overlay = false }: Des
- 桌面版 · 测试通道
-
+ Desktop version · Test channel
+
{resolveStageLabel(snapshot)}
-
AI 小说创作工作台
+
AI Novel Creation Workbench
@@ -318,24 +318,24 @@ export default function DesktopBootstrapShell({ snapshot, overlay = false }: Des
)}
- 这个页面只会在桌面版启动时短暂出现,用来承接本地服务启动,避免先看到白屏或空白窗口。
-
+ This page will only appear briefly when the desktop version is started. It is used to start the local service to avoid seeing a white screen or blank window first.
+
-
当前进度
+
Current progress
- 状态
+ Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.{resolveStateLabel(snapshot)}
{resolveProgressHint(snapshot)}
- 最近更新
+ Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.{formatSnapshotTime(snapshot.updatedAt)}
@@ -344,32 +344,32 @@ export default function DesktopBootstrapShell({ snapshot, overlay = false }: Des
-
日志与排查
+
Logs and troubleshooting
- 如果启动卡住、本地服务提前退出,或者你要定位启动耗时,可以直接查看桌面端日志。
-
+ If the startup is stuck, the local service exits early, or you want to locate the startup time, you can directly check the desktop log.
+
diff --git a/client/src/components/layout/DesktopLegacyDataImportCard.tsx b/client/src/components/layout/DesktopLegacyDataImportCard.tsx
index c6ed167c9..6905602e4 100644
--- a/client/src/components/layout/DesktopLegacyDataImportCard.tsx
+++ b/client/src/components/layout/DesktopLegacyDataImportCard.tsx
@@ -55,7 +55,7 @@ export default function DesktopLegacyDataImportCard({
})
.catch((error) => {
if (!cancelled) {
- toast.error(error instanceof Error ? error.message : "旧数据探测失败。");
+ toast.error(error instanceof Error ? error.message : "Old data detection failed.");
}
})
.finally(() => {
@@ -74,10 +74,10 @@ export default function DesktopLegacyDataImportCard({
}
const hasSuggestedSource = Boolean(snapshot?.suggestedSourcePath);
- const title = hasSuggestedSource ? "检测到旧版本地数据库" : "导入旧版本地数据库";
+ const title = hasSuggestedSource ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : "Import old version database";
const description = hasSuggestedSource
- ? "桌面版检测到了你之前 web/开发版使用的本地数据库,可以一键导入并接管原有小说、API Key 和知识库数据。"
- : "桌面版默认使用独立数据目录。如果你之前在 web/开发版里已经有本地数据,可以选择旧的 dev.db 导入到桌面版。";
+ ? "The desktop version has detected the local database used by your previous web/development version, and can import and take over the original novel, API Key and knowledge base data with one click."
+ : "The desktop version uses a separate data directory by default. If you already have local data in the web/development version, you can choose the old dev.db to import to the desktop version.";
const importData = async (preferSuggested: boolean) => {
try {
@@ -87,10 +87,10 @@ export default function DesktopLegacyDataImportCard({
return;
}
if (result?.scheduled) {
- toast("正在准备导入旧数据,应用会自动重启一次。");
+ toast("Preparing to import old data, the application will automatically restart.");
}
} catch (error) {
- toast.error(error instanceof Error ? error.message : "导入旧数据失败。");
+ toast.error(error instanceof Error ? error.message : "Failed to import old data.");
} finally {
setIsImporting(false);
}
@@ -102,30 +102,30 @@ export default function DesktopLegacyDataImportCard({
{title}Desktop
- {snapshot?.currentDatabaseLikelyFresh ? 当前桌面库看起来是空的 : null}
+ {snapshot?.currentDatabaseLikelyFresh ? The current desktop library appears to be empty : null}
- 导入前会自动备份当前桌面数据库到:{snapshot?.backupDirectory ?? "-"}
+ Before importing, the current desktop database will be automatically backed up to:{snapshot?.backupDirectory ?? "-"}
- 导入前请先关闭旧的 web/开发版进程,避免同一份 SQLite 文件还在被写入。
-
+ Please close the old web/development version process before importing to avoid that the same SQLite file is still being written.
+
diff --git a/client/src/components/layout/desktopUpdaterPresentation.ts b/client/src/components/layout/desktopUpdaterPresentation.ts
index 163d9b928..32ffa76d6 100644
--- a/client/src/components/layout/desktopUpdaterPresentation.ts
+++ b/client/src/components/layout/desktopUpdaterPresentation.ts
@@ -11,21 +11,21 @@ export function formatDesktopVersion(version: string): string {
export function getDesktopUpdaterStatusLabel(status: DesktopUpdaterSnapshot["status"]): string {
switch (status) {
case "disabled":
- return "暂不可用";
+ return "Not available yet";
case "idle":
- return "等待检查";
+ return "Waiting for inspection";
case "checking":
- return "正在检查";
+ return "Checking";
case "update-available":
- return "发现新版本";
+ return "new version found";
case "downloading":
- return "正在下载";
+ return "Downloading";
case "downloaded":
- return "等待重启安装";
+ return "Waiting for restart installation";
case "not-available":
- return "版本较新";
+ return "Newer version";
case "error":
- return "检查失败";
+ return "Check failed";
default:
return status;
}
@@ -34,44 +34,44 @@ export function getDesktopUpdaterStatusLabel(status: DesktopUpdaterSnapshot["sta
export function getDesktopUpdaterHint(updater: DesktopUpdaterSnapshot): string {
if (!updater.isSupported) {
if (updater.isPortable) {
- return "便携版需要下载新版安装包后手动替换,现有创作数据不会受到影响。";
+ return "The portable version needs to download the new version installation package and replace it manually. The existing creative data will not be affected.";
}
if (!updater.isPackaged) {
- return "开发环境不会下载安装包,正式安装版可在这里检查和安装更新。";
+ return "The development environment will not download the installation package, and the official installation version can check and install updates here.";
}
- return "此安装包暂时无法连接版本更新服务。";
+ return "This installation package is temporarily unable to connect to the version update service.";
}
switch (updater.status) {
case "idle":
- return "可随时检查桌面版更新。发现新版本后,由你确认下载和重启安装。";
+ return "You can check for updates to the desktop version at any time. After a new version is discovered, it is up to you to confirm the download and restart the installation.";
case "checking":
- return "正在连接版本更新服务,请稍候。";
+ return "Connecting to version update service, please wait.";
case "update-available":
- return `${formatDesktopVersion(updater.availableVersion ?? "新版本")} 可用,下载期间可以继续使用应用。`;
+ return `${formatDesktopVersion(updater.availableVersion ?? "new version")} 可用,下载期间可以继续使用应用。`;
case "downloading":
- return "更新包正在后台下载,请保持应用打开。";
+ return "The update package is being downloaded in the background, please keep the app open.";
case "downloaded":
- return "更新包准备完成。重启应用后会自动安装,未保存的输入请先保存。";
+ return "The update package is ready. It will be installed automatically after restarting the application. Please save unsaved input first.";
case "not-available":
- return "当前安装包符合此更新通道的最新版本。";
+ return "The current installation package is compliant with the latest version of this update channel.";
case "error":
- return "未能完成版本检查,请确认网络连接后重试。";
+ return "Failed to complete version check, please confirm network connection and try again.";
default:
- return "可在这里查看桌面版版本状态。";
+ return "You can check the desktop version status here.";
}
}
export function getDesktopInstallModeLabel(updater: DesktopUpdaterSnapshot): string {
if (updater.isPortable) {
- return "便携版";
+ return "Portable version";
}
if (!updater.isPackaged) {
- return "开发环境";
+ return "development environment";
}
- return "安装版";
+ return "Installation version";
}
export function getDesktopChannelLabel(channel: string): string {
- return channel === "beta" ? "测试通道" : channel === "latest" ? "稳定通道" : `${channel} 通道`;
+ return channel === "beta" ? "test channel" : channel === "latest" ? "stable channel" : `${channel} 通道`;
}
diff --git a/client/src/components/layout/mobile/MobileSiteShell.tsx b/client/src/components/layout/mobile/MobileSiteShell.tsx
index d215e989f..f9b69830c 100644
--- a/client/src/components/layout/mobile/MobileSiteShell.tsx
+++ b/client/src/components/layout/mobile/MobileSiteShell.tsx
@@ -69,7 +69,7 @@ export default function MobileSiteShell({ children }: MobileSiteShellProps) {
- AI 小说创作工作台
+ AI Novel Creation Workbench
{pageTitle}
@@ -82,8 +82,8 @@ export default function MobileSiteShell({ children }: MobileSiteShellProps) {
setMoreOpen(false)}>
- 开书
-
+ open book
+
setMoreOpen((current) => !current)}
- aria-label={moreOpen ? "关闭更多入口" : "打开更多入口"}
+ aria-label={moreOpen ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : "Open more entrances"}
>
{moreOpen ? : }
@@ -101,8 +101,8 @@ export default function MobileSiteShell({ children }: MobileSiteShellProps) {
size="icon"
className="h-8 w-8"
onClick={() => setVisualAssetLibraryOpen(true)}
- aria-label="打开视觉资源库"
- title="视觉资源库"
+ aria-label="Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."
+ title="Visual Resource Library"
>
@@ -119,8 +119,8 @@ export default function MobileSiteShell({ children }: MobileSiteShellProps) {
- AI 创作实况 / LIVE LOG
+ AI creation live / LIVE LOG
- 每次调用独立显示。新调用会自动聚焦,已完成调用会收起;清空只影响当前窗口。
-
+ Each call is displayed independently. New calls will be automatically focused, and completed calls will be hidden; clearing only affects the current window.
+
- {connected ? "前台日志已清空,等待新的 AI 生成开始…" : "正在连接 AI 实况服务…"}
+ {connected ? "The foreground log has been cleared, waiting for new AI generation to start..." : "Connecting to AI live service..."}
)}
diff --git a/client/src/components/onboarding/CreationSetupNotice.tsx b/client/src/components/onboarding/CreationSetupNotice.tsx
index 600f2bf87..3d0282266 100644
--- a/client/src/components/onboarding/CreationSetupNotice.tsx
+++ b/client/src/components/onboarding/CreationSetupNotice.tsx
@@ -23,14 +23,14 @@ export default function CreationSetupNotice() {
-
完成快捷配置后就可以启动 AI 创作
+
After completing the quick configuration, you can start AI creation
- {status?.blockingReasons[0] ?? "选择一个文本模型,系统会自动准备规划、正文、审校和修复所需的任务路由。"}
+ {status?.blockingReasons[0] ?? "Select a text model, and the system will automatically prepare the task routes required for planning, body text, proofreading, and repair."}
connect {form.providerKind === "custom" ? form.customProviderName || "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : selectedProvider?.name}
+
API Key will only be saved to the local or server-side key storage and will not be displayed in the completion results.
+ Once completed, this model serves as the initial default for core tasks such as planning, writing, reviewing, fixing, replanning, and summarizing.
+
) : (
@@ -369,8 +369,8 @@ export default function QuickSetupDialog(props: QuickSetupDialogProps) {
-
正在检测普通文本与结构化输出
-
检测通过后,系统会自动准备全部核心创作任务,不需要逐项配置路由。
+
Detecting plain text vs structured output
+
After passing the test, the system will automatically prepare all core creation tasks, and there is no need to configure routing one by one.
- {completeMutation.error instanceof Error ? completeMutation.error.message : "请检查 API Key、地址和模型名称后重试。"}
+ {completeMutation.error instanceof Error ? completeMutation.error.message : "Please check the API Key, address, and model name and try again."}
- {selectedBeat.summary?.trim() || "这一段还没有节奏说明,建议先回到节奏板补齐交付目标。"}
+ {selectedBeat.summary?.trim() || "There is no rhythm explanation for this section yet, so it is recommended to go back to the rhythm board to complete the delivery target."}
- {beats.length > 0 ? "当前查看整卷走势。切到具体节奏段后,可对照该段摘要和必须交付项调整强度。" : "当前卷还没有节奏段,先生成节奏板后再按段检查曲线。"}
+ {beats.length > 0 ? "Currently view the trend of the entire volume. Once you cut to a specific rhythm segment, you can adjust the intensity based on the segment summary and required deliverables." : "There is no rhythm section in the current volume. First generate the rhythm board and then check the curve by section."}
- 当前视图只用于查看,不会改动草稿
-
+ The current view is only for viewing and the draft will not be modified.
+
{showReferenceCurve && referenceTemplate ? (
- {referenceTemplate.label}参考
+ {referenceTemplate.label} Reference
) : null}
- }>
- setSelectedKind(undefined)}>全部
+ }>
+ setSelectedKind(undefined)}>Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
{kindFacets.map((facet) => (
setSelectedKind(facet.value)}>
{getVisualAssetKindLabel(facet.value)} {facet.count}
))}
-
- setSelectedSource(undefined)}>全部
+
+ setSelectedSource(undefined)}>Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
{sourceFacets.map((facet) => (
setSelectedSource(facet.value)}>
{getVisualAssetSourceLabel(facet.value)} {facet.count}
@@ -186,77 +186,18 @@ export function VisualAssetLibrary({
: null}
diff --git a/client/src/hooks/useDirectorChapterTitleRepair.ts b/client/src/hooks/useDirectorChapterTitleRepair.ts
index 8ab6ac75b..f2dd6c23c 100644
--- a/client/src/hooks/useDirectorChapterTitleRepair.ts
+++ b/client/src/hooks/useDirectorChapterTitleRepair.ts
@@ -22,7 +22,7 @@ export function useDirectorChapterTitleRepair(options: DirectorChapterTitleRepai
mutationFn: async (task: UnifiedTaskDetail) => {
const warning = resolveChapterTitleWarning(task);
if (!warning) {
- throw new Error("当前任务没有可直接 AI 修复的章节标题提醒。");
+ throw new Error("The current mission does not have a chapter title reminder that can be directly AI fixed.");
}
const response = await repairNovelWorkflowChapterTitles(task.id, {
volumeId: warning.volumeId ?? undefined,
@@ -52,10 +52,10 @@ export function useDirectorChapterTitleRepair(options: DirectorChapterTitleRepai
warning,
})).catch(() => {});
}
- toast.success("已开始 AI 修复章节标题,系统正在重写当前卷拆章。");
+ toast.success("AI repair of chapter titles has started, and the system is rewriting the current chapter chapters.");
},
onError: (error) => {
- const message = error instanceof Error ? error.message : "AI 修复章节标题失败。";
+ const message = error instanceof Error ? error.message : "AI failed to fix chapter titles.";
toast.error(message);
},
});
@@ -63,7 +63,7 @@ export function useDirectorChapterTitleRepair(options: DirectorChapterTitleRepai
return {
startRepair: (task: UnifiedTaskDetail | null | undefined) => {
if (!task) {
- toast.error("当前没有可修复的自动导演任务。");
+ toast.error("There are currently no autodirector tasks to fix.");
return;
}
mutation.mutate(task);
diff --git a/client/src/hooks/useLlmLiveFeed.ts b/client/src/hooks/useLlmLiveFeed.ts
index 283225bef..a50cf74f5 100644
--- a/client/src/hooks/useLlmLiveFeed.ts
+++ b/client/src/hooks/useLlmLiveFeed.ts
@@ -17,7 +17,7 @@ function updateSession(
context: event.context,
seq: event.seq,
phase: "requesting",
- phaseMessage: "正在连接模型",
+ phaseMessage: "Connecting model",
preview: "",
totalChars: 0,
startedAt: event.at,
@@ -34,7 +34,7 @@ function updateSession(
...current,
seq: event.seq,
phase: current.phase === "requesting" ? "streaming" : current.phase,
- phaseMessage: current.phase === "requesting" ? "模型正在返回内容" : current.phaseMessage,
+ phaseMessage: current.phase === "requesting" ? "The model is returning content" : current.phaseMessage,
preview: preview.length > MAX_PREVIEW_CHARS ? preview.slice(-MAX_PREVIEW_CHARS) : preview,
totalChars: event.totalChars,
updatedAt: event.at,
@@ -57,7 +57,7 @@ function updateSession(
...current,
seq: event.seq,
phase: "completed",
- phaseMessage: "模型结果已准备完成",
+ phaseMessage: "Model results are ready",
totalChars: event.totalChars,
updatedAt: event.at,
completedAt: event.at,
@@ -148,7 +148,7 @@ export function useLlmLiveFeed(input: {
{ signal: controller.signal },
);
if (!response.ok || !response.body) {
- throw new Error("生成实况连接失败");
+ throw new Error("Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.");
}
setConnected(true);
const reader = response.body.getReader();
diff --git a/client/src/hooks/useSSE.ts b/client/src/hooks/useSSE.ts
index 5078299fe..83183d074 100644
--- a/client/src/hooks/useSSE.ts
+++ b/client/src/hooks/useSSE.ts
@@ -150,7 +150,7 @@ export function useSSE(options?: UseSSEOptions) {
}
} catch (streamError) {
if ((streamError as Error).name !== "AbortError") {
- setError(streamError instanceof Error ? streamError.message : "流式请求失败。");
+ setError(streamError instanceof Error ? streamError.message : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.");
setIsStreaming(false);
}
} finally {
diff --git a/client/src/lib/autoDirectorPauseNotifications.ts b/client/src/lib/autoDirectorPauseNotifications.ts
index 61740d287..8dc86d97d 100644
--- a/client/src/lib/autoDirectorPauseNotifications.ts
+++ b/client/src/lib/autoDirectorPauseNotifications.ts
@@ -99,7 +99,7 @@ export function showAutoDirectorPauseNotification(input: {
return false;
}
- const notification = new window.Notification("自动导演需要你处理", {
+ const notification = new window.Notification("Auto Director needs you to handle it", {
body: buildAutoDirectorPauseNotificationBody(input.item),
tag: NOTIFICATION_TAG,
});
diff --git a/client/src/lib/desktop.ts b/client/src/lib/desktop.ts
index 0ee33f344..5cc6cf673 100644
--- a/client/src/lib/desktop.ts
+++ b/client/src/lib/desktop.ts
@@ -56,8 +56,8 @@ export interface DesktopDataImportResult {
const DEFAULT_BOOTSTRAP_SNAPSHOT: DesktopBootstrapSnapshot = {
state: "launching",
stage: "launching",
- title: "正在启动桌面工作区",
- detail: "正在准备桌面本地运行时。",
+ title: "Starting desktop workspace",
+ detail: "Preparing desktop local runtime.",
logDir: "",
logFile: "",
updatedAt: "",
@@ -66,7 +66,7 @@ const DEFAULT_BOOTSTRAP_SNAPSHOT: DesktopBootstrapSnapshot = {
const DEFAULT_UPDATER_SNAPSHOT: DesktopUpdaterSnapshot = {
status: "disabled",
- message: "当前运行环境无法使用桌面版更新。",
+ message: "The current operating environment cannot use desktop version updates.",
currentVersion: "0.0.0",
availableVersion: null,
progressPercent: null,
diff --git a/client/src/lib/directorTaskNotice.ts b/client/src/lib/directorTaskNotice.ts
index 81795708e..c93b4e847 100644
--- a/client/src/lib/directorTaskNotice.ts
+++ b/client/src/lib/directorTaskNotice.ts
@@ -26,7 +26,7 @@ export function parseDirectorTaskNotice(meta: Record | null | u
type: notice.action.type === "open_structured_outline" ? "open_structured_outline" : "open_structured_outline",
label: typeof notice.action.label === "string" && notice.action.label.trim()
? notice.action.label.trim()
- : "快速修复章节标题",
+ : "Quickly fix chapter titles",
volumeId: typeof notice.action.volumeId === "string" && notice.action.volumeId.trim()
? notice.action.volumeId.trim()
: null,
@@ -40,9 +40,9 @@ export function isChapterTitleDiversitySummary(value: string | null | undefined)
if (!normalized) {
return false;
}
- return normalized.includes("章节标题结构过于集中")
- || normalized.includes("相邻章节标题结构过于重复")
- || normalized.includes("章节标题出现重复");
+ return normalized.includes("Chapter title structure is too concentrated")
+ || normalized.includes("The title structure of adjacent chapters is too repetitive")
+ || normalized.includes("Chapter titles are duplicated");
}
export function buildStructuredOutlineRoute(
@@ -94,7 +94,7 @@ export function resolveChapterTitleWarning(task: StructuredOutlineTaskLike | nul
return {
summary: taskNotice.summary,
route: buildTaskNoticeRoute(task, taskNotice),
- label: "快速修复章节标题",
+ label: "Quickly fix chapter titles",
volumeId: taskNotice.action?.volumeId ?? task.resumeTarget?.volumeId ?? seedResumeTarget?.volumeId ?? null,
};
}
@@ -104,7 +104,7 @@ export function resolveChapterTitleWarning(task: StructuredOutlineTaskLike | nul
return {
summary: task.failureSummary?.trim() ?? "",
route: buildStructuredOutlineRoute(task, task.resumeTarget?.volumeId ?? seedResumeTarget?.volumeId ?? null),
- label: "快速修复章节标题",
+ label: "Quickly fix chapter titles",
volumeId: task.resumeTarget?.volumeId ?? seedResumeTarget?.volumeId ?? null,
};
}
diff --git a/client/src/lib/novelWorkflowActivityTags.ts b/client/src/lib/novelWorkflowActivityTags.ts
index f40f4a815..311968b69 100644
--- a/client/src/lib/novelWorkflowActivityTags.ts
+++ b/client/src/lib/novelWorkflowActivityTags.ts
@@ -1,11 +1,11 @@
const WORKFLOW_ACTIVITY_TAGS = [
- "资产回灌中",
- "角色成长中",
- "状态同步中",
- "资源账本同步中",
- "伏笔账本同步中",
- "账本校准中",
- "伏笔回填中",
+ "Assets are being reintroduced",
+ "Character development",
+ "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ "The foreshadowing ledger is being synchronized",
+ "Ledger calibration in progress",
+ "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
] as const;
export function extractWorkflowActivityTags(value: string | null | undefined): string[] {
diff --git a/client/src/lib/novelWorkflowContinuation.ts b/client/src/lib/novelWorkflowContinuation.ts
index 4f5edfb65..6ee89a76c 100644
--- a/client/src/lib/novelWorkflowContinuation.ts
+++ b/client/src/lib/novelWorkflowContinuation.ts
@@ -14,7 +14,7 @@ export function resolveWorkflowContinuationFeedback(
} {
const requestedScopeLabel = options?.scopeLabel?.trim();
const taskScopeLabel = task && "executionScopeLabel" in task ? task.executionScopeLabel?.trim() : undefined;
- const scopeLabel = requestedScopeLabel || taskScopeLabel || "当前章节范围";
+ const scopeLabel = requestedScopeLabel || taskScopeLabel || "Current chapter scope";
if (task && "kind" in task && task.status === "failed") {
return {
@@ -24,7 +24,7 @@ export function resolveWorkflowContinuationFeedback(
|| task.lastError?.trim()
|| (options?.mode === "auto_execute_range"
? `继续自动执行${scopeLabel}失败。`
- : "继续自动导演失败。"),
+ : "Continue automatic director failure."),
};
}
@@ -34,7 +34,7 @@ export function resolveWorkflowContinuationFeedback(
? `已跳过本次质量建议,自动导演会继续执行${scopeLabel}。`
: options?.mode === "auto_execute_range"
? `已继续自动执行${scopeLabel}。`
- : "自动导演已继续推进。",
+ : "Autodirector has moved on.",
};
}
@@ -48,7 +48,7 @@ export function resolveDirectorContinueMode(task: Pick<
if (
task?.checkpointType === "replan_required"
|| task?.currentItemKey === "quality_repair"
- || task?.currentStage?.includes("质量")
+ || task?.currentStage?.includes("quality")
) {
return "skip_quality_repair";
}
diff --git a/client/src/lib/novelWorkflowTaskUi.ts b/client/src/lib/novelWorkflowTaskUi.ts
index fa411e518..beee2e5f9 100644
--- a/client/src/lib/novelWorkflowTaskUi.ts
+++ b/client/src/lib/novelWorkflowTaskUi.ts
@@ -15,7 +15,7 @@ type WorkflowTaskLike = {
export const LIVE_TASK_STATUSES = new Set(["queued", "running", "waiting_approval"]);
export const BACKGROUND_RUNNING_TASK_STATUSES = new Set(["running"]);
-function getExecutionScopeLabel(scopeLabel?: string | null, fallback = "第 1-10 章"): string {
+function getExecutionScopeLabel(scopeLabel?: string | null, fallback = "Chapter 1-10"): string {
return scopeLabel?.trim() || fallback;
}
@@ -33,27 +33,27 @@ function buildAutoExecutionCancelledLabel(scopeLabel?: string | null): string {
export function formatWorkflowCheckpoint(checkpoint?: NovelWorkflowCheckpoint | null, scopeLabel?: string | null): string {
if (checkpoint === "candidate_selection_required") {
- return "等待确认书级方向";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
if (checkpoint === "book_contract_ready") {
- return "Book Contract 已就绪";
+ return "Book Contract is ready";
}
if (checkpoint === "character_setup_required") {
- return "角色准备待审核";
+ return "Role preparation pending review";
}
if (checkpoint === "volume_strategy_ready") {
- return "卷战略待审核";
+ return "Volume strategy pending review";
}
if (checkpoint === "chapter_batch_ready") {
return buildAutoExecutionPausedLabel(scopeLabel);
}
if (checkpoint === "replan_required") {
- return "等待重规划";
+ return "Waiting for re-planning";
}
if (checkpoint === "workflow_completed") {
- return "自动导演已完成";
+ return "Autodirector completed";
}
- return "自动导演";
+ return "automatic director";
}
export function getWorkflowBadge(task?: NovelAutoDirectorTaskSummary | null): {
@@ -89,31 +89,31 @@ export function getWorkflowBadge(task?: NovelAutoDirectorTaskSummary | null): {
}
if (task.status === "running") {
return {
- label: displayStatus ?? "自动导演进行中",
+ label: displayStatus ?? "Auto director in progress",
variant: "default",
};
}
if (task.status === "queued") {
return {
- label: displayStatus ?? "自动导演排队中",
+ label: displayStatus ?? "Automatic director queued",
variant: "secondary",
};
}
if (task.status === "failed") {
return {
- label: displayStatus ?? "自动导演失败",
+ label: displayStatus ?? "Auto director failed",
variant: "destructive",
};
}
if (task.status === "cancelled") {
return {
- label: displayStatus ?? "自动导演已取消",
+ label: displayStatus ?? "Auto director canceled",
variant: "outline",
};
}
return {
label: displayStatus ?? (task.checkpointType === "workflow_completed"
- ? "自动导演已完成"
+ ? "Autodirector completed"
: formatWorkflowCheckpoint(task.checkpointType, task.executionScopeLabel)),
variant: "outline",
};
diff --git a/client/src/lib/textFile.ts b/client/src/lib/textFile.ts
index 6fefe30f6..fe963cf02 100644
--- a/client/src/lib/textFile.ts
+++ b/client/src/lib/textFile.ts
@@ -1,5 +1,5 @@
const TEXT_FILE_ENCODING_CANDIDATES = ["utf-8", "gb18030", "gbk", "big5", "utf-16le", "utf-16be"] as const;
-const SUSPICIOUS_MOJIBAKE_TOKENS = ["銆€", "锛", "鏈功", "涓€", "鍥犱负"] as const;
+const SUSPICIOUS_MOJIBAKE_TOKENS = ["銆€", "Adze", "Chain power", "trickle €", "Perseverance"] as const;
function detectTxtBomEncoding(bytes: Uint8Array): string | null {
if (bytes.length >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) {
diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx
index 92b8f511d..be8596b28 100644
--- a/client/src/pages/Home.tsx
+++ b/client/src/pages/Home.tsx
@@ -78,8 +78,8 @@ export default function Home() {
error instanceof Error
? error.message
: input.mode === "auto_execute_range"
- ? "继续自动执行当前章节范围失败。"
- : "继续自动导演失败。",
+ ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."
+ : "Continue automatic director failure.",
);
},
});
@@ -145,7 +145,7 @@ export default function Home() {
}}
disabled={isWorkflowPending}
>
- {isWorkflowPending ? "继续执行中..." : (task?.resumeAction ?? `继续自动执行${task?.executionScopeLabel ?? "当前章节范围"}`)}
+ {isWorkflowPending ? "Continue to execute..." : (task?.resumeAction ?? `继续自动执行${task?.executionScopeLabel ?? "Current chapter scope"}`)}
);
}
@@ -165,7 +165,7 @@ export default function Home() {
}}
disabled={isWorkflowPending}
>
- {isWorkflowPending ? "继续中..." : (task?.resumeAction ?? "继续导演")}
+ {isWorkflowPending ? "Continue..." : (task?.resumeAction ?? "continue directing")}
);
}
@@ -177,7 +177,7 @@ export default function Home() {
to={getCandidateSelectionLink(task!.id)}
onClick={stopPropagation ? stopCardClick : undefined}
>
- {task!.resumeAction ?? "继续确认书级方向"}
+ {task!.resumeAction ?? "Continue to confirm the book-level direction"}
);
@@ -190,8 +190,8 @@ export default function Home() {
to={`/novels/${novel.id}/edit`}
onClick={stopPropagation ? stopCardClick : undefined}
>
- 进入章节执行
-
+ Enter chapter execution
+
);
}
@@ -203,8 +203,8 @@ export default function Home() {
to={`/novels/${novel.id}/edit?directorTaskId=${task.id}`}
onClick={stopPropagation ? stopCardClick : undefined}
>
- 查看推进状态
-
+ View advancement status
+
);
}
@@ -215,8 +215,8 @@ export default function Home() {
to={`/novels/${novel.id}/edit`}
onClick={stopPropagation ? stopCardClick : undefined}
>
- 编辑小说
-
+ Edit novel
+
);
};
diff --git a/client/src/pages/antiAiRules/AntiAiRulesPage.tsx b/client/src/pages/antiAiRules/AntiAiRulesPage.tsx
index ef454b417..ef837d619 100644
--- a/client/src/pages/antiAiRules/AntiAiRulesPage.tsx
+++ b/client/src/pages/antiAiRules/AntiAiRulesPage.tsx
@@ -111,20 +111,20 @@ export default function AntiAiRulesPage() {
mutationFn: (payload: ReturnType) => createAntiAiRule(payload),
onSuccess: async () => {
await refreshRules();
- toast.success("反 AI 规则已创建。");
+ toast.success("Anti-AI rules created.");
setDialogOpen(false);
},
- onError: (error) => toast.error(error instanceof Error ? error.message : "创建规则失败。"),
+ onError: (error) => toast.error(error instanceof Error ? error.message : "Failed to create rule."),
});
const updateMutation = useMutation({
mutationFn: ({ id, payload }: { id: string; payload: Partial> }) => updateAntiAiRule(id, payload),
onSuccess: async () => {
await refreshRules();
- toast.success("反 AI 规则已保存。");
+ toast.success("Anti-AI rules saved.");
setDialogOpen(false);
},
- onError: (error) => toast.error(error instanceof Error ? error.message : "保存规则失败。"),
+ onError: (error) => toast.error(error instanceof Error ? error.message : "Failed to save rules."),
});
const aiDraftMutation = useMutation({
@@ -148,7 +148,7 @@ export default function AntiAiRulesPage() {
onSuccess: (response) => {
const result = response.data;
if (!result) {
- toast.error("AI 没有返回可用草稿。");
+ toast.error("The AI did not return a usable draft.");
return;
}
setForm({
@@ -164,9 +164,9 @@ export default function AntiAiRulesPage() {
globalBaselineEnabled: result.draft.globalBaselineEnabled,
autoRewrite: result.draft.autoRewrite,
});
- toast.success("草稿填入表单,请检查后保存。");
+ toast.success("Fill in the form as a draft, please check it and save it.");
},
- onError: (error) => toast.error(error instanceof Error ? error.message : "AI 生成草稿失败。"),
+ onError: (error) => toast.error(error instanceof Error ? error.message : "AI failed to generate draft."),
});
const detectionMutation = useMutation({
@@ -179,7 +179,7 @@ export default function AntiAiRulesPage() {
temperature: 0.2,
}),
onSuccess: () => setRewritePreview(""),
- onError: (error) => toast.error(error instanceof Error ? error.message : "检测失败。"),
+ onError: (error) => toast.error(error instanceof Error ? error.message : "Detection failed."),
});
const rewriteMutation = useMutation({
@@ -212,7 +212,7 @@ export default function AntiAiRulesPage() {
});
},
onSuccess: (response) => setRewritePreview(response.data?.content ?? ""),
- onError: (error) => toast.error(error instanceof Error ? error.message : "修正失败。"),
+ onError: (error) => toast.error(error instanceof Error ? error.message : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."),
});
useEffect(() => {
@@ -239,7 +239,7 @@ export default function AntiAiRulesPage() {
event.preventDefault();
const payload = buildPayload(form);
if (!payload.key || !payload.name || !payload.description) {
- toast.error("请填写规则标识、名称和说明。");
+ toast.error("Please fill in the rule ID, name and description.");
return;
}
if (editingRule) {
@@ -268,16 +268,16 @@ export default function AntiAiRulesPage() {
- 反 AI 规则
-
+ Anti-AI rules
+
- 管理正文生成会参考的反 AI 规则,控制哪些规则进入全局默认,哪些只留给写法资产绑定使用。
-
+ Manage the anti-AI rules that will be referenced when generating text, and control which rules enter the global default and which ones are only reserved for writing asset binding.
+
- 新建规则
-
+ New rule
+
diff --git a/client/src/pages/antiAiRules/antiAiRulesPage.shared.ts b/client/src/pages/antiAiRules/antiAiRulesPage.shared.ts
index 413292551..9c163bf76 100644
--- a/client/src/pages/antiAiRules/antiAiRulesPage.shared.ts
+++ b/client/src/pages/antiAiRules/antiAiRulesPage.shared.ts
@@ -31,15 +31,15 @@ export const emptyForm: RuleFormState = {
};
export const typeLabels: Record = {
- forbidden: "禁用",
- risk: "风险",
- encourage: "鼓励",
+ forbidden: "Disable",
+ risk: "risk",
+ encourage: "encourage",
};
export const severityLabels: Record = {
- low: "低",
- medium: "中",
- high: "高",
+ low: "Low",
+ medium: "middle",
+ high: "high",
};
export function ruleToForm(rule: AntiAiRule): RuleFormState {
diff --git a/client/src/pages/antiAiRules/components/AntiAiEffectivePreviewCard.tsx b/client/src/pages/antiAiRules/components/AntiAiEffectivePreviewCard.tsx
index fd4a0a4bd..13dd8a46a 100644
--- a/client/src/pages/antiAiRules/components/AntiAiEffectivePreviewCard.tsx
+++ b/client/src/pages/antiAiRules/components/AntiAiEffectivePreviewCard.tsx
@@ -18,11 +18,11 @@ export default function AntiAiEffectivePreviewCard(props: AntiAiEffectivePreview
- 生效预览
-
+ Effective preview
+
- 查看正文生成会拿到的全局规则,以及选中写法后叠加的专属规则。
-
+ View the global rules that will be obtained when the text is generated, as well as the exclusive rules that are superimposed after selecting the writing method.
+
{props.loading ? (
-
- {props.editingRule
- ? "描述要调整的方向,AI 会基于表单内容优化规则。"
- : "描述想压制或鼓励的表达,AI 会生成一条可编辑规则草稿。"}
+ {props.editingRule ? "Describes the direction to be adjusted; AI will optimize the rule based on the form content." : "Describes the expression you want to suppress or encourage; AI will generate an editable rule draft."}
+ {candidateCharacters.length} candidates, and in-depth archives can be generated on demand.
+
- {candidateExpanded ? "收起" : "展开"}
+ {candidateExpanded ? "Collapse" : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."}
{candidateExpanded ? (
@@ -410,11 +405,11 @@ export default function BookAnalysisCharacterPanel(props: BookAnalysisCharacterP
/>
- 展开
+ Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
{!selectedAnalysis.isCurrentVersion ? (
- 该分析基于旧版源文档,当前激活文档版本为 v{selectedAnalysis.currentDocumentVersionNumber}。
+ This analysis is based on an older version of the source document, currently the active document version is v{selectedAnalysis.currentDocumentVersionNumber}。
- {selectedAnalysis.summary?.trim() || "生成总览后会在此显示概要内容。"}
+ {selectedAnalysis.summary?.trim() || "The summary will be displayed here after the overview is generated."}
+ readable {sectionStats.readableExpected}/{sectionStats.expected}
+ {sectionStats.unselected > 0 ? Not selected this time {sectionStats.unselected} : null}
+ {sectionStats.frozenReadable > 0 ? Results frozen {sectionStats.frozenReadable} : null}
setReadingMode("summary")}
>
- 重点速览
-
+ Highlights
+
setReadingMode("full")}
>
- 完整阅读
-
+ Read in full
+
{selectedAnalysis.sections.length === 0 ? (
-
没有可展示的拆书小节
+
There is no unpacking section to display
- 这份任务没有返回可阅读内容。源文档仍然安全,可以从上方重新生成或打开任务中心查看详情。
-
+ This task returned no readable content. The source document is still safe and can be regenerated from above or open the Task Center to view details.
+
+ Here, use the book-opening framework to check your manuscript, focusing on whether the rhythm, characters, themes, foreshadowing, and commercial selling points are clear; the conclusions are used to make judgments about revisions, and the text of the original novel will not be changed.
+
{unselectedSection
- ? "这个小节未纳入本次生成。需要补充时,可先取消跳过并保存,再重新生成。"
- : "当前小节还没有可展示的内容。"}
+ ? "This section is not included in this generation. If you need to supplement, you can cancel the skip and save it, and then regenerate it."
+ : "There is no content to display in the current section."}
);
@@ -97,8 +97,8 @@ export default function BookAnalysisSectionCard(props: BookAnalysisSectionCardPr
{section.title}
- {unselectedSection ? "本次未选择" : formatStatus(section.status)}
- {draft.frozen && !unselectedSection ? 已冻结 : null}
+ {unselectedSection ? "Not selected this time" : formatStatus(section.status)}
+ {draft.frozen && !unselectedSection ? Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know. : null}
@@ -226,51 +226,45 @@ export default function BookAnalysisSectionCard(props: BookAnalysisSectionCardPr
disabled={!canOperate}
onChange={(event) => onDraftChange(section, { frozen: event.target.checked })}
/>
- 跳过自动重跑,并保留这个小节的现有内容。
-
+ Skip auto-rerun and keep the existing content of this section.
+
{draft.frozen || frozenChangePending ? (
- {frozenChangePending
- ? draft.frozen
- ? "跳过设置尚未保存。保存后,自动重跑会保留这个小节的现有内容。"
- : "取消跳过尚未保存。保存后,即可重新生成或使用 AI 优化。"
- : unselectedSection
- ? "这个小节未纳入本次生成。需要补充时,请先取消跳过并保存。"
- : "当前内容已冻结。需要重新生成或 AI 优化时,请先取消跳过并保存。"}
+ {frozenChangePending ? draft.frozen ? "Skip settings have not been saved. After saving, automatic rerun will retain the existing content of this section." : "Cancellation of skipping has not been saved. After saving, you can regenerate or use AI optimization." : unselectedSection ? "This section was not included in this generation. If you need to add it, please cancel skipping and save first." : "Current content is frozen. If you need to regenerate or use AI optimization, please cancel skipping and save first."}
) : null}
-
本节特别关注
+
This section pays special attention to
onDraftChange(section, { focusInstruction: event.target.value })}
- placeholder="例如:只看阶段推进里的转折证据,或重点检查人物高光是否能复用。"
+ placeholder="For example: only look at the evidence of transitions in stage advancement, or focus on checking whether character highlights can be reused."
/>
-
编辑正文
+
Edit text
onDraftChange(section, { editedContent: event.target.value })}
- placeholder="在此直接编辑当前小节草稿。"
+ placeholder="Edit the current section draft directly here."
/>
-
AI 优化 / 修正
+
AI Optimization/Correction
onDraftChange(section, { optimizeInstruction: event.target.value })}
- placeholder="输入优化或修正提示词,例如:压缩冗余、突出冲突、保持同样事实。"
+ placeholder="Enter optimization or correction prompt words, such as: compress redundancy, highlight conflicts, maintain the same facts."
/>
- {keyword.trim() || status
- ? "没有符合当前筛选的拆书分析,可以调整筛选条件。"
- : "暂无拆书分析,点击上方「新建拆书」开始。"}
+ {keyword.trim() || status ? "No book breakdown analysis matching the current filter. You can adjust the filter criteria." : "No book breakdown analysis available. Click 'Create Book Breakdown' above to begin."}
) : null}
diff --git a/client/src/pages/bookAnalysis/components/BookAnalysisSourceRangePicker.tsx b/client/src/pages/bookAnalysis/components/BookAnalysisSourceRangePicker.tsx
index fbbdb502f..ddacf1d88 100644
--- a/client/src/pages/bookAnalysis/components/BookAnalysisSourceRangePicker.tsx
+++ b/client/src/pages/bookAnalysis/components/BookAnalysisSourceRangePicker.tsx
@@ -21,9 +21,9 @@ interface BookAnalysisSourceRangePickerProps {
}
const CHAR_PRESETS = [
- { label: "前 5 万字", value: 50_000 },
- { label: "前 10 万字", value: 100_000 },
- { label: "前 20 万字", value: 200_000 },
+ { label: "First 50,000 words", value: 50_000 },
+ { label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.", value: 100_000 },
+ { label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.", value: 200_000 },
];
const numberFormatter = new Intl.NumberFormat("zh-CN");
@@ -50,7 +50,7 @@ function parseCharInput(input: string): number | null {
return null;
}
const unit = match[2];
- if (unit === "万") {
+ if (unit === "Ten thousand") {
return Math.round(value * 10_000);
}
if (unit === "k") {
@@ -242,23 +242,23 @@ export default function BookAnalysisSourceRangePicker({
const rangeTitle = selectedRange && selectedStartChapter && selectedEndChapter
? `第 ${selectedStartChapter.chapterIndex + 1} 章 ~ 第 ${selectedEndChapter.chapterIndex + 1} 章`
- : "全文";
+ : "full text";
const rangeDetail = selectedRange && selectedStartChapter && selectedEndChapter
? `${selectedChapterCount} 章 · 约 ${formatCount(selectedCharCount)} 字 · 占全文 ${Math.round(percent)}%`
: `${sortedChapters.length > 0 ? `${sortedChapters.length} 章 · ` : ""}约 ${formatCount(sourceCharCount)} 字`;
const charModeHint = selectedRange && selectedStartChapter && selectedEndChapter
? `按章节边界覆盖第 ${selectedStartChapter.chapterIndex + 1} 章 ~ 第 ${selectedEndChapter.chapterIndex + 1} 章`
- : "输入字数后会自动换算为章节范围";
+ : "After entering the number of words, it will be automatically converted into a chapter range.";
return (
-
原文范围
+
Original scope
{([
- ["full", "全文"],
- ["chapter", "按章节"],
- ["chars", "按字数"],
+ ["full", "full text"],
+ ["chapter", "by chapter"],
+ ["chars", "By word count"],
] as const).map(([key, label]) => (
{sortedChapters.map((chapter) => (
))}
@@ -309,7 +309,7 @@ export default function BookAnalysisSourceRangePicker({
.filter((chapter) => chapter.chapterIndex >= (selectedRange?.startChapterIndex ?? sortedChapters[0]?.chapterIndex ?? 0))
.map((chapter) => (
))}
@@ -334,7 +334,7 @@ export default function BookAnalysisSourceRangePicker({
disabled={!canUseChapterRange}
onChange={(event) => setCharStartInput(event.target.value)}
onBlur={() => applyCharRange(charStartInput, charEndInput)}
- placeholder="起始字数,如 5000"
+ placeholder="Starting word count, such as 5000"
/>
setCharEndInput(event.target.value)}
onBlur={() => applyCharRange(charStartInput, charEndInput)}
- placeholder="结束字数,如 5万"
+ placeholder="End word count, such as 50,000"
/>
) : null}
@@ -401,31 +401,7 @@ function QuickButton({ children, onClick }: { children: string; onClick: () => v
{children}
- );
-}
-
-function RangeLoadHint({
- requested,
- loading,
- error,
- sourceSelected,
-}: {
- requested: boolean;
- loading: boolean;
- error?: string;
- sourceSelected: boolean;
-}) {
- let message = "选择文档后可按章节或字数限制本次分析输入。";
- if (sourceSelected && loading) {
- message = "正在加载章节范围...";
- } else if (sourceSelected && error) {
- message = "章节范围加载失败,可先按全文创建拆书。";
- } else if (sourceSelected && requested) {
- message = "当前文档章节不足,可按全文创建拆书。";
- } else if (sourceSelected) {
- message = "切换到范围模式后会加载章节范围。";
- }
- return (
+ ); } function RangeLoadHint({ requested, loading, error, sourceSelected, }: { requested: boolean; loading: boolean; error?: string; sourceSelected: boolean; }) { let message = "After selecting a document, you can limit the input for this analysis by chapter or word count."; if (sourceSelected && loading) { message = "Loading chapter range..."; } else if (sourceSelected && error) { message = "Chapter range loading failed, you can create a book breakdown by full text first."; } else if (sourceSelected && requested) { message = "The current document has insufficient chapters, you can create a book breakdown by full text."; } else if (sourceSelected) { message = "Chapter range will be loaded after switching to range mode."; } return (
- 以下字段内容较多,已按上限保留:{warningLabels.join("、")}
+ The following fields have a lot of content and have been reserved according to the upper limit:{warningLabels.join("、")}
- {character.personality || "还没有性格说明,可以先补充角色面对压力时会如何选择。"}
+ {character.personality || "There is no character description yet, so you can first add how the character chooses when faced with pressure."}
{extraActions}
- 与角色交谈
-
+ Talk to the character
+
- 生成形象图
-
+ Generate image diagram
+
- 编辑
-
+ edit
+
- {deleting ? "删除中..." : "删除"}
+ {deleting ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : "delete"}
-
-
-
-
-
-
+
+
+
+
+
+
-
角色形象
-
保存主形象后,可在后续视觉生成中保持角色识别度。
+
Character image
+
After saving the main image, the character recognition can be maintained in subsequent visual generation.
onSetPrimary(asset.id)}
>
- 设为主图
-
+ Set as main image
+
void handleDeleteAsset(asset)}
>
- {deletingAssetId === asset.id ? "删除中..." : "删除"}
+ {deletingAssetId === asset.id ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : "delete"}
@@ -168,7 +168,7 @@ export function CharacterCard({
>
@@ -178,15 +178,15 @@ export function CharacterCard({
disabled={previewAsset.isPrimary || settingPrimary || deletingAssetId === previewAsset.id}
onClick={() => onSetPrimary(previewAsset.id)}
>
- 设为主图
-
+ Set as main image
+
void handleDeleteAsset(previewAsset)}
>
- {deletingAssetId === previewAsset.id ? "删除中..." : "删除图片"}
+ {deletingAssetId === previewAsset.id ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : "Delete picture"}
>
) : null}
@@ -203,7 +203,7 @@ export function CharacterCard({
{previewAsset.localPath ? (
- 本地路径:{previewAsset.localPath}
+ Local path:{previewAsset.localPath}
- {props.value?.trim() || "待补充"}
+ {props.value?.trim() || "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."}
+ Fill in the role description here. After clicking "AI Optimization Prompt", this description will be organized into a special prompt for image generation.
+
updateSourcePrompt(event.target.value)}
/>
@@ -267,7 +267,7 @@ export function CharacterImageDialog({
+ Shown here is the prompt that will eventually be sent to the image model. You can edit manually directly; after AI optimization, you can continue to modify it here.
+
diff --git a/client/src/pages/chat/ChatPage.tsx b/client/src/pages/chat/ChatPage.tsx
index aa974a187..d4c686bb8 100644
--- a/client/src/pages/chat/ChatPage.tsx
+++ b/client/src/pages/chat/ChatPage.tsx
@@ -23,42 +23,42 @@ type ApprovalRequiredEvent = Extract;
type RunStatusEvent = Extract;
function toRunStatusLabel(status: string): string {
- if (status === "queued") return "排队中";
- if (status === "running") return "运行中";
- if (status === "waiting_approval") return "待审批";
- if (status === "succeeded") return "已完成";
- if (status === "failed") return "失败";
- if (status === "cancelled") return "已取消";
+ if (status === "queued") return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
+ if (status === "running") return "Running";
+ if (status === "waiting_approval") return "Pending approval";
+ if (status === "succeeded") return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
+ if (status === "failed") return "fail";
+ if (status === "cancelled") return "Canceled";
return status;
}
function toApprovalActionLabel(action: string): string {
- if (action === "approved") return "已通过";
- if (action === "rejected") return "已拒绝";
+ if (action === "approved") return "Passed";
+ if (action === "rejected") return "Rejected";
return action;
}
function toStepTypeLabel(stepType: string): string {
- if (stepType === "planning") return "规划";
- if (stepType === "tool_call") return "工具调用";
- if (stepType === "tool_result") return "工具结果";
- if (stepType === "approval") return "审批";
- if (stepType === "completion") return "收尾";
- if (stepType === "analysis") return "分析";
- if (stepType === "review") return "审校";
- if (stepType === "repair") return "修复";
- if (stepType === "writing") return "写作";
- if (stepType === "context") return "上下文";
+ if (stepType === "planning") return "planning";
+ if (stepType === "tool_call") return "Tool call";
+ if (stepType === "tool_result") return "Tool results";
+ if (stepType === "approval") return "Approval";
+ if (stepType === "completion") return "ending";
+ if (stepType === "analysis") return "analyze";
+ if (stepType === "review") return "Review";
+ if (stepType === "repair") return "repair";
+ if (stepType === "writing") return "writing";
+ if (stepType === "context") return "context";
return stepType;
}
function toAgentNameLabel(name: string): string {
const normalized = name.toLowerCase();
- if (normalized === "planner") return "规划器";
- if (normalized === "writer") return "写作器";
- if (normalized === "reviewer") return "审校器";
- if (normalized === "continuity") return "连续性检查";
- if (normalized === "repair") return "修复器";
+ if (normalized === "planner") return "Planner";
+ if (normalized === "writer") return "writer";
+ if (normalized === "reviewer") return "Reviewer";
+ if (normalized === "continuity") return "continuity check";
+ if (normalized === "repair") return "restorer";
return name;
}
@@ -67,7 +67,7 @@ function formatEvent(event: RuntimeEvent): string {
return `调用工具 ${event.toolName}: ${event.inputSummary}`;
}
if (event.type === "tool_result") {
- return `${event.toolName} ${event.success ? "成功" : "失败"}: ${event.outputSummary}`;
+ return `${event.toolName} ${event.success ? "success" : "fail"}: ${event.outputSummary}`;
}
if (event.type === "approval_required") {
return `等待审批: ${event.summary}`;
@@ -77,7 +77,7 @@ function formatEvent(event: RuntimeEvent): string {
function safePreview(json: string | null | undefined): string {
if (!json?.trim()) {
- return "无";
+ return "none";
}
try {
const parsed = JSON.parse(json) as unknown;
@@ -123,7 +123,7 @@ export default function ChatPage() {
if (!chatStore.hydrated || chatStore.currentSessionId || chatStore.sessions.length > 0) {
return;
}
- void chatStore.createSession("新对话");
+ void chatStore.createSession("Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.");
}, [chatStore, chatStore.currentSessionId, chatStore.hydrated, chatStore.sessions.length]);
useEffect(() => {
@@ -256,7 +256,7 @@ export default function ChatPage() {
if (chatStore.currentSessionId) {
return chatStore.currentSessionId;
}
- return chatStore.createSession("新对话");
+ return chatStore.createSession("Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.");
}, [chatStore]);
const buildPayloadMessages = (
@@ -265,7 +265,7 @@ export default function ChatPage() {
if (sessionMessages.length > 0) {
return sessionMessages;
}
- return [{ role: "user" as const, content: "继续当前任务。" }];
+ return [{ role: "user" as const, content: "Continue the current task." }];
};
const onRuntimeEvent = useCallback((event: RuntimeEvent) => {
@@ -307,7 +307,7 @@ export default function ChatPage() {
}
: null);
if (!runId || !pending) {
- setLocalError("当前没有可处理的审批项。");
+ setLocalError("There are currently no approval items to process.");
return;
}
setLocalError("");
@@ -316,7 +316,7 @@ export default function ChatPage() {
type: "run_status",
runId,
status: "running",
- message: action === "approve" ? "审批已提交,继续执行中" : "审批已提交,处理中",
+ message: action === "approve" ? "Approval has been submitted and execution is continuing." : "Approval has been submitted and is being processed",
});
const sessionMessages = buildPayloadMessages(
(currentSession?.messages ?? [])
@@ -349,7 +349,7 @@ export default function ChatPage() {
const triggerReplay = async (mode: "continue" | "dry_run") => {
if (!currentRunId || !effectiveReplayStepId) {
- setLocalError("当前运行没有可重放的步骤。");
+ setLocalError("There are no replayable steps for the current run.");
return;
}
setLocalError("");
@@ -360,7 +360,7 @@ export default function ChatPage() {
});
const newRunId = response.data?.run.id;
if (!newRunId) {
- setLocalError(response.error ?? "重放失败。");
+ setLocalError(response.error ?? "Replay failed.");
return;
}
if (chatStore.currentSessionId) {
@@ -374,10 +374,10 @@ export default function ChatPage() {
} catch (error) {
const message = error instanceof Error
? error.message
- : "重放失败。";
+ : "Replay failed.";
setLocalError(
message === "No replayable tool steps after source step."
- ? "所选步骤之后没有可重放的工具步骤,请选择更早的步骤。"
+ ? "There are no replayable tool steps after the selected step, please select an earlier step."
: message,
);
return;
@@ -440,15 +440,15 @@ export default function ChatPage() {
: (persistedRunState ?? scopedLatestRun);
const headerRunLabel = headerRunState ? toRunStatusLabel(headerRunState.status) : "";
const headerRunMessage = headerRunState?.status === "waiting_approval"
- ? "当前运行等待审批"
+ ? "Currently running and awaiting approval"
: headerRunState?.status === "running"
- ? (headerRunState.message?.trim() || "当前运行中")
+ ? (headerRunState.message?.trim() || "Currently running")
: headerRunState?.status === "succeeded"
- ? "当前运行已完成"
+ ? "The current run has completed"
: headerRunState?.status === "failed"
- ? (headerRunState.message?.trim() || "当前运行失败")
+ ? (headerRunState.message?.trim() || "Current run failed")
: headerRunState?.status === "cancelled"
- ? "当前运行已取消"
+ ? "The current run has been canceled"
: "";
const liveEvents = [...runtimeEvents, ...approvalSse.events];
@@ -468,12 +468,12 @@ export default function ChatPage() {
- 会话列表
+ Conversation list
- void chatStore.createSession("新对话")}>
- 新建对话
-
+ void chatStore.createSession("Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.")}>
+ New conversation
+
+ Temporary tip: Comic image generation only supports gpt-image-2. Please select the picture service that has configured the model before generating the character design draft or grid diagram.
+
);
}
diff --git a/client/src/pages/comic/ComicProjectPage.tsx b/client/src/pages/comic/ComicProjectPage.tsx
index 15b07c39f..ba8272c2a 100644
--- a/client/src/pages/comic/ComicProjectPage.tsx
+++ b/client/src/pages/comic/ComicProjectPage.tsx
@@ -52,7 +52,7 @@ function ExportPanel({ projectId, episodes }: { projectId: string; episodes: Com
if (artifact?.url) {
window.open(artifact.url, "_blank");
}
- toast.success("导出完成");
+ toast.success("Export completed");
},
onError: (e) => toast.error(String(e)),
});
@@ -61,7 +61,7 @@ function ExportPanel({ projectId, episodes }: { projectId: string; episodes: Com
- 选择话数
+ Select the number of words
{episodes.map((ep) => (
+ Section {ep.order} talk {ep.title ? `《${ep.title}》` : ""}({ep._count?.panels ?? 0} grid)
+
))}
+ When the main appearance contains words such as "sharp as a knife" or "triangular eyes" that conflict with your desired face shape, fill in the description of the face shape here - the picture prompt will suppress the conflicting words with the highest priority, without deleting the original description.
+
setOverride(e.target.value)}
- placeholder="留空 = 不启用。例如:脸型圆润饱满,下巴柔和不尖锐"
+ placeholder="Leave blank = not enabled. For example: the face is round and full, the chin is soft and not sharp"
/>
-
骨相速记(点击设为覆盖;已有覆盖时追加):
+
Bone phase shorthand (click to set as overlay; add if already covered):
+ Regular fine-tuning only changes the style, clothing details or posture; the character's face shape, hairstyle and iconic features will be locked together with the appearance anchor point.
+
{assets.length > 0
? `${assets.length} 个资产 · 已按类型分组`
- : "服装、武器、道具一旦录入,生格子图会自动注入到参考图,提升一致性"}
+ : "Once clothing, weapons, and props are entered, the grid image will be automatically injected into the reference image to improve consistency."}
+ Click on any colored label above to quickly add it.
+ When generating the grid image, the corresponding assets will be automatically synthesized into the reference image to lock the appearance of clothing/weapons/props.
+
+ After the grid script is generated, the system automatically extracts it to ensure the consistency of the cross-session plot and character status. Inaccurate entries can be deleted manually.
+
- {isLoading &&
加载中...
}
+ {isLoading &&
loading...
}
{!isLoading && facts.length === 0 && (
- 尚无事实条目。生成至少一话的分格脚本后会自动提取。
-
+ There are no fact entries yet. After generating a grid script of at least one episode, it will be automatically extracted.
+
- 标题
+ Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
setDraftTitle(e.target.value)}
className="w-full rounded border bg-background px-2 py-1 text-xs"
- placeholder="本话标题"
+ placeholder="Title of this episode"
/>
- 大纲梗概
+ Outline summary
setDraftOutline(e.target.value)}
className="w-full resize-y rounded border bg-background px-2 py-1 text-xs leading-relaxed"
- placeholder="本话情节概述"
+ placeholder="Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."
/>
- 结尾悬念
+ Suspense at the end
setDraftCliffhanger(e.target.value)}
className="w-full rounded border bg-background px-2 py-1 text-xs"
- placeholder="本话结尾的悬念或钩子"
+ placeholder="The suspense or hook at the end of the chapter"
/>
@@ -173,8 +171,8 @@ function EpisodeCard({
checked={draftPaywalled}
onChange={(e) => setDraftPaywalled(e.target.checked)}
/>
- 付费卡点集
-
+ Pay card point collection
+
- 建议先完善角色设计稿
+ It is recommended to complete the character design draft first
- {withoutSheet.map((c) => c.name).join("、")} 尚未生成三视图。
- 生成分格脚本时会注入角色视觉锚点,有设计稿才能保证各格角色外貌一致。
-
+ {withoutSheet.map((c) => c.name).join("、")} Three views have not been generated yet.
+ When generating grid scripts, visual anchors of the characters will be injected. Only with design drafts can the appearance of the characters in each grid be consistent.
+
- 约 {targetPanelCount} 格
+ about {targetPanelCount} grid
@@ -409,23 +393,23 @@ export function EpisodeListPanel({
value={scriptPromptInstruction}
maxLength={1000}
onChange={(event) => setScriptPromptInstruction(event.target.value)}
- placeholder="可补充本次分格重点,例如:多给主角冷静反应特写,避免每格都塞满背景,结尾强化悬念。"
+ placeholder="You can supplement the key points of this division, for example: give more close-ups of the protagonist's calm reactions, avoid filling each frame with background, and strengthen the suspense at the end."
className="min-h-20 w-full resize-y rounded-md border bg-background px-3 py-2 text-xs leading-relaxed"
/>
- 这些要求只影响本次分格生成,不会覆盖角色锚点、画风和结构化输出规则。
+ These requirements only affect this grid generation and will not cover character anchor points, painting styles, and structured output rules.{scriptPromptInstruction.length}/1000
)}
- {isLoading &&
加载中...
}
+ {isLoading &&
loading...
}
{!isLoading && episodes.length === 0 && (
- 尚无分话大纲,点击「生成大纲」开始。
-
+ There is no talk outline yet, click "Generate Outline" to start.
+
- 本次生图使用的参考素材
-
+ Reference materials used in this drawing
+
{imageData.referenceImages.length}
@@ -594,16 +593,16 @@ function PanelDetailDialog({
})}
- 这些素材会被合成为雪碧图后传给图像模型,用于锁定角色外形、服装、道具与场景。
-
+ These materials will be synthesized into sprites and then passed to the image model to lock the character's appearance, clothing, props and scenes.
+
deleteMut.mutate()}
@@ -201,10 +193,10 @@ function SceneDetail({
{/* 场景圣经编辑 */}
-
场景圣经
+
Scenario Bible
- 这些视觉约束会在生成该场景下每一格时注入提示词,锁定空间一致性。
-
+ These visual constraints inject cue words into the generation of each grid in the scene, locking in spatial consistency.
+
{BIBLE_FIELDS.map((field) => (
{field.label}
@@ -223,17 +215,17 @@ function SceneDetail({
onClick={() => saveMut.mutate()}
>
{saveMut.isPending ? : null}
- 保存场景圣经
-
+ save scene bible
+
+ The setting image will be passed to the image model as a low-weight reference image, and only the tone/layout/material will be locked, and the lens will still move freely according to each frame. It is recommended to save the scene Bible before generating it.
+
+ Scenes are automatically recognized when generating grid scripts. Here you can edit the scene Bible and generate setting diagrams, which can be used to lock the spatial consistency across grids/talks.
+
setShowAdd((v) => !v)}>
- 添加场景
-
+ Add scene
+
onQuickAction?.("总结当前小说进入整本生产前仍需确认的条件,并按优先级给出补齐顺序。")}
+ onClick={() => onQuickAction?.("Summarize the conditions that still need to be confirmed before the current novel enters full production, and give the order of completion according to priority.")}
>
- 生成确认清单
-
+ Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
+
onQuickAction?.("根据当前小说信息,为生产前缺失的关键条件各给出 3 个备选答案,方便我逐项选择。")}
+ onClick={() => onQuickAction?.("Based on the current novel information, 3 alternative answers are given for each of the key conditions that are missing before production, so that I can choose one by one.")}
>
- 批量给我备选
-
+ Give me options in batches
+
) : null}
-
下一项信息
+
Next information
{setup.nextQuestion}
@@ -168,17 +168,17 @@ export default function CreativeHubNovelSetupCard({
disabled={actionDisabled}
onClick={() => onQuickAction?.(setup.recommendedAction)}
>
- 按引导继续
-
+ Follow the guidance to continue
+
onQuickAction?.("总结当前这本书的初始化完成度,并告诉我还缺哪些关键信息。")}
+ onClick={() => onQuickAction?.("Summarize the initial completion of this book and tell me what key information is missing.")}
>
- 查看初始化摘要
-
+ View initialization summary
+
);
diff --git a/client/src/pages/creativeHub/components/CreativeHubSidebar.tsx b/client/src/pages/creativeHub/components/CreativeHubSidebar.tsx
index 59493dcf9..37514d5ca 100644
--- a/client/src/pages/creativeHub/components/CreativeHubSidebar.tsx
+++ b/client/src/pages/creativeHub/components/CreativeHubSidebar.tsx
@@ -47,30 +47,30 @@ interface CreativeHubSidebarProps {
}
function bindingStatusLabel(value: string | null | undefined): string {
- return value?.trim() ? "已绑定" : "未绑定";
+ return value?.trim() ? "Bound" : "Not bound";
}
function pipelineStatusLabel(status: string | null | undefined): string {
- if (status === "queued") return "等待执行";
- if (status === "running") return "执行中";
- if (status === "succeeded") return "已完成";
- if (status === "failed") return "执行失败";
- if (status === "cancelled") return "已取消";
- return "未启动";
+ if (status === "queued") return "Waiting for execution";
+ if (status === "running") return "Executing";
+ if (status === "succeeded") return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
+ if (status === "failed") return "Execution failed";
+ if (status === "cancelled") return "Canceled";
+ return "Not started";
}
function turnStatusLabel(status: CreativeHubTurnSummary["status"]): string {
switch (status) {
case "succeeded":
- return "已完成";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
case "interrupted":
- return "待确认";
+ return "To be confirmed";
case "failed":
- return "失败";
+ return "fail";
case "cancelled":
- return "已取消";
+ return "Canceled";
case "running":
- return "进行中";
+ return "in progress";
default:
return status;
}
@@ -79,15 +79,15 @@ function turnStatusLabel(status: CreativeHubTurnSummary["status"]): string {
function threadStatusLabel(status: CreativeHubThread["status"] | undefined): string {
switch (status) {
case "busy":
- return "执行中";
+ return "Executing";
case "interrupted":
- return "待处理";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
case "error":
- return "异常";
+ return "abnormal";
case "idle":
- return "空闲";
+ return "idle";
default:
- return "未初始化";
+ return "not initialized";
}
}
@@ -112,68 +112,68 @@ function buildBlockerCardData(input: {
}) {
if (input.interrupt) {
return {
- title: "当前阻塞",
+ title: "currently blocked",
summary: input.interrupt.summary,
details: [
`等待确认: ${input.interrupt.title}`,
input.interrupt.targetType ? `目标类型: ${input.interrupt.targetType}` : "",
].filter(Boolean),
tone: "border-warning/30 bg-warning/5 text-foreground",
- actionLabel: "查看待确认项",
- actionPrompt: "总结当前待确认的创作决策,并说明推荐处理方式",
+ actionLabel: "View pending items",
+ actionPrompt: "Summarize the current creative decisions to be confirmed and explain the recommended processing methods",
};
}
if (input.diagnostics?.failureSummary) {
return {
- title: "当前风险",
+ title: "Current risks",
summary: input.diagnostics.failureSummary,
details: [
input.diagnostics.failureCode ? `错误码: ${input.diagnostics.failureCode}` : "",
input.diagnostics.recoveryHint ? `恢复建议: ${input.diagnostics.recoveryHint}` : "",
].filter(Boolean),
tone: "border-destructive/30 bg-destructive/5 text-foreground",
- actionLabel: "生成恢复方案",
- actionPrompt: input.diagnostics.recoveryHint || "分析当前失败原因并给出恢复步骤",
+ actionLabel: "Generate recovery plan",
+ actionPrompt: input.diagnostics.recoveryHint || "Analyze the current failure reasons and provide recovery steps",
};
}
if (input.productionStatus?.failureSummary) {
return {
- title: "当前阻塞",
+ title: "currently blocked",
summary: input.productionStatus.failureSummary,
details: [
input.productionStatus.recoveryHint ? `恢复建议: ${input.productionStatus.recoveryHint}` : "",
`当前阶段: ${input.productionStatus.currentStage}`,
].filter(Boolean),
tone: "border-destructive/30 bg-destructive/5 text-foreground",
- actionLabel: "处理当前阻塞",
+ actionLabel: "Handle current blocking",
actionPrompt: input.productionStatus.recoveryHint || "分析当前生产阻塞并继续推进",
};
}
if (input.latestTurnSummary?.status === "interrupted") {
return {
- title: "当前关注点",
+ title: "Current concerns",
summary: input.latestTurnSummary.nextSuggestion,
details: [
`阶段: ${input.latestTurnSummary.currentStage}`,
`状态: ${turnStatusLabel(input.latestTurnSummary.status)}`,
],
tone: "border-info/30 bg-info/5 text-foreground",
- actionLabel: "按建议继续",
+ actionLabel: "Continue as suggested",
actionPrompt: input.latestTurnSummary.nextSuggestion,
};
}
return {
- title: "当前状态",
- summary: "当前没有需要立即处理的阻塞项,可以继续推进创作。",
+ title: "Current status",
+ summary: "There are currently no blocking items that need to be processed immediately, and you can continue to advance your creation.",
details: input.latestTurnSummary?.nextSuggestion
? [`建议下一步: ${input.latestTurnSummary.nextSuggestion}`]
: [],
tone: "border-border bg-muted/20 text-foreground",
- actionLabel: input.latestTurnSummary?.nextSuggestion ? "按建议继续" : undefined,
+ actionLabel: input.latestTurnSummary?.nextSuggestion ? "Continue as suggested" : undefined,
actionPrompt: input.latestTurnSummary?.nextSuggestion,
};
}
@@ -243,14 +243,14 @@ export default function CreativeHubSidebar({
aria-busy={isBindingNovel || isCreatingNovel || novelsRetrying}
>
- 当前小说与状态
+ Current novel and status
{onQuickAction ? (
@@ -110,8 +110,8 @@ function renderNovelList(output: Record, onQuickAction?: (promp
variant="outline"
onClick={() => onQuickAction(`把《${title}》设为当前工作区`)}
>
- 设为当前工作区
-
+ Set as current workspace
+
) : null}
@@ -127,25 +127,25 @@ function renderWorkspaceCard(
variant: "created" | "selected",
onQuickAction?: (prompt: string) => void,
) {
- const title = typeof output.title === "string" && output.title.trim() ? output.title.trim() : "未命名小说";
+ const title = typeof output.title === "string" && output.title.trim() ? output.title.trim() : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
const chapterCount = typeof output.chapterCount === "number" ? output.chapterCount : 0;
const actions = variant === "created"
? [
- { label: "查看当前进度", prompt: "这本书当前写到哪一章" },
- { label: "开始设计第一章", prompt: "为这本书规划第一章" },
+ { label: "View current progress", prompt: "Which chapter is this book currently on?" },
+ { label: "Start designing the first chapter", prompt: "Planning the first chapter of the book" },
]
: [
- { label: "查看当前进度", prompt: "这本书当前写到哪一章" },
- { label: "查看前两章", prompt: "前两章都写了什么" },
+ { label: "View current progress", prompt: "Which chapter is this book currently on?" },
+ { label: "View the first two chapters", prompt: "What was written in the first two chapters?" },
];
return (
《{title}》
- {variant === "created" ? "新小说已创建并绑定到当前线程。" : "当前线程已切换到该小说工作区。"}
+ {variant === "created" ? "A new novel is created and bound to the current thread." : "The current thread has been switched to the novel workspace."}
{renderActionButtons([
- { label: "查看世界观约束", prompt: "查看当前小说的世界观规则" },
- { label: "检查世界观冲突", prompt: "检查当前小说和世界观是否存在冲突" },
+ { label: "View worldview constraints", prompt: "View the worldview rules of the current novel" },
+ { label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.", prompt: "Check if there is any conflict between the current novel and the worldview" },
], onQuickAction)}
) : null}
{renderActionButtons([
- { label: "继续整本生成", prompt: "继续生成当前小说" },
- { label: "查看整本进度", prompt: "整本生成到哪一步了" },
+ { label: "Continue to generate the entire book", prompt: "Continue to generate the current novel" },
+ { label: "Check the progress of the entire book", prompt: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
], onQuickAction)}
);
@@ -245,19 +245,19 @@ function renderPipelineRunCard(
? startOrder === endOrder
? `第 ${startOrder} 章`
: `第 ${startOrder} 到第 ${endOrder} 章`
- : "当前章节范围";
- const title = toolName === "preview_pipeline_run" ? "整本写作预览" : "整本写作任务";
+ : "Current chapter scope";
+ const title = toolName === "preview_pipeline_run" ? "Full writing preview" : "Whole writing assignment";
const description = toolName === "preview_pipeline_run"
? `${scope} 的整本写作预览已完成,当前可进入审批或继续诊断。`
: `${scope} 的整本写作任务已启动${jobId ? `(任务 ${jobId})` : ""}。`;
const actions = toolName === "preview_pipeline_run"
? [
- { label: "查看整本进度", prompt: "整本生成到哪一步了" },
- { label: "查看阻塞", prompt: "为什么整本生成没有启动" },
+ { label: "Check the progress of the entire book", prompt: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
+ { label: "View blocking", prompt: "Why does the entire build not start?" },
]
: [
- { label: "查看整本进度", prompt: "整本生成到哪一步了" },
- { label: "查看任务状态", prompt: "列出当前系统任务状态" },
+ { label: "Check the progress of the entire book", prompt: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
+ { label: "View task status", prompt: "List current system task status" },
];
return renderProductionAssetCard(title, description, actions, onQuickAction);
}
@@ -269,11 +269,11 @@ function renderDiagnosticCard(output: Record, onQuickAction?: (
return (
{failureSummary ?
{failureSummary}
: null}
- {failureDetails ?
详情:{failureDetails}
: null}
- {recoveryHint ?
建议:{recoveryHint}
: null}
+ {failureDetails ?
Details:{failureDetails}
: null}
+ {recoveryHint ?
suggestion:{recoveryHint}
: null}
{renderActionButtons([
- { label: "继续诊断", prompt: "继续解释失败原因和恢复建议" },
- { label: "查看任务状态", prompt: "列出当前系统任务状态" },
+ { label: "continue diagnosis", prompt: "Continue to explain the cause of failure and recovery recommendations" },
+ { label: "View task status", prompt: "List current system task status" },
], onQuickAction)}
- {content || "当前没有可显示的章节内容。"}
+ {content || "There is currently no chapter content to display."}
{renderActionButtons([
- { label: "继续总结", prompt: "总结这一段内容的关键剧情" },
- { label: "检查冲突", prompt: "检查这一章是否和世界观或前文冲突" },
+ { label: "Continue to summarize", prompt: "Summarize the key plot of this paragraph" },
+ { label: "Check for conflicts", prompt: "Check whether this chapter conflicts with the world view or the previous text" },
], onQuickAction)}
);
@@ -341,7 +341,7 @@ export default function CreativeHubToolResultCard({
const [expanded, setExpanded] = useState(false);
const detailsId = useId();
const payload = asRecord(output);
- const summaryText = compactText(summary, 160) || "工具已返回结果。";
+ const summaryText = compactText(summary, 160) || "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
const cardContent = (() => {
if (toolName === "list_novels") {
return renderNovelList(payload, onQuickAction);
@@ -356,13 +356,13 @@ export default function CreativeHubToolResultCard({
return renderWorldBindingCard(payload, onQuickAction);
}
if (toolName === "generate_world_for_novel") {
- const worldName = typeof payload.worldName === "string" && payload.worldName.trim() ? payload.worldName.trim() : "未命名世界观";
+ const worldName = typeof payload.worldName === "string" && payload.worldName.trim() ? payload.worldName.trim() : "Unnamed world view";
return renderProductionAssetCard(
- "世界观已生成",
+ "World view has been generated",
`已生成世界观《${worldName}》。`,
[
- { label: "继续整本生成", prompt: "继续生成当前小说" },
- { label: "查看生产进度", prompt: "整本生成到哪一步了" },
+ { label: "Continue to generate the entire book", prompt: "Continue to generate the current novel" },
+ { label: "Check production progress", prompt: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
],
onQuickAction,
);
@@ -370,37 +370,37 @@ export default function CreativeHubToolResultCard({
if (toolName === "generate_novel_characters") {
const characterCount = typeof payload.characterCount === "number" ? payload.characterCount : 0;
return renderProductionAssetCard(
- "核心角色已生成",
+ "Core roles have been generated",
`已生成 ${characterCount} 个核心角色。`,
[
- { label: "继续整本生成", prompt: "继续生成当前小说" },
- { label: "查看角色状态", prompt: "查看当前小说角色状态" },
+ { label: "Continue to generate the entire book", prompt: "Continue to generate the current novel" },
+ { label: "View character status", prompt: "View current novel character status" },
],
onQuickAction,
);
}
if (toolName === "generate_story_bible") {
return renderProductionAssetCard(
- "小说圣经已生成",
+ "Novel Bible has been generated",
typeof payload.mainPromise === "string" && payload.mainPromise.trim()
? payload.mainPromise.trim()
: "当前小说圣经已生成。",
[
- { label: "继续整本生成", prompt: "继续生成当前小说" },
- { label: "查看整本进度", prompt: "整本生成到哪一步了" },
+ { label: "Continue to generate the entire book", prompt: "Continue to generate the current novel" },
+ { label: "Check the progress of the entire book", prompt: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
],
onQuickAction,
);
}
if (toolName === "generate_novel_outline") {
return renderProductionAssetCard(
- "发展走向已生成",
+ "The development trend has been generated",
typeof payload.outline === "string" && payload.outline.trim()
? payload.outline.trim()
- : "当前小说发展走向已生成。",
+ : "The current development trend of the novel has been generated.",
[
- { label: "继续整本生成", prompt: "继续生成当前小说" },
- { label: "查看整本进度", prompt: "整本生成到哪一步了" },
+ { label: "Continue to generate the entire book", prompt: "Continue to generate the current novel" },
+ { label: "Check the progress of the entire book", prompt: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
],
onQuickAction,
);
@@ -408,11 +408,11 @@ export default function CreativeHubToolResultCard({
if (toolName === "generate_structured_outline") {
const targetChapterCount = typeof payload.targetChapterCount === "number" ? payload.targetChapterCount : 0;
return renderProductionAssetCard(
- "结构化大纲已生成",
- targetChapterCount > 0 ? `已生成 ${targetChapterCount} 章结构化大纲。` : "当前小说结构化大纲已生成。",
+ "Structured outline generated",
+ targetChapterCount > 0 ? `已生成 ${targetChapterCount} 章结构化大纲。` : "The structured outline of the current novel has been generated.",
[
- { label: "同步章节目录", prompt: "继续生成当前小说" },
- { label: "查看整本进度", prompt: "整本生成到哪一步了" },
+ { label: "Synchronize chapter table of contents", prompt: "Continue to generate the current novel" },
+ { label: "Check the progress of the entire book", prompt: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
],
onQuickAction,
);
@@ -420,11 +420,11 @@ export default function CreativeHubToolResultCard({
if (toolName === "sync_chapters_from_structured_outline") {
const chapterCount = typeof payload.chapterCount === "number" ? payload.chapterCount : 0;
return renderProductionAssetCard(
- "章节目录已同步",
- chapterCount > 0 ? `已同步 ${chapterCount} 个章节目录。` : "已同步章节目录。",
+ "Chapter table of contents has been synchronized",
+ chapterCount > 0 ? `已同步 ${chapterCount} 个章节目录。` : "Chapter table of contents has been synchronized.",
[
- { label: "查看整本进度", prompt: "整本生成到哪一步了" },
- { label: "启动整本生成", prompt: "继续生成当前小说" },
+ { label: "Check the progress of the entire book", prompt: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
+ { label: "Start full generation", prompt: "Continue to generate the current novel" },
],
onQuickAction,
);
@@ -454,7 +454,7 @@ export default function CreativeHubToolResultCard({
|| toolName === "list_writing_formulas"
|| toolName === "list_base_characters"
) {
- return renderListCard(payload, "当前没有可展示的结果。", onQuickAction);
+ return renderListCard(payload, "There are currently no results to display.", onQuickAction);
}
if (
toolName === "get_chapter_content"
@@ -475,7 +475,7 @@ export default function CreativeHubToolResultCard({
@@ -74,8 +74,8 @@ export default function CreativeHubTurnSummaryCard({
variant="outline"
onClick={() => onQuickAction(summary.nextSuggestion)}
>
- 沿这个方向继续
-
+ continue in this direction
+
) : null}
diff --git a/client/src/pages/creativeHub/components/NovelProductionStarterCard.tsx b/client/src/pages/creativeHub/components/NovelProductionStarterCard.tsx
index 2322c53ea..a48627188 100644
--- a/client/src/pages/creativeHub/components/NovelProductionStarterCard.tsx
+++ b/client/src/pages/creativeHub/components/NovelProductionStarterCard.tsx
@@ -36,60 +36,60 @@ function ProductionField(props: {
}
function fromNarrativePov(value: "first_person" | "third_person" | "mixed" | null | undefined): string {
- if (value === "first_person") return "第一人称";
- if (value === "third_person") return "第三人称";
- if (value === "mixed") return "混合视角";
+ if (value === "first_person") return "first person";
+ if (value === "third_person") return "third person";
+ if (value === "mixed") return "mixed perspective";
return "";
}
function toNarrativePov(value: string): "first_person" | "third_person" | "mixed" | null {
- if (value === "第一人称") return "first_person";
- if (value === "第三人称") return "third_person";
- if (value === "混合视角") return "mixed";
+ if (value === "first person") return "first_person";
+ if (value === "third person") return "third_person";
+ if (value === "mixed perspective") return "mixed";
return null;
}
function fromPacePreference(value: "slow" | "balanced" | "fast" | null | undefined): string {
- if (value === "slow") return "慢节奏";
- if (value === "balanced") return "均衡节奏";
- if (value === "fast") return "快节奏";
+ if (value === "slow") return "slow pace";
+ if (value === "balanced") return "balanced rhythm";
+ if (value === "fast") return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
return "";
}
function toPacePreference(value: string): "slow" | "balanced" | "fast" | null {
- if (value === "慢节奏") return "slow";
- if (value === "均衡节奏") return "balanced";
- if (value === "快节奏") return "fast";
+ if (value === "slow pace") return "slow";
+ if (value === "balanced rhythm") return "balanced";
+ if (value === "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.") return "fast";
return null;
}
function fromProjectMode(value: "ai_led" | "co_pilot" | "draft_mode" | "auto_pipeline" | null | undefined): string {
- if (value === "ai_led") return "AI 主导";
- if (value === "co_pilot") return "人机协作";
- if (value === "draft_mode") return "草稿优先";
- if (value === "auto_pipeline") return "自动流水线";
+ if (value === "ai_led") return "AI-driven";
+ if (value === "co_pilot") return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
+ if (value === "draft_mode") return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
+ if (value === "auto_pipeline") return "Automatic assembly line";
return "";
}
function toProjectMode(value: string): "ai_led" | "co_pilot" | "draft_mode" | "auto_pipeline" | null {
- if (value === "AI 主导") return "ai_led";
- if (value === "人机协作") return "co_pilot";
- if (value === "草稿优先") return "draft_mode";
- if (value === "自动流水线") return "auto_pipeline";
+ if (value === "AI-driven") return "ai_led";
+ if (value === "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.") return "co_pilot";
+ if (value === "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.") return "draft_mode";
+ if (value === "Automatic assembly line") return "auto_pipeline";
return null;
}
function fromLevel(value: "low" | "medium" | "high" | null | undefined): string {
- if (value === "low") return "低";
- if (value === "medium") return "中";
- if (value === "high") return "高";
+ if (value === "low") return "Low";
+ if (value === "medium") return "middle";
+ if (value === "high") return "high";
return "";
}
function toLevel(value: string): "low" | "medium" | "high" | null {
- if (value === "低") return "low";
- if (value === "中") return "medium";
- if (value === "高") return "high";
+ if (value === "Low") return "low";
+ if (value === "middle") return "medium";
+ if (value === "high") return "high";
return null;
}
@@ -143,7 +143,7 @@ function buildProductionPrompt(input: {
segments.push(`情绪强度:${emotionIntensity}。`);
}
if (aiFreedom) {
- segments.push(`AI 自由度:${aiFreedom}。`);
+ segments.push(`AI freedom:${aiFreedom}。`);
}
if (defaultChapterLength) {
segments.push(`默认章长:约 ${defaultChapterLength} 字。`);
@@ -177,7 +177,7 @@ function buildProductionPrompt(input: {
segments.push(`情绪强度:${emotionIntensity}。`);
}
if (aiFreedom) {
- segments.push(`AI 自由度:${aiFreedom}。`);
+ segments.push(`AI freedom:${aiFreedom}。`);
}
if (defaultChapterLength) {
segments.push(`默认章长:约 ${defaultChapterLength} 字。`);
@@ -259,7 +259,7 @@ export default function NovelProductionStarterCard({
const detailErrorMessage = novelDetailQuery.error instanceof Error
? novelDetailQuery.error.message
: isContinueMode && novelDetailQuery.isSuccess && !novelDetailQuery.data?.data
- ? "没有读取到当前小说的生产设置。"
+ ? "The production settings for the current novel were not read."
: "";
const submitMutation = useMutation({
mutationFn: async () => {
@@ -294,7 +294,7 @@ export default function NovelProductionStarterCard({
}));
},
onError: (error) => {
- toast.error(error instanceof Error ? error.message : "整本生产启动失败。");
+ toast.error(error instanceof Error ? error.message : "The entire production startup failed.");
},
});
const formDisabled = actionDisabled
@@ -317,30 +317,30 @@ export default function NovelProductionStarterCard({
return (
-
整本生产
+
Entire production
{isContinueMode
- ? `当前将继续生产《${resolvedTitle || "当前小说"}》。`
- : "当前处于全局模式,可直接创建新书并启动整本生产。"}
+ ? `当前将继续生产《${resolvedTitle || "current novel"}》。`
+ : "Currently in global mode, you can directly create a new book and start the entire production."}
- 建议先确认:题材、风格、视角、节奏、章长、AI 自由度。条件越完整,整本生产偏差越小。
-
+ It is recommended to confirm first: subject matter, style, perspective, rhythm, chapter length, and AI freedom. The more complete the conditions, the smaller the overall production deviation.
+
Duration:{selectedEpisode.durationSec ?? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."} Second
+
Emotional net worth:{selectedEpisode.emotionNet ?? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."}
+
state:{statusLabel(selectedEpisode.status)}
-
本集信息
+
Information about this episode
- 标题
+ Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
setDraft((current) => ({ ...current, title: event.target.value }))} />
- 预计时长(秒)
+ Estimated duration (seconds)
setDraft((current) => ({ ...current, durationSec: event.target.value }))} />
- 开场钩子
+ opening hook
- 打开工作台
-
+ Open workbench
+ props.onAssemble(props.project)}
>
- 整理素材
-
+ Organize materials
+
props.onStrategy(props.project)}
>
- 生成策略
-
+ Generate strategy
+
props.onOutline(props.project)}
>
- 生成前 12 集
-
+ Generating first 12 episodes
+
);
@@ -197,7 +197,7 @@ export default function DramaWorkspacePage() {
mutationFn: (payload: CreateDramaProjectPayload) => createDramaProject(payload),
onSuccess: async (response) => {
await queryClient.invalidateQueries({ queryKey: queryKeys.drama.projects });
- toast.success("短剧项目已创建。");
+ toast.success("Skit project created.");
if (response.data?.id) {
navigate(`/drama/projects/${response.data.id}`);
return;
@@ -215,7 +215,7 @@ export default function DramaWorkspacePage() {
const trackRecommendationMutation = useMutation({
mutationFn: () => recommendDramaTrack({
- title: form.title.trim() || selectedNovel?.title || "短剧项目",
+ title: form.title.trim() || selectedNovel?.title || "Short play project",
sourceType: form.source,
sourceDigest: buildRecommendationDigest(form, selectedNovel),
theme: form.theme.trim() || undefined,
@@ -226,7 +226,7 @@ export default function DramaWorkspacePage() {
if (recommendation) {
setTrackRecommendation(recommendation);
setForm((current) => ({ ...current, track: recommendation.recommendedTrack }));
- toast.success("已推荐适合的短剧赛道。");
+ toast.success("Suitable skit tracks have been recommended.");
}
},
});
@@ -253,15 +253,15 @@ export default function DramaWorkspacePage() {
}
if (stepIndex === 1) {
if (form.source === "novel_import" && !form.sourceRef.trim()) {
- toast.error("请选择要改编的小说。");
+ toast.error("Please select a novel to adapt.");
return false;
}
if (form.source === "original" && !form.inspiration.trim()) {
- toast.error("请填写原创灵感。");
+ toast.error("Please fill in original inspiration.");
return false;
}
if (form.source === "text_import" && !form.rawText.trim()) {
- toast.error("请粘贴要整理的文本。");
+ toast.error("Please paste the text you want to organize.");
return false;
}
}
@@ -280,19 +280,19 @@ export default function DramaWorkspacePage() {
return;
}
if (!form.title.trim()) {
- toast.error("请先填写短剧项目名。");
+ toast.error("Please fill in the short play project name first.");
return;
}
if (form.source === "novel_import" && !form.sourceRef.trim()) {
- toast.error("请选择要改编的小说。");
+ toast.error("Please select a novel to adapt.");
return;
}
if (form.source === "original" && !form.inspiration.trim()) {
- toast.error("请填写原创灵感。");
+ toast.error("Please fill in original inspiration.");
return;
}
if (form.source === "text_import" && !form.rawText.trim()) {
- toast.error("请粘贴要整理的文本。");
+ toast.error("Please paste the text you want to organize.");
return;
}
createMutation.mutate(buildCreatePayload(form));
@@ -303,7 +303,7 @@ export default function DramaWorkspacePage() {
...current,
source,
sourceRef: "",
- title: source === "original" && !current.title ? "原创短剧项目" : current.title,
+ title: source === "original" && !current.title ? "Original short drama project" : current.title,
}));
setTrackRecommendation(null);
setStepIndex(1);
@@ -312,17 +312,17 @@ export default function DramaWorkspacePage() {
return (
-
短剧工作台
+
Short drama workbench
- 从小说、原创灵感或导入文本整理短剧素材,再生成竖屏付费短剧策略和分集台本。
-
+ Organize short play materials from novels, original inspirations or imported texts, and generate vertical screen paid short play strategies and episode scripts.
+
- 新建短剧项目
- 按步骤选择来源、补充内容,再创建可进入短剧产线的项目。
+ New short drama project
+ Follow the steps to select sources, supplement content, and create projects that can enter the short drama production line.
Adapted from an existing novel, it is suitable for converting an existing novel into a short vertical screen play.
chooseSource("original")}>
-
原创短剧
-
从一句灵感开始,系统整理人物、冲突和节拍。
+
Original short play
+
Start with an inspiration and systematically organize characters, conflicts, and beats.
chooseSource("text_import")}>
-
粘贴文本
-
把外部故事梗概、短篇或素材文本整理成短剧项目。
+
Paste text
+
Organize external story synopses, short stories, or material texts into short play projects.
) : null}
@@ -360,7 +360,7 @@ export default function DramaWorkspacePage() {
{form.source === "novel_import" ? (
<>
- 选择小说
+ Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
{novels.map((novel) => (
+ {novel.title || "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."}({novel._count.chapters} chapter)
+
))}
{selectedNovel ? (
+ Selected {selectedNovel.title || "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."},common {selectedNovel._count.chapters} chapter. After creation, it will be organized into short play material packages.
+
@@ -240,15 +240,15 @@ function CharacterImagesBlock(props: {
) : (
)}
- {sheet.status === "done" ? "重新生成设计稿" : "生成角色设计稿"}
+ {sheet.status === "done" ? "Regenerate design draft" : "Generate character design draft"}
{sheet.status === "done" && (
<>
v{sheet.version ?? 1}
- 视频生成将自动引用此图作为角色参考
+ Video generation will automatically reference this image as a character reference
>
)}
- {sheet.history?.length ? {sheet.history.length} 个历史版本 : null}
+ {sheet.history?.length ? {sheet.history.length} historical version : null}
@@ -270,7 +270,7 @@ function CharacterImageHistory({ history }: { history: NonNullable right.version - left.version);
return (
-
设计稿历史版本
+
Historical versions of design drafts
{items.map((item) => {
const label = `v${item.version}${item.provider ? ` · ${item.provider}` : ""}`;
@@ -330,17 +330,17 @@ function CharacterAssetEditor(props: {
{props.character.name}
- {draft.screenRole || "先明确这个角色在短剧里的出镜功能。"}
+ {draft.screenRole || "First, clarify the role of this character in the short play."}
@@ -418,11 +418,11 @@ function CharacterAssetEditor(props: {
props.onSave(props.character, draft)}>
- 保存角色资产
-
+ Save character assets
+
props.onSaveToLibrary(props.character)}>
- 保存到短剧角色库
-
+ Save to short play character library
+
- 短剧角色资产
- 角色资产会进入台本、分镜和视频提示词,优先保证观众识别、造型一致和台词可拍。
+ Short drama character assets
+ Character assets will be included in the script, storyboards, and video prompts, with priority given to ensuring audience recognition, consistent styling, and filmable lines. setSelectedLibraryId(event.target.value)}
>
-
+
{props.library.map((item) => (
{characters.length === 0 ? (
-
整理素材后会自动导入主要角色,再补齐造型、表演和台词锚点。
+
After organizing the materials, the main characters will be automatically imported, and then the styling, performance and line anchor points will be completed.
) : (
{characters.map((character) => (
diff --git a/client/src/pages/drama/components/DramaEpisodeAudioPanel.tsx b/client/src/pages/drama/components/DramaEpisodeAudioPanel.tsx
index dedfb3ffd..5e4d2c0e0 100644
--- a/client/src/pages/drama/components/DramaEpisodeAudioPanel.tsx
+++ b/client/src/pages/drama/components/DramaEpisodeAudioPanel.tsx
@@ -46,11 +46,11 @@ function isActiveBatch(job: DramaBatchJob | undefined): boolean {
function batchStatusLabel(status: DramaBatchJob["status"]): string {
const labels: Record = {
- pending: "等待中",
- running: "执行中",
- paused: "已暂停",
- done: "已完成",
- failed: "有失败项",
+ pending: "Waiting",
+ running: "Executing",
+ paused: "Suspended",
+ done: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ failed: "There are failed items",
};
return labels[status] ?? status;
}
@@ -105,18 +105,18 @@ export function DramaEpisodeAudioPanel(props: {
return (
- {item.quality?.repairPlan?.instruction || "查看问题后决定是否修复。"}
+ {item.quality?.repairPlan?.instruction || "Review the problem and decide whether to fix it."}
+ This episode needs to be addressed, but there is no structured breakdown of issues.
+
)}
@@ -269,13 +269,13 @@ export function DramaQualityPanel(props: {
{uncheckedItems.length > 0 ? (
- 待检查台本
- 这些集已有台本,还没有质量检查结果。
+ Script to be checked
+ These episodes are scripted and have no QA results yet.
{uncheckedItems.map((item) => (
- 第 {item.episode.order} 集:{item.episode.title}
+ Section {item.episode.order} set:{item.episode.title} props.onReview(item.episode.order)}
>
- 检查
-
+ Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
+
))}
diff --git a/client/src/pages/drama/components/DramaSourcePanel.tsx b/client/src/pages/drama/components/DramaSourcePanel.tsx
index 20a78ee97..8ec3494f8 100644
--- a/client/src/pages/drama/components/DramaSourcePanel.tsx
+++ b/client/src/pages/drama/components/DramaSourcePanel.tsx
@@ -39,39 +39,32 @@ function SourceQualityChecklist(props: {
}) {
const checks = [
{
- label: "故事梗概",
+ label: "Story summary",
ready: props.synopsisReady,
- detail: props.synopsisReady ? "已整理为短剧素材" : "缺少故事梗概",
+ detail: props.synopsisReady ? "Organized into short play material" : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
},
{
- label: "来源节拍",
+ label: "source beat",
ready: props.beatCount >= 8,
- detail: props.beatCount >= 8 ? `${props.beatCount} 个节拍` : `${props.beatCount} 个节拍,可能不足以支撑长集数`,
+ detail: props.beatCount >= 8 ? `${props.beatCount} beats` : `${props.beatCount} beats may not be enough to support a long episode.`,
},
{
- label: "角色资源",
+ label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
ready: props.characterCount >= 2,
- detail: props.characterCount >= 2 ? `${props.characterCount} 个角色` : "主要角色不足",
+ detail: props.characterCount >= 2 ? `${props.characterCount} characters` : "Not enough main characters",
},
{
- label: "硬事实",
+ label: "hard facts",
ready: props.factCount > 0,
- detail: props.factCount > 0 ? `${props.factCount} 条硬事实` : "缺少可约束后续台本的事实",
- },
- ];
-
- return (
+ detail: props.factCount > 0 ? `${props.factCount} hard facts` : "Facts lacking to bind subsequent scripts", }, ]; return (
- 素材质量提示
- 这些提示决定后续策略、分集和台本是否有足够输入。
-
-
+ Material Quality HintsThese hints determine whether subsequent strategies, episode breaks, and scripts have sufficient input.
{checks.map((check) => (
{check.label}
- {check.ready ? "可用" : "需补充"}
+ {check.ready ? "Available" : "Need to be supplemented"}
{check.detail}
@@ -83,18 +76,18 @@ function SourceQualityChecklist(props: {
function readinessLabel(readiness: DramaSourceSupplementGuidance["readiness"]): string {
const labels: Record = {
- ready: "可继续",
- needs_supplement: "建议补充",
- needs_rebuild: "建议重整素材",
+ ready: "Can continue",
+ needs_supplement: "Suggestions to add",
+ needs_rebuild: "It is recommended to reorganize the material",
};
return labels[readiness];
}
function nextActionLabel(nextAction: DramaSourceSupplementGuidance["nextAction"]): string {
const labels: Record = {
- continue: "继续生成策略",
- supplement_notes: "先补充说明",
- rebuild_source_bundle: "补充后重整素材",
+ continue: "Continue to generate strategies",
+ supplement_notes: "Additional explanation first",
+ rebuild_source_bundle: "Reorganize material after supplement",
};
return labels[nextAction];
}
@@ -109,7 +102,7 @@ function SourceSupplementPanel({ project }: { project: DramaProjectDetail }) {
onSuccess: (response) => {
if (response.data) {
setGuidance(response.data);
- toast.success("素材补充建议已生成。");
+ toast.success("Material supplement suggestions have been generated.");
}
},
});
@@ -118,20 +111,15 @@ function SourceSupplementPanel({ project }: { project: DramaProjectDetail }) {
- 补充素材建议
- 让系统指出影响策略、分集和台本生成的素材缺口。
-
- mutation.mutate()}>
- {mutation.isPending ? "分析中..." : "生成补充建议"}
+ Suggestions for Supplementary MaterialsThis allows the system to identify material gaps that affect strategy, episode segmentation, and script generation.
+ The source material has not been compiled yet. First click "Organize Materials" and the system will organize the novel, inspiration or imported text into the synopsis, beats, characters and hard facts that can be used in the short drama.
+
- {failureReason ? `视频任务失败:${failureReason}` : "视频任务失败。请刷新状态或重新生成提示词后再创建任务。"}
+ {failureReason ? `视频任务失败:${failureReason}` : "Video task failed. Please refresh the status or regenerate the prompt word before creating the task."}
Please wait, the subject matter and novel relationship are being synchronized.
) : null}
{genreTreeQuery.isError ? (
void genreTreeQuery.refetch()}>
- 重新加载
-
+ Reload
+
)}
/>
) : null}
@@ -270,13 +270,13 @@ export default function GenreManagementPage() {
{!genreTreeQuery.isLoading && !genreTreeQuery.isError && genreTree.length === 0 ? (
- 创建第一棵题材树
-
+ Create the first theme tree
+
)}
/>
) : null}
diff --git a/client/src/pages/genres/components/GenreCreateDialog.tsx b/client/src/pages/genres/components/GenreCreateDialog.tsx
index 7d1094b7e..8af0da4dd 100644
--- a/client/src/pages/genres/components/GenreCreateDialog.tsx
+++ b/client/src/pages/genres/components/GenreCreateDialog.tsx
@@ -70,7 +70,7 @@ export default function GenreCreateDialog({
},
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: queryKeys.genres.all });
- toast.success("题材基底树已创建。");
+ toast.success("The theme base tree has been created.");
onOpenChange(false);
},
});
@@ -88,7 +88,7 @@ export default function GenreCreateDialog({
return;
}
setDraft(cloneGenreDraft(response.data));
- toast.success("AI 题材基底树已生成。");
+ toast.success("The AI theme base tree has been generated.");
},
});
@@ -96,26 +96,26 @@ export default function GenreCreateDialog({
- 新建题材基底树
+ Create a new theme base tree
- 先确定父级位置,再手动填写结构或让 AI 先生成一个草稿。这里维护的是作品的题材基底,也就是“这是什么书”。
-
+ Determine the parent position first, then fill in the structure manually or let AI generate a draft. What is maintained here is the subject matter foundation of the work, that is, "what kind of book is this?"
+
-
AI 生成
+
AI generation
- 适合先把大类、子类和下级题材基底一起打出来,再手动微调。
-
+ It is suitable to type out the basics of major categories, subcategories and lower-level themes together, and then manually fine-tune them.
+
- {node.description?.trim() || "尚未说明题材定位,建议补充读者期待和核心创作方向。"}
+ {node.description?.trim() || "The subject matter and positioning are not yet specified; it is recommended to add information about reader expectations and the core creative direction."}
The wizard will retain this result, and the home page will continue to focus on the current project and next creation.
- 查看全部小说
+ View all novels
) : (
-
你不需要先学会所有功能
-
沿着上面的唯一推荐动作推进即可。世界、角色、卷章规划和普通质量问题会由 AI 在主链中持续处理。
+
You don’t need to learn all the features first
+
Just follow the only recommended actions above. World, character, chapter planning, and general quality issues are continuously handled by AI in the main chain.
{props.hasNovels ? "Creative pace is good" : "No pending creative tasks"}
{props.hasNovels ? "You can continue working on your novel following the recommendations above." : "After starting your first novel, this section will display items that require your confirmation."}
) : props.items.map((item) => (
diff --git a/client/src/pages/home/components/HomeNextActionPanel.tsx b/client/src/pages/home/components/HomeNextActionPanel.tsx
index 8cb4210e4..490c3db71 100644
--- a/client/src/pages/home/components/HomeNextActionPanel.tsx
+++ b/client/src/pages/home/components/HomeNextActionPanel.tsx
@@ -35,8 +35,8 @@ export function HomeNextActionPanel(props: {
- 正在整理你的创作工作台...
-
+ Organizing your creative workbench...
+
@@ -51,12 +51,12 @@ export function HomeNextActionPanel(props: {
return (
- 暂时无法读取项目
+ The item cannot be read at the moment
-
还不能为你判断下一步
-
重新加载项目后,系统会继续为你整理最合适的创作入口。
+
I can’t decide the next step for you yet
+
After reloading the project, the system will continue to organize the most suitable creation entry for you.
+ Select the vector model and vector library address to start searching. When you need to finely control recall quality or task performance, expand the advanced configuration.
+
-
向量模型
+
Vector model
- 选择用于生成向量的服务商和模型。
-
+ Select the provider and model used to generate vectors.
+
+ Batch size, timeout, retry, and polling parameters can be adjusted when importing in large batches or when the service is slow to respond.
+
-
Embedding 批大小
+
Embedding batch size
-
Embedding 并发数
+
Embedding concurrency number
- 同时发起的 embedding API 请求数。默认 4;调高可大幅缩短大文档索引时间,但要看服务商配额。
-
+ The number of embedding API requests initiated simultaneously. The default is 4; increasing it can significantly shorten the indexing time for large documents, but it depends on the service provider's quota.
+
-
Embedding 超时(毫秒)
+
Embedding timeout (milliseconds)
-
Embedding 最大重试次数
+
Embedding maximum number of retries
-
Embedding 重试基础间隔(毫秒)
+
Embedding retry base interval (milliseconds)
-
Worker 轮询间隔(毫秒)
+
Worker polling interval (milliseconds)
-
Worker 最大尝试次数
+
Worker maximum number of attempts
-
Worker 重试基础间隔(毫秒)
+
Worker retry base interval (milliseconds)
-
RAG HTTP 超时(毫秒)
+
RAG HTTP Timeout (milliseconds)
- {isSaving ? "保存中..." : "保存知识检索设置"}
+ {isSaving ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : "Save knowledge search settings"}
diff --git a/client/src/pages/knowledge/components/KnowledgeLibraryOverview.tsx b/client/src/pages/knowledge/components/KnowledgeLibraryOverview.tsx
index abcfd9e02..1496ab36a 100644
--- a/client/src/pages/knowledge/components/KnowledgeLibraryOverview.tsx
+++ b/client/src/pages/knowledge/components/KnowledgeLibraryOverview.tsx
@@ -51,18 +51,18 @@ function getRecommendation(props: KnowledgeLibraryOverviewProps): Recommendation
if (props.isError) {
return {
action: "retry",
- description: "资料列表暂时无法读取。重新加载不会修改已有资料或索引任务。",
+ description: "The data list cannot be read at the moment. Reloading will not modify existing data or indexing tasks.",
icon: CircleAlert,
- title: "重新加载知识资料",
+ title: "Reload knowledge data",
tone: "danger",
};
}
if (props.isLoading) {
return {
action: "open_documents",
- description: "正在整理资料状态与索引结果,加载完成后会给出可执行的下一步。",
+ description: "The data status and index results are being sorted, and the executable next step will be given after the loading is completed.",
icon: LoaderCircle,
- title: "正在读取知识资料",
+ title: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
tone: "neutral",
};
}
@@ -71,43 +71,43 @@ function getRecommendation(props: KnowledgeLibraryOverviewProps): Recommendation
action: "open_ops",
description: `${props.activeJobCount} 个索引任务正在执行,可查看进度;创作时优先选择已完成索引的资料。`,
icon: RefreshCw,
- title: "查看资料同步进度",
+ title: "View data synchronization progress",
tone: "info",
};
}
if (props.failedIndexDocumentCount > 0 || props.failedJobCount > 0) {
return {
action: "open_ops",
- description: "部分资料尚未完成索引。先查看失败原因并重建索引,其他可用资料仍可继续参与创作。",
+ description: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
icon: CircleAlert,
- title: "处理未完成的资料索引",
+ title: "Process unfinished data indexing",
tone: "warning",
};
}
if (props.visibleDocumentCount === 0 && props.hasFilters) {
return {
action: "clear_filters",
- description: "当前搜索或状态条件没有匹配结果,清除筛选即可返回完整资料列表。",
+ description: "There are no matching results for the current search or status criteria. Clear the filter to return to the complete information list.",
icon: SearchCheck,
- title: "查看其他知识资料",
+ title: "View other knowledge materials",
tone: "neutral",
};
}
if (props.visibleDocumentCount === 0) {
return {
action: "upload",
- description: "上传一份 TXT 资料,系统会建立索引,供拆书、规划和正文创作调用。",
+ description: "Upload a TXT file, and the system will create an index for use in book opening, planning, and text creation.",
icon: Upload,
- title: "先添加一份创作资料",
+ title: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
tone: "info",
};
}
if (props.searchableDocumentCount === 0) {
return {
action: "open_documents",
- description: "列表中还没有已启用且完成索引的资料。选择一份资料启用或重建索引后即可用于创作。",
+ description: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
icon: Database,
- title: "准备一份可检索资料",
+ title: "Prepare a searchable document",
tone: "warning",
};
}
@@ -115,7 +115,7 @@ function getRecommendation(props: KnowledgeLibraryOverviewProps): Recommendation
action: "open_documents",
description: `${props.searchableDocumentCount} 份资料可以参与检索。可查看版本、测试召回,或选择资料继续创作。`,
icon: BookOpenCheck,
- title: "选择资料继续创作",
+ title: "Select data to continue creating",
tone: "success",
};
}
@@ -130,40 +130,40 @@ export default function KnowledgeLibraryOverview(props: KnowledgeLibraryOverview
return (
- 上传资料
-
+ Upload information
+
);
case "retry":
return (
- 重新加载
-
+ Reload
+
);
case "clear_filters":
return (
- 清除筛选
-
+ Clear filters
+
);
case "open_ops":
return (
- 查看索引状态
-
+ View index status
+
);
default:
if (props.isLoading) {
return (
- 正在加载
-
+ Loading
+
);
}
return (
- 查看资料
-
+ View profile
+
);
}
})();
@@ -172,18 +172,18 @@ export default function KnowledgeLibraryOverview(props: KnowledgeLibraryOverview
<>
- 上传资料
-
+ Upload information
+
>
)}
@@ -193,24 +193,24 @@ export default function KnowledgeLibraryOverview(props: KnowledgeLibraryOverview
items={[
{
key: "documents",
- label: props.hasFilters ? "当前筛选结果" : "当前资料",
+ label: props.hasFilters ? "Current filter results" : "Current information",
value: documentStatusUnavailable ? "—" : props.visibleDocumentCount,
- detail: props.hasFilters ? "按当前搜索和状态条件统计" : "默认展示未归档资料",
+ detail: props.hasFilters ? "Statistics by current search and status conditions" : "Display unarchived data by default",
icon: Files,
},
{
key: "enabled",
- label: "已启用",
+ label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
value: documentStatusUnavailable ? "—" : props.enabledCount,
- detail: "可被选择用于创作",
+ detail: "Can be selected for authoring",
icon: FileCheck2,
tone: documentStatusUnavailable ? "neutral" : props.enabledCount > 0 ? "success" : "neutral",
},
{
key: "searchable",
- label: "可检索",
+ label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
value: documentStatusUnavailable ? "—" : props.searchableDocumentCount,
- detail: "已启用且索引完成",
+ detail: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
icon: SearchCheck,
tone: documentStatusUnavailable
? "neutral"
@@ -218,11 +218,11 @@ export default function KnowledgeLibraryOverview(props: KnowledgeLibraryOverview
},
{
key: "index-jobs",
- label: "正在同步",
+ label: "Synchronizing",
value: props.activeJobCount,
detail: props.failedJobCount > 0
- ? `${props.failedJobCount} 份资料的最近索引失败`
- : "没有失败任务需要处理",
+ ? `${props.failedJobCount} records where the most recent indexing failure occurred.`
+ : "There are no failed tasks to process",
icon: RefreshCw,
tone: props.failedJobCount > 0 ? "danger" : props.activeJobCount > 0 ? "info" : "neutral",
},
diff --git a/client/src/pages/knowledge/components/KnowledgeOpsTab.tsx b/client/src/pages/knowledge/components/KnowledgeOpsTab.tsx
index 22d88c219..c35209bae 100644
--- a/client/src/pages/knowledge/components/KnowledgeOpsTab.tsx
+++ b/client/src/pages/knowledge/components/KnowledgeOpsTab.tsx
@@ -51,16 +51,16 @@ export default function KnowledgeOpsTab({
- 基础统计
+ basic statistics
-
当前列表文档数:{visibleDocumentsCount}
-
启用文档数:{enabledCount}
-
停用文档数:{disabledCount}
+
Current number of list documents:{visibleDocumentsCount}
- 推荐先让 AI 自动导演从一句灵感整理方向、世界、角色和章节准备。需要完全手动填写时,也可以继续使用下方表单。
-
+ It is recommended to first let the AI automatic director organize the direction, world, characters and chapter preparation from a sentence of inspiration. If you need to fill it out completely manually, you can continue to use the form below.
+
- AI 自动导演开书
+ AI automatic director opens book
-
手动创建
+
Create manually
- 手动路径适合你已经清楚题材、卖点和前期承诺的项目;创建后仍可在工作台继续调整。
-
+ The manual path is suitable for projects where you already know the theme, selling points, and early commitments; you can continue to adjust it on the workbench after creation.
+
setBasicForm((prev) => patchNovelBasicForm(prev, patch))}
onSubmit={() => createNovelMutation.mutate()}
isSubmitting={createNovelMutation.isPending}
- submitLabel="创建并进入项目"
+ submitLabel="Create and enter the project"
showPublicationStatus={false}
framingQuickFill={(
{
createDownload(blob, fileName);
- toast.success(scope === "full" ? "整本书导出已开始。" : "当前步骤导出已开始。");
+ toast.success(scope === "full" ? "Export of the entire book has started." : "The current step export has started.");
},
onError: (error) => {
- toast.error(error instanceof Error ? error.message : "导出失败。");
+ toast.error(error instanceof Error ? error.message : "Export failed.");
},
});
@@ -923,7 +923,7 @@ export default function NovelEdit() {
takeoverDismissStorageKey(id),
activeAutoDirectorRefreshSignature,
);
- toast.success("已收起这条导演接管提醒。需要时仍可从执行详情继续处理。");
+ toast.success("This directorial takeover reminder has been closed. You can still continue processing from the execution details if needed.");
};
const isTakeoverDismissed = Boolean(
activeAutoDirectorRefreshSignature
@@ -1037,7 +1037,7 @@ export default function NovelEdit() {
const targetTaskId = input?.directorTaskId || actionTargetDirectorTaskId;
const targetTask = targetTaskId === visibleDirectorTask?.id ? visibleDirectorTask : activeAutoDirectorTask;
if (!targetTaskId) {
- throw new Error("当前没有可继续的自动导演任务。");
+ throw new Error("There are currently no automatic director tasks to continue.");
}
return continueNovelWorkflow(targetTaskId, {
continuationMode: resolveDirectorContinueMode(targetTask),
@@ -1048,72 +1048,7 @@ export default function NovelEdit() {
const targetTask = targetTaskId === visibleDirectorTask?.id ? visibleDirectorTask : activeAutoDirectorTask;
setDirectorTaskId(response.data?.taskId ?? targetTaskId);
void invalidateAutoDirectorTaskState(response.data?.taskId ?? targetTaskId);
- const feedback = resolveWorkflowContinuationFeedback(response.data, {
- mode: resolveDirectorContinueMode(targetTask),
- });
- if (feedback.tone === "error") {
- toast.error(feedback.message);
- return;
- }
- alignToAutoDirectorResumeTarget(targetTask);
- toast.success(feedback.message);
- },
- onError: (error) => {
- const message = error instanceof Error ? error.message : "继续自动导演失败。";
- toast.error(message);
- },
- });
- const calibrateDirectorStepMutation = useMutation({
- mutationFn: async (input: {
- directorTaskId: string;
- stepId: string;
- action: DirectorStepCalibrationAction;
- instruction?: string | null;
- }) => calibrateDirectorStep(input.directorTaskId, {
- stepId: input.stepId,
- action: input.action,
- instruction: input.instruction,
- }),
- onSuccess: async (_response, input) => {
- await invalidateAutoDirectorTaskState(input.directorTaskId);
- toast.success(input.action === "validate" ? "当前步骤检查已完成。" : "当前步骤已更新,请检查结果。");
- },
- onError: (error) => {
- toast.error(error instanceof Error ? error.message : "步骤校准失败。");
- },
- });
- const acceptManualChangesAndContinueMutation = useMutation({
- mutationFn: (directorTaskId: string) => acceptManualChangesAndContinueDirector(directorTaskId),
- onSuccess: async (response, directorTaskId) => {
- setDirectorTaskId(response.data?.taskId ?? directorTaskId);
- await invalidateAutoDirectorTaskState(response.data?.taskId ?? directorTaskId);
- toast.success("已确认当前修改,导演将从下一个未完成步骤继续。");
- },
- onError: (error) => {
- toast.error(error instanceof Error ? error.message : "确认修改并继续失败。");
- },
- });
- const continueAutoExecutionMutation = useMutation({
- mutationFn: async (input?: { directorTaskId?: string; continuationMode?: "auto_execute_range" | "skip_quality_repair" }) => {
- const targetTaskId = input?.directorTaskId || actionTargetDirectorTaskId;
- if (!targetTaskId) {
- throw new Error("当前没有可继续自动执行的自动导演任务。");
- }
- return continueNovelWorkflow(targetTaskId, {
- continuationMode: input?.continuationMode ?? "auto_execute_range",
- });
- },
- onSuccess: async (response, input) => {
- const targetTaskId = input?.directorTaskId || actionTargetDirectorTaskId;
- const targetTask = targetTaskId === visibleDirectorTask?.id ? visibleDirectorTask : activeAutoDirectorTask;
- setDirectorTaskId(response.data?.taskId ?? targetTaskId);
- void invalidateAutoDirectorTaskState(response.data?.taskId ?? targetTaskId);
- const feedback = resolveWorkflowContinuationFeedback(response.data, {
- mode: input?.continuationMode ?? "auto_execute_range",
- scopeLabel: activeAutoExecutionScopeLabel,
- });
- if (feedback.tone === "error") {
- toast.error(feedback.message);
+ const feedback = resolveWorkflowContinuationFeedback(response.data,{ mode: resolveDirectorContinueMode(targetTask), }); if (feedback.tone === "error") { toast.error(feedback.message); return; } alignToAutoDirectorResumeTarget(targetTask); toast.success(feedback.message); }, onError: (error) => { const message = error instanceof Error ? error.message : "Continue AutoDirector failed."; toast.error(message); }, }); const calibrateDirectorStepMutation = useMutation({ mutationFn: async (input: { directorTaskId: string; stepId: string; action: DirectorStepCalibrationAction; instruction?: string | null; }) => calibrateDirectorStep(input.directorTaskId, { stepId: input.stepId, action: input.action, instruction: input.instruction, }), onSuccess: async (_response, input) => { await invalidateAutoDirectorTaskState(input.directorTaskId); toast.success(input.action === "validate" ? "Current step check completed." : "Current step updated, please check the result."); }, onError: (error) => { toast.error(error instanceof Error ? error.message : "Step calibration failed."); }, }); const acceptManualChangesAndContinueMutation = useMutation({ mutationFn: (directorTaskId: string) => acceptManualChangesAndContinueDirector(directorTaskId), onSuccess: async (response, directorTaskId) => { setDirectorTaskId(response.data?.taskId ?? directorTaskId); await invalidateAutoDirectorTaskState(response.data?.taskId ?? directorTaskId); toast.success("The current modification has been confirmed. The director will continue from the next incomplete step."); }, onError: (error) => { toast.error(error instanceof Error ? error.message : "Confirmation of modification and continued failure."); }, }); const continueAutoExecutionMutation = useMutation({ mutationFn: async (input?: { directorTaskId?: string; continuationMode?: "auto_execute_range" | "skip_quality_repair" }) => { const targetTaskId = input?.directorTaskId || actionTargetDirectorTaskId; if (!targetTaskId) { throw new Error("There is currently no auto-directing task that can be continued."); } return continueNovelWorkflow(targetTaskId, { continuationMode: input?.continuationMode ?? "auto_execute_range", }); }, onSuccess: async (response, input) => { const targetTaskId = input?.directorTaskId || actionTargetDirectorTaskId; const targetTask = targetTaskId === visibleDirectorTask?.id ? visibleDirectorTask : activeAutoDirectorTask; setDirectorTaskId(response.data?.taskId ?? targetTaskId); void invalidateAutoDirectorTaskState(response.data?.taskId ?? targetTaskId); const feedback = resolveWorkflowContinuationFeedback(response.data, { mode: input?.continuationMode ?? "auto_execute_range", scopeLabel: activeAutoExecutionScopeLabel, }); if (feedback.tone === "error") { toast.error(feedback.message);
return;
}
alignToAutoDirectorResumeTarget(targetTask);
@@ -1151,7 +1086,7 @@ export default function NovelEdit() {
? error.message
: input.mode === "auto_execute_range"
? `继续自动执行${activeAutoExecutionScopeLabel}失败。`
- : "继续自动导演失败。";
+ : "Continue automatic director failure.";
toast.error(message);
},
});
@@ -1162,7 +1097,7 @@ export default function NovelEdit() {
}) => {
const targetTaskId = input.directorTaskId || actionTargetDirectorTaskId;
if (!targetTaskId) {
- throw new Error("当前没有可执行的动作。");
+ throw new Error("There are currently no actions to perform.");
}
return executeAutoDirectorFollowUpAction(targetTaskId, {
actionCode: input.actionCode,
@@ -1180,10 +1115,10 @@ export default function NovelEdit() {
toast.error(result.message);
return;
}
- toast.success(result?.message ?? "已执行动作。");
+ toast.success(result?.message ?? "Action performed.");
},
onError: (error) => {
- toast.error(error instanceof Error ? error.message : "执行动作失败。");
+ toast.error(error instanceof Error ? error.message : "Execution of action failed.");
},
});
const consistencyIssue = useMemo(
@@ -1237,7 +1172,7 @@ export default function NovelEdit() {
if (!showToast) {
return;
}
- toast.success(targetVolumeId ? "已定位到当前卷拆章,可直接修复标题。" : "已切到节奏 / 拆章,可直接修复标题。");
+ toast.success(targetVolumeId ? "The chapter of the current volume has been located and the title can be repaired directly." : "The rhythm/chapter has been cut and the title can be repaired directly.");
};
const handleTaskDrawerProjectionAction = (action: DirectorBookAutomationAction) => {
if (!bookAutomationProjection) {
@@ -1312,7 +1247,7 @@ export default function NovelEdit() {
const retryAutoDirectorWithCurrentModelMutation = useMutation({
mutationFn: async () => {
if (!retryableAutoDirectorTask?.id) {
- throw new Error("当前没有可重试的自动导演任务。");
+ throw new Error("There are currently no autodirector tasks to retry.");
}
return retryTask("novel_workflow", retryableAutoDirectorTask.id, {
llmOverride: {
@@ -1327,83 +1262,7 @@ export default function NovelEdit() {
syncAutoDirectorTaskCache(queryClient, id, response.data);
void invalidateAutoDirectorTaskState(response.data?.id ?? retryableAutoDirectorTask?.id);
setIsTaskDrawerOpen(true);
- toast.success(`已切换到 ${llm.provider} / ${llm.model} 并重新启动自动导演。`);
- },
- onError: (error) => {
- const message = error instanceof Error ? error.message : "切换当前模型重试失败。";
- toast.error(message);
- },
- });
- const retryAutoDirectorWithTaskModelMutation = useMutation({
- mutationFn: async () => {
- if (!retryableAutoDirectorTask?.id) {
- throw new Error("当前没有可重试的自动导演任务。");
- }
- return retryTask("novel_workflow", retryableAutoDirectorTask.id, { resume: true });
- },
- onSuccess: async (response) => {
- syncAutoDirectorTaskCache(queryClient, id, response.data);
- void invalidateAutoDirectorTaskState(response.data?.id ?? retryableAutoDirectorTask?.id);
- setIsTaskDrawerOpen(true);
- toast.success("自动导演已按任务原模型重新启动。");
- },
- onError: (error) => {
- const message = error instanceof Error ? error.message : "按原模型重试失败。";
- toast.error(message);
- },
- });
- const cancelAutoDirectorMutation = useMutation({
- mutationFn: async (targetTaskId?: string) => {
- const taskId = targetTaskId || displayAutoDirectorTask?.id || activeAutoDirectorTask?.id;
- if (!taskId) {
- throw new Error("当前没有可取消的自动导演任务。");
- }
- return cancelTask("novel_workflow", taskId);
- },
- onSuccess: async (response, targetTaskId) => {
- setIsDirectorExitActionExpanded(false);
- syncAutoDirectorTaskCache(queryClient, id, response.data);
- void invalidateAutoDirectorTaskState(response.data?.id ?? targetTaskId ?? displayAutoDirectorTask?.id ?? activeAutoDirectorTask?.id);
- toast.success("已取消自动导演任务。");
- },
- onError: (error) => {
- const message = error instanceof Error ? error.message : "取消自动导演失败。";
- toast.error(message);
- },
- });
- const archiveCompletedAutoDirectorMutation = useMutation({
- mutationFn: async (targetTaskId?: string) => {
- const taskId = targetTaskId || displayAutoDirectorTask?.id;
- if (!taskId) {
- throw new Error("当前没有可收起的自动导演完成记录。");
- }
- return archiveTask("novel_workflow", taskId);
- },
- onSuccess: async (_response, targetTaskId) => {
- setIsDirectorExitActionExpanded(false);
- await invalidateAutoDirectorTaskState(targetTaskId ?? displayAutoDirectorTask?.id);
- toast.success("已收起这次自动导演完成提醒。");
- },
- onError: (error) => {
- const message = error instanceof Error ? error.message : "收起自动导演完成提醒失败。";
- toast.error(message);
- },
- });
- useEffect(() => {
- setRetryOverride({
- provider: llm.provider,
- model: llm.model,
- temperature: llm.temperature,
- });
- }, [activeAutoDirectorTask?.id, llm.model, llm.provider, llm.temperature]);
- useEffect(() => {
- if (activeAutoDirectorTask?.status !== "failed") {
- if (autoOpenedFailedTaskId) {
- setAutoOpenedFailedTaskId("");
- }
- return;
- }
- if (!activeAutoDirectorTask.id || activeAutoDirectorTask.id === autoOpenedFailedTaskId) {
+ toast.success(`已切换到 ${llm.provider} / ${llm.model} 并重新启动自动导演。`); }, onError: (error) => { const message = error instanceof Error ? error.message : "Failed to retry switching the current model."; toast.error(message); }, }); const retryAutoDirectorWithTaskModelMutation = useMutation({ mutationFn: async () => { if (!retryableAutoDirectorTask?.id) { throw new Error("There is no retryable AutoDirector task."); } return retryTask("novel_workflow", retryableAutoDirectorTask.id, { resume: true }); }, onSuccess: async (response) => { syncAutoDirectorTaskCache(queryClient, id, response.data); void invalidateAutoDirectorTaskState(response.data?.id ?? retryableAutoDirectorTask?.id); setIsTaskDrawerOpen(true); toast.success("Automatic Director has been restarted according to the original task model."); }, onError: (error) => { const message = error instanceof Error ? error.message : "Failed to retry according to the original model."; toast.error(message); }, }); const cancelAutoDirectorMutation = useMutation({ mutationFn: async (targetTaskId?: string) => { const taskId = targetTaskId || displayAutoDirectorTask?.id || activeAutoDirectorTask?.id; if (!taskId) { throw new Error("There are currently no cancelable automatic director tasks."); } return cancelTask("novel_workflow", taskId); }, onSuccess: async (response, targetTaskId) => { setIsDirectorExitActionExpanded(false); syncAutoDirectorTaskCache(queryClient, id, response.data); void invalidateAutoDirectorTaskState(response.data?.id ?? targetTaskId ?? displayAutoDirectorTask?.id ?? activeAutoDirectorTask?.id); toast.success("AutoDirector task has been cancelled."); }, onError: (error) => { const message = error instanceof Error ? error.message : "Cancellation of autodirector failed."; toast.error(message); }, }); const archiveCompletedAutoDirectorMutation = useMutation({ mutationFn: async (targetTaskId?: string) => { const taskId = targetTaskId || displayAutoDirectorTask?.id; if (!taskId) { throw new Error("No autodirector completion record can be collapsed at present."); } return archiveTask("novel_workflow", taskId); }, onSuccess: async (_response, targetTaskId) => { setIsDirectorExitActionExpanded(false); await invalidateAutoDirectorTaskState(targetTaskId ?? displayAutoDirectorTask?.id); toast.success("The autodirector completion notification has been collapsed."); }, onError: (error) => { const message = error instanceof Error ? error.message : "Failed to collapse the autodirector completion notification."; toast.error(message); }, }); useEffect(() => { setRetryOverride({ provider: llm.provider, model: llm.model, temperature: llm.temperature, }); }, [activeAutoDirectorTask?.id, llm.model, llm.provider, llm.temperature]); useEffect(() => { if (activeAutoDirectorTask?.status !== "failed") { if (autoOpenedFailedTaskId) { setAutoOpenedFailedTaskId(""); } return; } if (!activeAutoDirectorTask.id || activeAutoDirectorTask.id === autoOpenedFailedTaskId) {
return;
}
setIsTaskDrawerOpen(true);
@@ -1457,24 +1316,24 @@ export default function NovelEdit() {
task,
projection: bookAutomationProjection,
});
- const novelTitle = novelDetailQuery.data?.data?.title?.trim() || task.title?.trim() || "当前项目";
+ const novelTitle = novelDetailQuery.data?.data?.title?.trim() || task.title?.trim() || "current project";
const reviewScope = activeDirectorSession?.reviewScope ?? null;
const autoExecutionScopeLabel = resolveAutoExecutionScopeLabel(task);
const actions: NonNullable = [];
if (activeChapterTitleWarning) {
actions.push({
label: chapterTitleRepairMutation.isPending && chapterTitleRepairMutation.pendingTaskId === task.id
- ? "AI 修复中..."
+ ? "AI is being fixed..."
: activeChapterTitleWarning.label,
onClick: () => {
if (hasUnsavedVolumeDraft) {
- toast.error("当前拆章工作区还有未保存修改,请先保存工作区,再发起 AI 修复标题。");
+ toast.error("There are still unsaved modifications in the current chapter-breaking workspace. Please save the workspace first and then initiate AI repair of the title.");
return;
}
chapterTitleRepairMutation.startRepair(task);
},
variant: mode === "failed" ? "default" : "outline",
- disabled: chapterTitleRepairMutation.isPending,
+ disabled:chapterTitleRepairMutation.isPending,
});
}
const reviewTab = tabFromScope(reviewScope);
@@ -1483,7 +1342,7 @@ export default function NovelEdit() {
&& task.checkpointType === "candidate_selection_required"
) {
actions.push({
- label: "去确认书级方向",
+ label: "Go to confirm book level direction",
onClick: () => openCandidateSelection(task.id),
variant: "default",
});
@@ -1494,14 +1353,14 @@ export default function NovelEdit() {
&& task.checkpointType !== "chapter_batch_ready"
) {
actions.push({
- label: "去当前审核阶段",
+ label: "Go to current review stage",
onClick: () => setActiveTab(reviewTab),
variant: "outline",
});
}
if (task.pendingManualRecovery) {
actions.push({
- label: continueAutoDirectorMutation.isPending ? "继续中..." : "继续自动导演",
+ label: continueAutoDirectorMutation.isPending ? "Continue..." : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
onClick: () => continueAutoDirectorMutation.mutate({ directorTaskId: task.id }),
variant: "default",
disabled: continueAutoDirectorMutation.isPending,
@@ -1513,12 +1372,12 @@ export default function NovelEdit() {
if (action === "validate") {
return null;
}
- const value = window.prompt("告诉 AI 这一步需要调整什么(可留空)", "");
+ const value = window.prompt("Tell AI what needs to be adjusted in this step (can be left blank)", "");
return value === null ? undefined : value.trim();
};
if (stepId) {
actions.push({
- label: calibrateDirectorStepMutation.isPending ? "检查中..." : "AI 检查当前步骤",
+ label: calibrateDirectorStepMutation.isPending ? "Checking..." : "AI checks current step",
onClick: () => calibrateDirectorStepMutation.mutate({
directorTaskId: task.id,
stepId,
@@ -1528,7 +1387,7 @@ export default function NovelEdit() {
disabled: calibrateDirectorStepMutation.isPending,
});
actions.push({
- label: calibrateDirectorStepMutation.isPending ? "完善中..." : "AI 完善当前步骤",
+ label: calibrateDirectorStepMutation.isPending ? "In progress..." : "AI improves current steps",
onClick: () => {
const instruction = requestCalibrationInstruction("improve");
if (instruction !== undefined) {
@@ -1539,8 +1398,8 @@ export default function NovelEdit() {
disabled: calibrateDirectorStepMutation.isPending,
});
actions.push({
- label: calibrateDirectorStepMutation.isPending ? "生成中..." : "重新生成当前步骤",
- onClick: () => {
+ label: calibrateDirectorStepMutation.isPending ? "Generating..." : "Regenerate current step",
+ onClick:() => {
const instruction = requestCalibrationInstruction("regenerate");
if (instruction !== undefined) {
calibrateDirectorStepMutation.mutate({ directorTaskId: task.id, stepId, action: "regenerate", instruction });
@@ -1551,13 +1410,13 @@ export default function NovelEdit() {
});
}
actions.push({
- label: acceptManualChangesAndContinueMutation.isPending ? "确认中..." : "保存并确认",
+ label: acceptManualChangesAndContinueMutation.isPending ? "Confirming..." : "Save and confirm",
onClick: () => acceptManualChangesAndContinueMutation.mutate(task.id),
variant: "default",
disabled: acceptManualChangesAndContinueMutation.isPending,
});
actions.push({
- label: acceptManualChangesAndContinueMutation.isPending ? "继续中..." : "继续自动导演",
+ label: acceptManualChangesAndContinueMutation.isPending ? "Continue..." : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
onClick: () => acceptManualChangesAndContinueMutation.mutate(task.id),
variant: "outline",
disabled: acceptManualChangesAndContinueMutation.isPending,
@@ -1570,7 +1429,7 @@ export default function NovelEdit() {
disabled: continueAutoExecutionMutation.isPending,
});
actions.push({
- label: "进入章节执行",
+ label: "Enter chapter execution",
onClick: () => {
if (task.resumeTarget?.chapterId) {
setSelectedChapterId(task.resumeTarget.chapterId);
@@ -1581,7 +1440,7 @@ export default function NovelEdit() {
});
} else if (mode === "waiting" && task.checkpointType === "workflow_completed") {
actions.push({
- label: "进入章节执行",
+ label: "Enter chapter execution",
onClick: () => openChapterExecution(task),
variant: "default",
});
@@ -1596,14 +1455,14 @@ export default function NovelEdit() {
disabled: continueAutoExecutionMutation.isPending,
});
actions.push({
- label: "打开质量修复",
+ label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
onClick: () => openQualityRepair(task),
variant: "outline",
});
} else if (mode === "waiting") {
actions.push({
- label: continueAutoDirectorMutation.isPending ? "继续中..." : "继续自动导演",
- onClick: () => continueAutoDirectorMutation.mutate({ directorTaskId: task.id }),
+ label: continueAutoDirectorMutation.isPending ? "Continue..." : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ onClick:() => continueAutoDirectorMutation.mutate({ directorTaskId: task.id }),
variant: "default",
disabled: continueAutoDirectorMutation.isPending,
});
@@ -1616,28 +1475,28 @@ export default function NovelEdit() {
disabled: continueAutoExecutionMutation.isPending,
});
actions.push({
- label: "打开质量修复",
+ label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
onClick: () => openQualityRepair(task),
variant: "outline",
});
}
if (consistencyIssue) {
actions.push({
- label: continueAutoDirectorMutation.isPending ? "修复中..." : "补齐导演产物",
+ label: continueAutoDirectorMutation.isPending ? "Under repair..." : "Complement director products",
onClick: () => continueAutoDirectorMutation.mutate({ directorTaskId: task.id }),
variant: "default",
disabled: continueAutoDirectorMutation.isPending,
});
if (consistencyIssue === "missing_characters") {
actions.push({
- label: "去角色准备",
+ label: "Go to character preparation",
onClick: () => setActiveTab("character"),
variant: "outline",
});
}
} else if (task.checkpointType === "chapter_batch_ready" && mode !== "waiting") {
actions.push({
- label: "进入章节执行",
+ label: "Enter chapter execution",
onClick: () => {
if (task.resumeTarget?.chapterId) {
setSelectedChapterId(task.resumeTarget.chapterId);
@@ -1651,28 +1510,28 @@ export default function NovelEdit() {
if (canCancelTask) {
if (task.status === "failed") {
actions.push({
- label: cancelAutoDirectorMutation.isPending ? "取消中..." : "取消任务",
+ label: cancelAutoDirectorMutation.isPending ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : "Cancel task",
onClick: () => cancelAutoDirectorMutation.mutate(task.id),
variant: "destructive",
disabled: cancelAutoDirectorMutation.isPending,
});
} else if (isDirectorExitActionExpanded) {
actions.push({
- label: "继续导演",
+ label: "continue directing",
onClick: () => setIsDirectorExitActionExpanded(false),
variant: "outline",
disabled: cancelAutoDirectorMutation.isPending,
});
actions.push({
- label: cancelAutoDirectorMutation.isPending ? "退出中..." : "退出导演模式",
+ label: cancelAutoDirectorMutation.isPending ? "Exiting..." : "Exit director mode",
onClick: () => cancelAutoDirectorMutation.mutate(task.id),
variant: "destructive",
disabled: cancelAutoDirectorMutation.isPending,
});
} else {
actions.push({
- label: "退出导演模式",
- onClick: () => setIsDirectorExitActionExpanded(true),
+ label: "Exit director mode",
+ onClick:() => setIsDirectorExitActionExpanded(true),
variant: "destructive",
disabled: cancelAutoDirectorMutation.isPending,
});
@@ -1682,26 +1541,26 @@ export default function NovelEdit() {
|| task.status === "cancelled"
) {
actions.push({
- label: "收起此提醒",
+ label: "Discard this reminder",
onClick: dismissTakeover,
variant: "secondary",
});
} else if (canArchiveCompletedAutoDirectorTask(task)) {
actions.push({
- label: archiveCompletedAutoDirectorMutation.isPending ? "收起中..." : "完成并收起",
+ label: archiveCompletedAutoDirectorMutation.isPending ? "Collapse..." : "Complete and close",
onClick: () => archiveCompletedAutoDirectorMutation.mutate(task.id),
variant: "secondary",
disabled: archiveCompletedAutoDirectorMutation.isPending,
});
} else if (task.status === "waiting_approval") {
actions.push({
- label: "收起此提醒",
+ label: "Discard this reminder",
onClick: dismissTakeover,
variant: "secondary",
});
}
actions.push({
- label: "执行详情",
+ label: "Execution details",
onClick: () => setIsTaskDrawerOpen(true),
variant: mode === "running" ? "outline" : "secondary",
});
@@ -1721,11 +1580,11 @@ export default function NovelEdit() {
scopeLabel: autoExecutionScopeLabel,
}),
description: consistencyIssue === "missing_characters"
- ? "任务记录显示已完成开书交接,但当前项目里还没有角色资产,所以角色准备和章节执行都不完整。可以直接补齐导演产物,系统会继续修复。"
+ ? "The task record shows that the book opening handover has been completed, but there are no character assets in the current project, so character preparation and chapter execution are incomplete. You can directly patch the director's product, and the system will continue to be repaired."
: consistencyIssue === "missing_chapters"
- ? "任务记录显示前几章已经可开写,但当前章节执行区还是空的,说明导演产物还没有完整落库。可以直接补齐导演产物继续修复。"
+ ? "The mission record shows that the first few chapters can be written, but the execution area of the current chapter is still empty, indicating that the director's products have not been completely deposited. You can directly complete the director's product and continue repairing it."
: task.pendingManualRecovery
- ? "任务已停在当前进度。你可以查看执行详情,再从最近进度点继续。"
+ ? "The task has stopped at its current progress. You can view execution details and continue from the most recent progress point."
: buildTakeoverDescription({
mode,
checkpointType: task.checkpointType,
@@ -1736,15 +1595,15 @@ export default function NovelEdit() {
? dashboardView.progressPercent
: task.progress,
currentAction: consistencyIssue === "missing_characters"
- ? "检测到角色准备仍为空,当前导演结果需要继续补齐。"
+ ? "It is detected that the character preparation is still empty, and the current director results need to be filled in."
: consistencyIssue === "missing_chapters"
- ? "检测到章节执行区为空,当前导演结果需要继续同步章节资源。"
+ ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."
: task.pendingManualRecovery
? (
task.blockingReason?.trim()
|| task.recoveryHint?.trim()
|| task.lastError?.trim()
- || "任务已暂停,等待从最近检查点恢复。"
+ || "The task is paused, waiting to be resumed from the most recent checkpoint."
)
: dashboardView?.currentAction?.trim()
? dashboardView.currentAction.trim()
@@ -1752,13 +1611,13 @@ export default function NovelEdit() {
? activeDirectorSnapshot.displayState.currentAction.trim()
: automationActionText
? automationActionText
- : mode === "running" && task.checkpointType === "chapter_batch_ready" && task.currentItemLabel?.includes("已暂停")
+ : mode === "running" && task.checkpointType === "chapter_batch_ready" && task.currentItemLabel?.includes("Suspended")
? `正在继续自动执行${autoExecutionScopeLabel}`
: task.currentItemLabel ?? null,
checkpointLabel: consistencyIssue
- ? "导演产物待补齐"
+ ? "Director products to be completed"
: task.pendingManualRecovery
- ? "等待恢复"
+ ? "Waiting for recovery"
: mode === "running" && task.checkpointType === "chapter_batch_ready"
? `${autoExecutionScopeLabel}自动执行中`
: formatTakeoverCheckpoint(task.checkpointType, task),
@@ -1799,11 +1658,11 @@ export default function NovelEdit() {
if (activeChapterTitleWarning) {
actions.push({
label: chapterTitleRepairMutation.isPending && chapterTitleRepairMutation.pendingTaskId === task.id
- ? "AI 修复中..."
+ ? "AI is being fixed..."
: activeChapterTitleWarning.label,
onClick: () => {
if (hasUnsavedVolumeDraft) {
- toast.error("当前拆章工作区还有未保存修改,请先保存工作区,再发起 AI 修复标题。");
+ toast.error("There are still unsaved modifications in the current chapter-breaking workspace. Please save the workspace first and then initiate AI repair of the title.");
return;
}
chapterTitleRepairMutation.startRepair(task);
@@ -1814,14 +1673,14 @@ export default function NovelEdit() {
}
if (consistencyIssue) {
actions.push({
- label: continueAutoDirectorMutation.isPending ? "补齐中..." : "补齐导演产物",
+ label: continueAutoDirectorMutation.isPending ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : "Complement director products",
onClick: () => continueAutoDirectorMutation.mutate({ directorTaskId: task.id }),
variant: "default",
disabled: continueAutoDirectorMutation.isPending,
});
if (consistencyIssue === "missing_characters") {
actions.push({
- label: "去角色准备",
+ label: "Go to character preparation",
onClick: () => {
setActiveTab("character");
setIsTaskDrawerOpen(false);
@@ -1831,7 +1690,7 @@ export default function NovelEdit() {
}
} else if (task.pendingManualRecovery) {
actions.push({
- label: continueAutoDirectorMutation.isPending ? "继续中..." : "继续自动导演",
+ label: continueAutoDirectorMutation.isPending ? "Continue..." : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
onClick: () => continueAutoDirectorMutation.mutate({ directorTaskId: task.id }),
variant: "default",
disabled: continueAutoDirectorMutation.isPending,
@@ -1848,13 +1707,13 @@ export default function NovelEdit() {
disabled: continueAutoExecutionMutation.isPending,
});
actions.push({
- label: "进入章节执行",
+ label: "Enter chapter execution",
onClick: () => openChapterExecution(task),
variant: "outline",
});
} else if (task.status === "waiting_approval" && task.checkpointType === "candidate_selection_required") {
actions.push({
- label: "去确认书级方向",
+ label: "Go to confirm book level direction",
onClick: () => openCandidateSelection(task.id),
variant: "default",
});
@@ -1870,7 +1729,7 @@ export default function NovelEdit() {
disabled: continueAutoExecutionMutation.isPending,
});
actions.push({
- label: "打开质量修复",
+ label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
onClick: () => openQualityRepair(task),
variant: "outline",
});
@@ -1880,12 +1739,12 @@ export default function NovelEdit() {
&& task.checkpointType !== "chapter_batch_ready"
) {
actions.push({
- label: "去当前审核阶段",
+ label: "Go to current review stage",
onClick: openReviewStage,
variant: "default",
});
actions.push({
- label: continueAutoDirectorMutation.isPending ? "继续中..." : "继续自动导演",
+ label: continueAutoDirectorMutation.isPending ? "Continue..." : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
onClick: () => continueAutoDirectorMutation.mutate({ directorTaskId: task.id }),
variant: "outline",
disabled: continueAutoDirectorMutation.isPending,
@@ -1899,13 +1758,13 @@ export default function NovelEdit() {
disabled: continueAutoExecutionMutation.isPending,
});
actions.push({
- label: "打开质量修复",
+ label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
onClick: () => openQualityRepair(task),
variant: "outline",
});
} else if (task.checkpointType === "chapter_batch_ready" || task.checkpointType === "workflow_completed") {
actions.push({
- label: "进入章节执行",
+ label: "Enter chapter execution",
onClick: () => openChapterExecution(task),
variant: "default",
});
@@ -1915,7 +1774,7 @@ export default function NovelEdit() {
if (canCancelDirectorTask(task)) {
actions.push({
- label: cancelAutoDirectorMutation.isPending ? "取消中..." : "取消任务",
+ label: cancelAutoDirectorMutation.isPending ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : "Cancel task",
onClick: () => cancelAutoDirectorMutation.mutate(task.id),
variant: "destructive",
disabled: cancelAutoDirectorMutation.isPending,
@@ -2027,18 +1886,18 @@ export default function NovelEdit() {
return;
}
const labels: Record = {
- basic: "项目设定已打开",
- story_macro: "故事宏观规划已打开",
- character: "角色准备已打开",
- outline: "卷战略 / 卷骨架已打开",
- structured: "节奏 / 拆章已打开",
- chapter: selectedChapter ? `正在查看第${selectedChapter.order}章执行面板` : "章节执行已打开",
- pipeline: "质量修复 / 流水线已打开",
+ basic: "Project settings are open",
+ story_macro: "The story macro plan has been opened",
+ character: "Character preparation is on",
+ outline: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ structured: "Rhythm/Chapter Breaking is open",
+ chapter: selectedChapter ? `正在查看第${selectedChapter.order}章执行面板` : "Chapter execution is turned on",
+ pipeline: "Quality fix/pipeline turned on",
};
void syncNovelWorkflowStageSilently({
novelId: id,
stage: workflowStageFromTab(activeTab),
- itemLabel: labels[activeTab] ?? "小说主流程已打开",
+ itemLabel: labels[activeTab] ?? "The main process of the novel has been opened",
chapterId: activeTab === "chapter" ? selectedChapterId || undefined : undefined,
volumeId: activeTab === "structured" || activeTab === "outline" ? selectedVolumeId || undefined : undefined,
status: "waiting_approval",
@@ -2177,10 +2036,10 @@ export default function NovelEdit() {
mutationFn: (proposalId: string) => confirmCharacterResourceProposal(id, proposalId),
onSuccess: async () => {
await invalidateCharacterResourceViews();
- toast.success("资源变更已确认,后续写作会参考它。");
+ toast.success("The resource change has been acknowledged and will be referenced in subsequent writing.");
},
onError: (error) => {
- toast.error(error instanceof Error ? error.message : "确认资源变更失败。");
+ toast.error(error instanceof Error ? error.message : "Confirmation of resource changes failed.");
},
});
@@ -2188,17 +2047,17 @@ export default function NovelEdit() {
mutationFn: (proposalId: string) => rejectCharacterResourceProposal(id, proposalId),
onSuccess: async () => {
await invalidateCharacterResourceViews();
- toast.success("资源变更已忽略。");
+ toast.success("Resource changes ignored.");
},
onError: (error) => {
- toast.error(error instanceof Error ? error.message : "忽略资源变更失败。");
+ toast.error(error instanceof Error ? error.message : "Ignore resource change failure.");
},
});
const extractChapterResourcesMutation = useMutation({
mutationFn: async () => {
if (!selectedChapterId) {
- throw new Error("请先选择要复查资源的章节。");
+ throw new Error("Please first select the chapter for which you want to review resources.");
}
return extractChapterResources(id, selectedChapterId, {
provider: llm.provider,
@@ -2215,10 +2074,10 @@ export default function NovelEdit() {
}
toast.success(committedCount > 0
? `已复查本章资源,${committedCount} 个变更会用于后续写作。`
- : "已复查本章资源,未发现需要更新的关键资源。");
+ : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.");
},
onError: (error) => {
- toast.error(error instanceof Error ? error.message : "复查本章资源失败。");
+ toast.error(error instanceof Error ? error.message : "Failed to review resources for this chapter.");
},
});
@@ -2238,7 +2097,7 @@ export default function NovelEdit() {
: `已回填最近 ${scanned} 章资源,${committed} 条变化会用于后续写作。`);
},
onError: (error) => {
- toast.error(error instanceof Error ? error.message : "回填角色资源失败。");
+ toast.error(error instanceof Error ? error.message : "Backfilling character resources failed.");
},
});
@@ -2562,7 +2421,7 @@ export default function NovelEdit() {
onCreateChapter: () => createChapterMutation.mutate(),
isCreatingChapter: createChapterMutation.isPending,
onRemoveChapter: (chapter: Chapter) => {
- const confirmed = window.confirm(`确认移除「第${chapter.order}章 ${chapter.title || "未命名章节"}」吗?该章节尚未开始写作,移除后不可恢复。`);
+ const confirmed = window.confirm(`确认移除「第${chapter.order}章 ${chapter.title || "Unnamed chapter"}」吗?该章节尚未开始写作,移除后不可恢复。`);
if (confirmed) {
deleteManualChapterMutation.mutate(chapter.id);
}
@@ -2646,7 +2505,7 @@ export default function NovelEdit() {
onAbortStream: handleAbortChapterStream,
directorTakeoverEntry: undefined,
};
- const pipelineTab = { novelId: id, worldInjectionSummary, hasCharacters, onGoToCharacterTab: goToCharacterTab, pipelineForm, onPipelineFormChange: (field: "startOrder" | "endOrder" | "maxRetries" | "runMode" | "autoReview" | "autoRepair" | "skipCompleted" | "qualityThreshold" | "repairMode", value: number | boolean | string) => setPipelineForm((prev) => ({ ...prev, [field]: value } as typeof prev)), maxOrder, onGenerateBible: () => void bibleSSE.start(`/novels/${id}/bible/generate`, { provider: llm.provider, model: llm.model, temperature: 0.6 }), onAbortBible: bibleSSE.abort, isBibleStreaming: bibleSSE.isStreaming, bibleStreamContent: bibleSSE.content, onGenerateBeats: () => void beatsSSE.start(`/novels/${id}/beats/generate`, { provider: llm.provider, model: llm.model, targetChapters: pipelineForm.endOrder }), onAbortBeats: beatsSSE.abort, isBeatsStreaming: beatsSSE.isStreaming, beatsStreamContent: beatsSSE.content, onRunPipeline: (patch?: Partial) => runPipelineMutation.mutate(patch), isRunningPipeline: runPipelineMutation.isPending, pipelineMessage, pipelineJob: pipelineJobQuery.data?.data, chapters, selectedChapterId, onSelectedChapterChange: setSelectedChapterId, onReviewChapter: () => reviewMutation.mutate(), isReviewing: reviewMutation.isPending, onRepairChapter: () => { setRepairBeforeContent(selectedChapter?.content ?? ""); setRepairAfterContent(""); setActiveRepairStream(selectedChapter ? { chapterId: selectedChapter.id, chapterLabel: `第${selectedChapter.order}章 ${selectedChapter.title || "未命名章节"}` } : null); void repairSSE.start(`/novels/${id}/chapters/${selectedChapterId}/repair`, { provider: llm.provider, model: llm.model, reviewIssues: reviewResult?.issues ?? [], auditIssueIds: openAuditIssueIds }); }, isRepairing: repairSSE.isStreaming, onGenerateHook: () => hookMutation.mutate(), isGeneratingHook: hookMutation.isPending, reviewResult, repairBeforeContent, repairAfterContent, repairStreamContent: repairSSE.content, isRepairStreaming: repairSSE.isStreaming, onAbortRepair: handleAbortRepair, qualitySummary, chapterReports: qualityReportQuery.data?.data?.chapterReports ?? [], bible, plotBeats };
+ const pipelineTab = { novelId: id, worldInjectionSummary, hasCharacters, onGoToCharacterTab: goToCharacterTab, pipelineForm, onPipelineFormChange: (field: "startOrder" | "endOrder" | "maxRetries" | "runMode" | "autoReview" | "autoRepair" | "skipCompleted" | "qualityThreshold" | "repairMode", value: number | boolean | string) => setPipelineForm((prev) => ({ ...prev, [field]: value } as typeof prev)), maxOrder, onGenerateBible: () => void bibleSSE.start(`/novels/${id}/bible/generate`, { provider: llm.provider, model: llm.model, temperature: 0.6 }), onAbortBible: bibleSSE.abort, isBibleStreaming: bibleSSE.isStreaming, bibleStreamContent: bibleSSE.content, onGenerateBeats: () => void beatsSSE.start(`/novels/${id}/beats/generate`, { provider: llm.provider, model: llm.model, targetChapters: pipelineForm.endOrder }), onAbortBeats: beatsSSE.abort, isBeatsStreaming: beatsSSE.isStreaming, beatsStreamContent: beatsSSE.content, onRunPipeline: (patch?: Partial) => runPipelineMutation.mutate(patch), isRunningPipeline: runPipelineMutation.isPending, pipelineMessage, pipelineJob: pipelineJobQuery.data?.data, chapters, selectedChapterId, onSelectedChapterChange: setSelectedChapterId, onReviewChapter: () => reviewMutation.mutate(), isReviewing: reviewMutation.isPending, onRepairChapter: () => { setRepairBeforeContent(selectedChapter?.content ?? ""); setRepairAfterContent(""); setActiveRepairStream(selectedChapter ? { chapterId: selectedChapter.id, chapterLabel: `第${selectedChapter.order}章 ${selectedChapter.title || "Unnamed chapter"}` } : null); void repairSSE.start(`/novels/${id}/chapters/${selectedChapterId}/repair`, { provider: llm.provider, model: llm.model, reviewIssues: reviewResult?.issues ?? [], auditIssueIds: openAuditIssueIds }); }, isRepairing: repairSSE.isStreaming, onGenerateHook: () => hookMutation.mutate(), isGeneratingHook: hookMutation.isPending, reviewResult, repairBeforeContent, repairAfterContent, repairStreamContent: repairSSE.content, isRepairStreaming: repairSSE.isStreaming, onAbortRepair: handleAbortRepair, qualitySummary, chapterReports: qualityReportQuery.data?.data?.chapterReports ?? [], bible, plotBeats };
const characterTab = {
novelId: id,
llmProvider: llm.provider,
diff --git a/client/src/pages/novels/NovelList.tsx b/client/src/pages/novels/NovelList.tsx
index 1a18a8d8a..cc5f9e809 100644
--- a/client/src/pages/novels/NovelList.tsx
+++ b/client/src/pages/novels/NovelList.tsx
@@ -90,10 +90,10 @@ export default function NovelList() {
mutationFn: (id: string) => deleteNovel(id),
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: queryKeys.novels.all });
- toast.success("小说已删除。");
+ toast.success("The novel has been deleted.");
},
onError: (error) => {
- toast.error(error instanceof Error ? error.message : "删除小说失败。");
+ toast.error(error instanceof Error ? error.message : "Failed to delete novel.");
},
});
@@ -106,10 +106,10 @@ export default function NovelList() {
),
onSuccess: ({ blob, fileName }) => {
createDownload(blob, fileName);
- toast.success("导出已开始。");
+ toast.success("Export has started.");
},
onError: (error) => {
- toast.error(error instanceof Error ? error.message : "导出小说失败。");
+ toast.error(error instanceof Error ? error.message : "Failed to export novel.");
},
});
@@ -143,8 +143,8 @@ export default function NovelList() {
error instanceof Error
? error.message
: input.mode === "auto_execute_range"
- ? "继续自动执行当前章节范围失败。"
- : "继续自动导演失败。",
+ ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."
+ : "Continue automatic director failure.",
);
},
});
@@ -219,11 +219,11 @@ export default function NovelList() {
) : novelListQuery.isError ? (
- 加载小说列表失败
- 当前无法读取项目列表,可以重试一次。
+ Failed to load novel list
+ The project list cannot be read at the moment, you can try again.
- void novelListQuery.refetch()}>重新加载
+ void novelListQuery.refetch()}>Reload
) : novels.length === 0 ? (
@@ -269,20 +269,20 @@ export default function NovelList() {
>
{cockpitProjectionQuery.isPending ? (
- 读取这本书的 AI 状态...
-
+ Read the AI status of this book...
+
) : cockpitProjectionQuery.isError ? (
-
无法读取这本书的 AI 状态,请稍后重试。
+
Unable to read the AI status of this book, please try again later.
+ Reading the generated text chapter by chapter is suitable for checking coherence, rhythm and what has been written.
+
- 打开工作区
-
+ Open workspace
+
{activeChapter ? (
- 编辑本章
-
+ Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
+
) : null}
@@ -246,8 +238,8 @@ export default function NovelPreview() {
) : isError ? (
- 加载预览失败
- 当前无法读取小说章节,可以重新加载。
+ Loading preview failed
+ The novel chapter cannot be loaded at the moment and can be reloaded. {
@@ -255,19 +247,19 @@ export default function NovelPreview() {
void chaptersQuery.refetch();
}}
>
- 重新加载
-
+ Reload
+
) : chapters.length === 0 ? (
- 还没有章节
- 先进入小说工作区生成章节目录或正文,再回到这里预览整本内容。
+ No chapter yet
+ First enter the novel workspace to generate a chapter table of contents or text, and then return here to preview the entire content.
- 进入小说工作区
+ Enter the novel workspace
@@ -277,11 +269,11 @@ export default function NovelPreview() {
- 章节目录
-
+ Chapter table of contents
+
- 已生成正文 {generatedChapters.length}/{chapters.length} 章,约 {formatCount(totalWordCount)} 字。
-
+ Text has been generated {generatedChapters.length}/{chapters.length} chapter, approx. {formatCount(totalWordCount)} Character.
+
{chapters.map((chapter) => {
@@ -300,19 +292,19 @@ export default function NovelPreview() {
+ After entering the chapter editing page to generate or rewrite the text, the complete content will be displayed here.
+
{activeChapter ? (
- 编辑本章
+ Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
) : null}
diff --git a/client/src/pages/novels/autoDirector/AutoDirectorCreatePage.tsx b/client/src/pages/novels/autoDirector/AutoDirectorCreatePage.tsx
index 1a9a1abb6..dce6a192a 100644
--- a/client/src/pages/novels/autoDirector/AutoDirectorCreatePage.tsx
+++ b/client/src/pages/novels/autoDirector/AutoDirectorCreatePage.tsx
@@ -107,7 +107,7 @@ export default function AutoDirectorCreatePage() {
}
},
onError: (error) => {
- toast.error(error instanceof Error ? error.message : "恢复自动导演任务失败。");
+ toast.error(error instanceof Error ? error.message : "Failed to resume automatic director task.");
},
});
@@ -158,8 +158,8 @@ export default function AutoDirectorCreatePage() {
candidates: controller.batches.length > 0
? `已生成 ${controller.batches.length} 批方向候选`
: controller.hasActiveDirectorTask
- ? "导演任务进行中"
- : "等待生成方向候选",
+ ? "Director assignment in progress"
+ : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
}), [
controller.batches.length,
controller.directorBasicForm,
@@ -268,13 +268,13 @@ export default function AutoDirectorCreatePage() {
{showSummaryBar ? (
-
AI 自动导演创建
+
AI automatic director creation
- 从一个起始想法开始,AI 完成整本规划准备后,再由你选择正文生产方式。
-
+ Start with a starting idea, and after AI completes the entire planning and preparation, you will then choose the text production method.
+
+ Here we only confirm the basic parameters that affect the reading experience of the entire book. Leave the default when unsure and the AI will continue to base its judgment on your starting thoughts.
+
);
diff --git a/client/src/pages/novels/autoDirector/StageCandidates.tsx b/client/src/pages/novels/autoDirector/StageCandidates.tsx
index 0420c497c..52f8d6211 100644
--- a/client/src/pages/novels/autoDirector/StageCandidates.tsx
+++ b/client/src/pages/novels/autoDirector/StageCandidates.tsx
@@ -51,16 +51,16 @@ export default function StageCandidates({
-
方向候选
+
Direction candidates
- 先选最贴近你想法的一套方向;不满意时再展开调整或生成新一轮。
-
+ First choose a set of directions that is closest to your ideas; if you are not satisfied, make adjustments or create a new round.
+
- 回改设定
-
+ Change settings back
+
{
if (idea.trim()) {
- const confirmed = window.confirm("上方起始想法已有内容。确认使用这条灵感并覆盖原内容吗?");
+ const confirmed = window.confirm("The starting idea above already has content. Are you sure to use this inspiration and overwrite the original content?");
if (!confirmed) {
return;
}
@@ -88,19 +88,19 @@ export default function StageIdea({
className="w-full text-center"
>
- 用一句话,开始你的整本书
-
+ Start your entire book with one sentence
+
- 写下你想看的故事,AI 会先帮你整理成可选择的整本书方向。
-
+ Write down the story you want to read, and AI will help you organize it into optional directions for the entire book.
+
@@ -115,7 +115,7 @@ export default function StageIdea({
className="min-h-[180px] w-full resize-none bg-transparent px-1 py-1 text-base leading-7 text-foreground outline-none placeholder:text-muted-foreground/60 sm:text-lg sm:leading-8"
value={idea}
onChange={(event) => onIdeaChange(event.target.value)}
- placeholder="例如:普通女大学生误入异能组织,一边上学打工,一边调查父亲失踪真相。"
+ placeholder="Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."
/>
- {isGeneratingIdeaInspirations ? "正在准备几个开头..." : "没有想法?看几个开头"}
+ {isGeneratingIdeaInspirations ? "Preparing a few openings..." : "No ideas? Looking at a few openings"}
- {isGenerating ? "生成中..." : "用默认设置直接生成方向"}
+ {isGenerating ? "Generating..." : "Generating direction directly using default settings"}
- 继续完善设定
-
+ Continue to improve settings
+
diff --git a/client/src/pages/novels/autoDirector/StageModelRun.tsx b/client/src/pages/novels/autoDirector/StageModelRun.tsx
index b9347aca6..bc34da51d 100644
--- a/client/src/pages/novels/autoDirector/StageModelRun.tsx
+++ b/client/src/pages/novels/autoDirector/StageModelRun.tsx
@@ -26,14 +26,14 @@ export default function StageModelRun({
-
确认模型与生产准备
+
Confirm model and production preparation
- 确认后,AI 会先给出两套整书方向,再自动完成开写前的角色和卷章准备。
-
+ After confirmation, the AI will first give two sets of directions for the entire book, and then automatically complete the preparation of characters and chapters before writing.
+
- 启动前最后一步
-
+ Last step before starting
+
@@ -41,41 +41,41 @@ export default function StageModelRun({
- 自动导演会这样推进
-
+ The automatic director will advance like this
+
-
1. 选择整书方向
-
AI 生成两套差异明确的方向,由你二选一。
+
1. Select the direction of the book
+
AI generates two sets of clearly different directions, and you can choose one of them.
-
2. 自动准备到可开写
-
系统完成角色、卷战略、拆章和章节执行资源。
+
2. Automatically prepare for writing
+
The system completes characters, volume strategies, chapter splitting, and chapter execution resources.
-
3. 选择正文生产方式
-
简易创作由 AI 写完整本书;专业创作进入完整工作台。
+
3. Select text production method
+
Simple creation allows the entire book to be written by AI; professional creation enters the complete workbench.
- 正文会停在开写前等待你的选择
- 不会提前生成章节正文
+ The text will stop waiting for your choice before starting to write
+ Chapter text will not be generated in advance
-
正文后去 AI 检测与修正
+
Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
- 开启后,章节正文生成完成时会检测 AI 味风险,并在命中可修正问题时生成修订稿。
-
+ When turned on, AI-flavored risks will be detected when chapter text generation is completed, and revised drafts will be generated when correctable problems are hit.
+
+ The selected model at the top is used by default; adjust it if you need to temporarily change the model.
+
@@ -95,9 +95,9 @@ export default function StageModelRun({
- 返回世界与写法
+ Return to the world and writing methods
- {isGenerating ? "生成中..." : "开始生成方向"}
+ {isGenerating ? "Generating..." : "Start generating directions"}
diff --git a/client/src/pages/novels/autoDirector/StageWorldStyle.tsx b/client/src/pages/novels/autoDirector/StageWorldStyle.tsx
index bbbceea98..b367869c2 100644
--- a/client/src/pages/novels/autoDirector/StageWorldStyle.tsx
+++ b/client/src/pages/novels/autoDirector/StageWorldStyle.tsx
@@ -41,28 +41,21 @@ export default function StageWorldStyle({
-
给故事一个世界底色
+
Give the story a world background
- 可以选一个世界样本给 AI 参考,也可以让它根据起始想法自动整理本书世界。写法会作为后续规划和正文的默认语气。
-
-
-
- 可保持默认
+ You can select a world sample for the AI to refer to, or you can let it automatically organize the world in this book based on your starting ideas. The writing method will serve as the default tone for subsequent planning and text.
+
{worldOptions.length > 0
- ? "这里只给自动导演提供快速参考。完整导入、生成和同步请在小说页的“本书世界”中完成。"
- : "没有可选世界样本时,可以先用起始想法开书。"}
+ ? "This is just a quick reference for Auto Director. Please complete the complete import, generation and synchronization in the \"Book World\" on the novel page."
+ : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."}
+ The automatic director will refer to "{selectedWorld.name}” sample world and organize the world constraints that can be used in this book before character preparation.
+
- {selectedStyleSummary?.stageSummaryLines[0] ?? "有沉淀好的写法资产时,建议直接选一套,帮助你更清楚地预期导演会怎样写。"}
+ {selectedStyleSummary?.stageSummaryLines[0] ?? "When you have good writing assets, it is recommended to choose a set directly to help you more clearly anticipate how the director will write."}
- 这套写法会影响后续章节的语气和节奏:{selectedStyleSummary.stageSummaryLines.join(";")}
+ This way of writing will affect the tone and rhythm of subsequent chapters:{selectedStyleSummary.stageSummaryLines.join(";")}
) : null}
- 返回起始设置
- 确认世界与写法
+ Return to initial settings
+ Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
);
diff --git a/client/src/pages/novels/autoDirector/directorCreateStages.ts b/client/src/pages/novels/autoDirector/directorCreateStages.ts
index 06f55251d..fd7a7a62f 100644
--- a/client/src/pages/novels/autoDirector/directorCreateStages.ts
+++ b/client/src/pages/novels/autoDirector/directorCreateStages.ts
@@ -16,11 +16,11 @@ export const AUTO_DIRECTOR_CREATE_STAGES: Array<{
order: number;
label: string;
}> = [
- { key: "idea", order: 0, label: "起始想法" },
- { key: "basic", order: 1, label: "导演起始设置" },
- { key: "world_style", order: 2, label: "世界与写法" },
- { key: "model_run", order: 3, label: "模型与生产准备" },
- { key: "candidates", order: 4, label: "方向与自动准备" },
+ { key: "idea", order: 0, label: "starting idea" },
+ { key: "basic", order: 1, label: "Director's initial settings" },
+ { key: "world_style", order: 2, label: "The world and writing" },
+ { key: "model_run", order: 3, label: "Model and production preparation" },
+ { key: "candidates", order: 4, label: "Orientation and automatic preparation" },
];
function findLabel(options: Array<{ value: string; label: string }>, value: string): string {
@@ -30,7 +30,7 @@ function findLabel(options: Array<{ value: string; label: string }>, value: stri
export function summarizeIdea(idea: string): string {
const normalized = idea.trim().replace(/\s+/g, " ");
if (!normalized) {
- return "等待填写起始想法";
+ return "Waiting to fill in the starting idea";
}
return normalized.length > 42 ? `${normalized.slice(0, 42)}...` : normalized;
}
@@ -57,12 +57,12 @@ export function summarizeWorldStyleStage(input: {
const worldLabel = selectedWorld
? `参考世界:${selectedWorld.name}`
: input.worldSetupMode === "skip"
- ? "暂不使用世界观"
- : "自动生成本书世界";
+ ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."
+ : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
const styleProfile = input.styleProfiles.find((profile) => profile.id === input.styleProfileId);
const styleLabel = styleProfile?.name
?? input.selectedStyleSummary?.headline
- ?? (input.basicForm.styleTone.trim() ? `文风:${input.basicForm.styleTone.trim()}` : "默认写法");
+ ?? (input.basicForm.styleTone.trim() ? `文风:${input.basicForm.styleTone.trim()}` : "Default writing method");
return `${worldLabel} · ${styleLabel}`;
}
@@ -72,5 +72,5 @@ export function summarizeModelRunStage(input: {
postGenerationStyleReviewEnabled: boolean;
}): string {
const runModeLabel = input.runModeOptions.find((option) => option.value === input.runMode)?.label ?? input.runMode;
- return `${runModeLabel} · ${input.postGenerationStyleReviewEnabled ? "正文后检测 AI 味" : "不做正文后 AI 味检测"}`;
+ return `${runModeLabel} · ${input.postGenerationStyleReviewEnabled ? "Detect AI-scented text after post-processing" : "Disable AI-scented text after post-processing"}`;
}
diff --git a/client/src/pages/novels/autoDirector/useAutoDirectorCreateController.ts b/client/src/pages/novels/autoDirector/useAutoDirectorCreateController.ts
index 67789fdd4..1534b02e8 100644
--- a/client/src/pages/novels/autoDirector/useAutoDirectorCreateController.ts
+++ b/client/src/pages/novels/autoDirector/useAutoDirectorCreateController.ts
@@ -212,7 +212,7 @@ export function useAutoDirectorCreateController(input: UseAutoDirectorCreateCont
setIdeaInspirations(response.data?.ideas ?? []);
},
onError: (error) => {
- toast.error(error instanceof Error ? error.message : "生成起始想法失败,请稍后重试。");
+ toast.error(error instanceof Error ? error.message : "Failed to generate starting idea, please try again later.");
},
});
@@ -290,7 +290,7 @@ export function useAutoDirectorCreateController(input: UseAutoDirectorCreateCont
const ensureWorkflowTask = async () => {
const nextIdea = requestIdea;
if (!nextIdea) {
- throw new Error("请先补充起始想法,再继续生成或确认书级方向。");
+ throw new Error("Please add initial ideas before continuing to generate or confirm book-level directions.");
}
if (workflowTaskId) {
return workflowTaskId;
@@ -336,7 +336,7 @@ export function useAutoDirectorCreateController(input: UseAutoDirectorCreateCont
const buildCandidateRequestPayload = (currentWorkflowTaskId: string) => {
if (!requestIdea) {
- throw new Error("请先补充起始想法,再继续生成或确认书级方向。");
+ throw new Error("Please add initial ideas before continuing to generate or confirm book-level directions.");
}
return buildAutoDirectorRequestPayload(
directorBasicForm,
@@ -377,7 +377,7 @@ export function useAutoDirectorCreateController(input: UseAutoDirectorCreateCont
mutationFn: async (payload: { candidate: DirectorCandidate; workflowTaskId?: string }) => {
const currentWorkflowTaskId = payload.workflowTaskId || await ensureWorkflowTask();
if (!requestIdea) {
- throw new Error("请先补充起始想法,再继续生成或确认书级方向。");
+ throw new Error("Please add initial ideas before continuing to generate or confirm book-level directions.");
}
const autoExecutionPlan = buildAutoExecutionPlanForRunMode();
const response = await confirmDirectorCandidate({
@@ -401,8 +401,8 @@ export function useAutoDirectorCreateController(input: UseAutoDirectorCreateCont
onSuccess: async ({ command, workflowTaskId: nextWorkflowTaskId }) => {
if (!command) {
setDialogMode("execution_failed");
- setExecutionError("确认方案失败,未返回导演命令。");
- toast.error("确认方案失败,未返回导演命令。");
+ setExecutionError("Confirmation of the plan failed and the director's command was not returned.");
+ toast.error("Confirmation of the plan failed and the director's command was not returned.");
return;
}
if (nextWorkflowTaskId) {
@@ -418,11 +418,11 @@ export function useAutoDirectorCreateController(input: UseAutoDirectorCreateCont
queryKey: queryKeys.tasks.detail("novel_workflow", nextWorkflowTaskId),
});
}
- toast.success("系统收到书级方向,会创建小说项目并继续推进规划。");
+ toast.success("When the system receives book-level direction, it creates the novel project and moves forward with planning.");
},
onError: async (error, payload) => {
setDialogMode("execution_failed");
- setExecutionError(error instanceof Error ? error.message : "导演任务执行失败。");
+ setExecutionError(error instanceof Error ? error.message : "Director task execution failed.");
setExecutionRequested(false);
if (payload.workflowTaskId) {
await queryClient.invalidateQueries({
@@ -439,7 +439,7 @@ export function useAutoDirectorCreateController(input: UseAutoDirectorCreateCont
mutationFn: async () => {
const taskId = directorTask?.id || workflowTaskId;
if (!taskId) {
- throw new Error("当前没有可继续的自动导演任务。");
+ throw new Error("There are currently no automatic director tasks to continue.");
}
return continueNovelWorkflow(taskId, { continuationMode: "resume" });
},
@@ -468,10 +468,10 @@ export function useAutoDirectorCreateController(input: UseAutoDirectorCreateCont
await Promise.allSettled(invalidations);
setDialogMode("execution_progress");
setExecutionError("");
- toast.success("已确认,AI 会继续推进。");
+ toast.success("It’s confirmed that AI will continue to advance.");
},
onError: (error) => {
- toast.error(error instanceof Error ? error.message : "继续自动导演失败。");
+ toast.error(error instanceof Error ? error.message : "Continue automatic director failure.");
},
});
@@ -508,7 +508,7 @@ export function useAutoDirectorCreateController(input: UseAutoDirectorCreateCont
});
} catch (error) {
confirmSubmitLockedRef.current = false;
- const message = error instanceof Error ? error.message : "创建导演主任务失败。";
+ const message = error instanceof Error ? error.message : "Failed to create director main task.";
setDialogMode("candidate_selection");
setExecutionRequested(false);
setExecutionError(message);
@@ -517,7 +517,7 @@ export function useAutoDirectorCreateController(input: UseAutoDirectorCreateCont
};
const handleBackgroundContinue = () => {
- toast.success("导演任务会继续在后台运行,可在 AI 驾驶舱查看进度。");
+ toast.success("Director tasks will continue to run in the background, and progress can be viewed in the AI cockpit.");
navigate("/");
};
diff --git a/client/src/pages/novels/chapterDetailPlanning.shared.ts b/client/src/pages/novels/chapterDetailPlanning.shared.ts
index ef8b4b5a2..0a4b4d268 100644
--- a/client/src/pages/novels/chapterDetailPlanning.shared.ts
+++ b/client/src/pages/novels/chapterDetailPlanning.shared.ts
@@ -15,9 +15,9 @@ export interface ChapterDetailBatchSelection {
export type ChapterDetailBundleRequest = string | ChapterDetailBatchSelection;
export function detailModeLabel(mode: ChapterDetailMode): string {
- if (mode === "purpose") return "章节目标";
- if (mode === "boundary") return "执行边界";
- return "任务单";
+ if (mode === "purpose") return "Chapter Objectives";
+ if (mode === "boundary") return "execution boundary";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
export function hasChapterDetailDraft(
diff --git a/client/src/pages/novels/chapterPlanning.shared.ts b/client/src/pages/novels/chapterPlanning.shared.ts
index fd51f6a7f..b7924169e 100644
--- a/client/src/pages/novels/chapterPlanning.shared.ts
+++ b/client/src/pages/novels/chapterPlanning.shared.ts
@@ -27,8 +27,8 @@ export function buildReplanRecommendationFromAuditReports(
return {
recommended: blockingIssueIds.length > 0,
reason: blockingIssueIds.length > 0
- ? "存在未解决的高优先级审计问题,建议重规划后续章节。"
- : "当前没有阻塞性审计问题,无需重规划后续章节。",
+ ? "There are unresolved high-priority audit issues, and it is recommended to re-plan subsequent chapters."
+ : "There are currently no blocking audit issues and there is no need to re-plan subsequent chapters.",
blockingIssueIds,
};
}
diff --git a/client/src/pages/novels/components/BasicInfoTab.tsx b/client/src/pages/novels/components/BasicInfoTab.tsx
index 705ffe76e..b91e86f69 100644
--- a/client/src/pages/novels/components/BasicInfoTab.tsx
+++ b/client/src/pages/novels/components/BasicInfoTab.tsx
@@ -11,13 +11,13 @@ export default function BasicInfoTab(props: BasicTabProps) {
return (
diff --git a/client/src/pages/novels/components/BookPayoffLedgerCard.tsx b/client/src/pages/novels/components/BookPayoffLedgerCard.tsx
index d3b652e60..d450f75fc 100644
--- a/client/src/pages/novels/components/BookPayoffLedgerCard.tsx
+++ b/client/src/pages/novels/components/BookPayoffLedgerCard.tsx
@@ -16,19 +16,19 @@ interface BookPayoffLedgerCardProps {
function payoffStatusLabel(status: string): string {
switch (status) {
case "setup":
- return "已埋设";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
case "hinted":
- return "已提示";
+ return "Already prompted";
case "pending_payoff":
- return "待回收";
+ return "To be recycled";
case "paid_off":
- return "已回收";
+ return "Recycled";
case "failed":
- return "已失效";
+ return "Expired";
case "overdue":
- return "已逾期";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
default:
- return status || "未知";
+ return status || "unknown";
}
}
@@ -69,17 +69,17 @@ function formatWindow(item: PayoffLedgerItem): string {
if (typeof item.targetStartChapterOrder === "number") {
return `从第 ${item.targetStartChapterOrder} 章开始`;
}
- return "未限定";
+ return "Unlimited";
}
function scopeLabel(scopeType: PayoffLedgerItem["scopeType"]): string {
if (scopeType === "book") {
- return "全书";
+ return "whole book";
}
if (scopeType === "volume") {
- return "卷级";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
- return "章节";
+ return "chapter";
}
function sourceSummary(item: PayoffLedgerItem): string {
@@ -87,7 +87,7 @@ function sourceSummary(item: PayoffLedgerItem): string {
.map((source) => source.refLabel?.trim())
.filter(Boolean)
.slice(0, 3);
- return labels.length > 0 ? labels.join(" / ") : "暂无来源摘要";
+ return labels.length > 0 ? labels.join(" / ") : "No source summary yet";
}
export default function BookPayoffLedgerCard(props: BookPayoffLedgerCardProps) {
@@ -109,20 +109,20 @@ export default function BookPayoffLedgerCard(props: BookPayoffLedgerCardProps) {
- 待兑现 {ledgerSummary?.pendingCount ?? 0}
+ To be cashed {ledgerSummary?.pendingCount ?? 0}
- 紧急 {ledgerSummary?.urgentCount ?? 0}
+ urgent {ledgerSummary?.urgentCount ?? 0}
- 逾期 {ledgerSummary?.overdueCount ?? 0}
+ Overdue {ledgerSummary?.overdueCount ?? 0}
- 已回收 {ledgerSummary?.paidOffCount ?? 0}
+ Recycled {ledgerSummary?.paidOffCount ?? 0}
>
)}
/>
@@ -131,12 +131,12 @@ export default function BookPayoffLedgerCard(props: BookPayoffLedgerCardProps) {
+ Subsequent planning, writing, review and repair prioritize consuming the canonical results here instead of just focusing on a certain original field.
+
+ There is currently no canonical foreshadowing ledger available. When entering an old project for the first time, the system will lazily synchronize this ledger; if it is still empty, it means that the relevant planning or status materials are not enough.
+
)}
-
全书最新状态快照
+
Snapshot of the latest status of the book
{snapshotForeshadows.length}
- 这里显示的是全书最新状态,不只限当前卷,用来辅助判断整体回收压力。
-
+ What is displayed here is the latest status of the entire book, not just the current volume, to assist in judging the overall recycling pressure.
+
+ There is no Foreshadowing status snapshot available yet. After performing chapter generation or auditing first, the status here will be gradually enriched.
+
)}
diff --git a/client/src/pages/novels/components/ChapterExecutionActionPanel.tsx b/client/src/pages/novels/components/ChapterExecutionActionPanel.tsx
index a95c91f59..2c4ce62bf 100644
--- a/client/src/pages/novels/components/ChapterExecutionActionPanel.tsx
+++ b/client/src/pages/novels/components/ChapterExecutionActionPanel.tsx
@@ -107,8 +107,8 @@ function resolvePrimaryAction(params: {
if (!selectedChapter) {
return {
- label: "请先选择章节",
- reason: "先从左侧选中当前要推进的一章,系统才知道下一步该帮你做什么。",
+ label: "Please select a chapter first",
+ reason: "First select the chapter you want to advance from the left side, and then the system will know what to do for you next.",
variant: "default",
disabled: true,
};
@@ -116,8 +116,8 @@ function resolvePrimaryAction(params: {
if (selectedChapter.chapterStatus === "needs_repair") {
return {
- label: "打开章节编辑器",
- reason: "这章已经有正文。即使审核发现问题,也不应阻塞继续编辑;你可以先进入编辑器,或在下方一键修复。",
+ label: "Open chapter editor",
+ reason: "This chapter already has text. Even if a problem is found in the review, it should not block the continued editing; you can enter the editor first, or fix it with one click below.",
variant: "default",
href: `/novels/${novelId}/chapters/${selectedChapter.id}`,
};
@@ -130,8 +130,8 @@ function resolvePrimaryAction(params: {
|| selectedChapter.generationState === "drafted"
) {
return {
- label: isRunningFullAudit ? "正在运行完整审校..." : "运行完整审校",
- reason: "正文已经出来了,先做完整审校,再决定是修复还是继续改写。",
+ label: isRunningFullAudit ? "Running full review..." : "Run a full review",
+ reason: "The main text has been published. I will review it completely before deciding whether to repair it or continue to rewrite it.",
variant: "default",
ai: true,
onClick: onRunFullAudit,
@@ -141,8 +141,8 @@ function resolvePrimaryAction(params: {
if (selectedChapter.chapterStatus === "unplanned" || !chapterHasPreparationAssets(selectedChapter)) {
return {
- label: isGeneratingChapterPlan ? "正在生成执行计划..." : "先生成执行计划",
- reason: "这章还缺明确目标和任务单,先补执行计划更容易写顺。",
+ label: isGeneratingChapterPlan ? "Generating execution plan..." : "Generate execution plan first",
+ reason: "This chapter still lacks clear goals and task lists. It is easier to write down the execution plan first.",
variant: "default",
ai: true,
onClick: onGenerateChapterPlan,
@@ -152,8 +152,8 @@ function resolvePrimaryAction(params: {
if (!selectedChapter.content?.trim() || selectedChapter.chapterStatus === "pending_generation") {
return {
- label: isSelectedChapterStreaming ? "正在写本章..." : "写本章",
- reason: "准备信息已经够用了,现在最值得做的是直接生成这一章的正文。",
+ label: isSelectedChapterStreaming ? "Writing this chapter..." : "write this chapter",
+ reason: "It's enough to prepare the information. The most worthwhile thing to do now is to directly generate the text of this chapter.",
variant: "default",
ai: true,
onClick: onGenerateSelectedChapter,
@@ -162,8 +162,8 @@ function resolvePrimaryAction(params: {
}
return {
- label: "打开章节编辑器",
- reason: "这一章已经有正文,直接进入编辑器处理细修和恢复会更高效。",
+ label: "Open chapter editor",
+ reason: "This chapter already has text. It would be more efficient to directly enter the editor to handle refinement and recovery.",
variant: "default",
href: `/novels/${novelId}/chapters/${selectedChapter.id}`,
};
@@ -227,8 +227,8 @@ export default function ChapterExecutionActionPanel(props: ChapterExecutionActio
const displayedStatus = selectedChapter ? resolveDisplayedChapterStatus(selectedChapter) : undefined;
const selectedChapterLabel = selectedChapter
- ? `第${selectedChapter.order}章 ${selectedChapter.title || "未命名章节"}`
- : "请选择一个章节";
+ ? `第${selectedChapter.order}章 ${selectedChapter.title || "Unnamed chapter"}`
+ : "Please select a chapter";
const primaryAction = resolvePrimaryAction({
novelId,
@@ -264,26 +264,26 @@ export default function ChapterExecutionActionPanel(props: ChapterExecutionActio
backgroundActivities: backgroundSyncActivities,
});
- const showQuickEditorAction = Boolean(selectedChapter && primaryAction.label !== "打开章节编辑器");
- const showQuickAuditAction = Boolean(selectedChapter && primaryAction.label !== "运行完整审校" && primaryAction.label !== "正在运行完整审校...");
+ const showQuickEditorAction = Boolean(selectedChapter && primaryAction.label !== "Open chapter editor");
+ const showQuickAuditAction = Boolean(selectedChapter && primaryAction.label !== "Run a full review" && primaryAction.label !== "Running full review...");
const showQuickRepairAction = Boolean(
selectedChapter
&& displayedStatus === "needs_repair"
- && primaryAction.label !== "自动修复问题"
- && primaryAction.label !== "正在自动修复...",
+ && primaryAction.label !== "Automatically fix problems"
+ && primaryAction.label !== "Automatically repairing...",
);
return (
- AI 执行台
+ AI execution platform
- 默认只保留当前最推荐的一步。其他动作还在,但都退到下面的折叠区,避免右侧按钮堆满。
-
+ By default, only the currently most recommended step is retained. Other actions are still there, but they have been retreated to the folding area below to avoid cluttering the right buttons.
+
-
当前操作对象
+
Current operation object
{selectedChapterLabel}
{selectedChapter ? (
@@ -301,7 +301,7 @@ export default function ChapterExecutionActionPanel(props: ChapterExecutionActio
-
当前最推荐动作
+
The most recommended actions at the moment
{primaryAction.reason}
@@ -309,121 +309,121 @@ export default function ChapterExecutionActionPanel(props: ChapterExecutionActio
+ When in doubt, keep the default value. Only make manual adjustments if you know for sure that this chapter needs faster pace, more conflict, or more freedom.
+
diff --git a/client/src/pages/novels/components/ChapterExecutionQueueCard.tsx b/client/src/pages/novels/components/ChapterExecutionQueueCard.tsx
index 408ee335f..1d39eceeb 100644
--- a/client/src/pages/novels/components/ChapterExecutionQueueCard.tsx
+++ b/client/src/pages/novels/components/ChapterExecutionQueueCard.tsx
@@ -48,14 +48,14 @@ export default function ChapterExecutionQueueCard(props: ChapterExecutionQueueCa
- 章节队列
+ Chapter queue
- 左侧只负责切章和查看推进状态,把正文阅读区完整留给中间的主写作面板。
-
+ The left side is only responsible for cutting chapters and checking the progress status, leaving the entire text reading area to the main writing panel in the middle.
+
- 当前可见 {chapters.length} 章
- 筛选:{queueFilters.find((item) => item.key === queueFilter)?.label ?? "全部"}
+ currently visible {chapters.length} chapter
+ filter:{queueFilters.find((item) => item.key === queueFilter)?.label ?? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."}
@@ -77,8 +77,8 @@ export default function ChapterExecutionQueueCard(props: ChapterExecutionQueueCa
+ First select a chapter from the left, which will become the main writing area of the current chapter, displaying the main text, task orders, quality feedback and repair records.
+
);
}
const chapterLabel = `第${selectedChapter.order}章`;
- const chapterTitle = selectedChapter.title || "未命名章节";
- const chapterObjective = chapterPlan?.objective ?? selectedChapter.expectation ?? "这一章还没有明确目标,建议先补章节计划。";
+ const chapterTitle = selectedChapter.title || "Unnamed chapter";
+ const chapterObjective = chapterPlan?.objective ?? selectedChapter.expectation ?? "There is no clear goal for this chapter yet, so it is recommended to make up the chapter plan first.";
const savedChapterContent = selectedChapter.content?.trim() ?? "";
const hasSavedChapterContent = hasText(savedChapterContent);
@@ -78,10 +78,10 @@ export default function ChapterExecutionResultPanel(props: ChapterExecutionResul
const hasVisibleLiveWritingOutput = hasText(visibleLiveWritingOutput);
const useLiveWritingPanel = isSelectedChapterStreaming || (!hasSavedChapterContent && hasVisibleLiveWritingOutput);
const contentPanelTitle = isSelectedChapterFinalizing
- ? "章节收尾中"
+ ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."
: useLiveWritingPanel
- ? "实时写作稿"
- : "已保存正文";
+ ? "Real-time writing"
+ : "Text saved";
const contentPanelContent = useLiveWritingPanel
? visibleLiveWritingOutput
: hasSavedChapterContent
@@ -137,8 +137,8 @@ export default function ChapterExecutionResultPanel(props: ChapterExecutionResul
{writingInOtherChapter ? (
) : null}
@@ -147,37 +147,33 @@ export default function ChapterExecutionResultPanel(props: ChapterExecutionResul
- {isSelectedChapterFinalizing
- ? "收尾处理中"
- : isSelectedChapterStreaming
- ? "实时写作中"
- : "已保存版本"}
+ {isSelectedChapterFinalizing ? "Finalizing" : isSelectedChapterStreaming ? "Writing in real time" : "Save version"}
{chapterLabel}
- 当前展示 {contentPanelWordCount} 字
+ Current display {contentPanelWordCount} Character
{chapterTitle}
{contentPanelTitle}。{isSelectedChapterFinalizing
- ? (chapterRunStatus?.message ?? "正文可读,系统正在保存草稿并回灌章节资产。")
+ ? (chapterRunStatus?.message ?? "The text is readable, and the system is saving the draft and reinjecting chapter assets.")
: isSelectedChapterStreaming
- ? "AI 正在持续输出这一章的正文,先在这里观察节奏和手感,不满意时可以随时停止。"
+ ? "AI is continuing to output the text of this chapter. First observe the rhythm and feel here. You can stop at any time if you are not satisfied."
: chapterObjective}
+ There is no text for the current chapter. It is recommended to complete the chapter plan or task list first, and then directly execute "Write this Chapter" from the right side.
+
)}
diff --git a/client/src/pages/novels/components/ChapterExecutionStatusFlow.tsx b/client/src/pages/novels/components/ChapterExecutionStatusFlow.tsx
index 4a66bccb3..2f7df678e 100644
--- a/client/src/pages/novels/components/ChapterExecutionStatusFlow.tsx
+++ b/client/src/pages/novels/components/ChapterExecutionStatusFlow.tsx
@@ -55,8 +55,8 @@ export default function ChapterExecutionStatusFlow(props: ChapterExecutionStatus
- 当前阶段:
- {stages.find((stage) => stage.key === currentStageKey)?.label ?? "未开始"}
+ Current stage:
+ {stages.find((stage) => stage.key === currentStageKey)?.label ?? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."}
{currentStageNote}
diff --git a/client/src/pages/novels/components/ChapterManagementTab.tsx b/client/src/pages/novels/components/ChapterManagementTab.tsx
index dda440c3e..bf838376c 100644
--- a/client/src/pages/novels/components/ChapterManagementTab.tsx
+++ b/client/src/pages/novels/components/ChapterManagementTab.tsx
@@ -126,11 +126,11 @@ export default function ChapterManagementTab(props: ChapterTabViewProps) {
const queueFilters = useMemo(
() => ([
- { key: "all", label: "全部" },
- { key: "setup", label: "待准备" },
- { key: "draft", label: "待写作" },
- { key: "review", label: "待修整" },
- { key: "completed", label: "已完成" },
+ { key: "all", label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
+ { key: "setup", label: "To be prepared" },
+ { key: "draft", label: "To be written" },
+ { key: "review", label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
+ { key: "completed", label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
] as const).map((item) => ({
...item,
count: chapters.filter((chapter) => chapterMatchesQueueFilter(chapter, item.key)).length,
@@ -141,21 +141,21 @@ export default function ChapterManagementTab(props: ChapterTabViewProps) {
return (
- 章节执行
+ Chapter execution
- 把这里收成真正的主工作台:左侧只管切章,中间完整承接正文,右侧专心放 AI 动作和策略。
-
+ Turn this place into the real main workbench: the left side is just for cutting chapters, the middle is the complete text, and the right side is dedicated to AI actions and strategies.
+
- {isCreatingChapter ? "创建中..." : "新建章节"}
+ {isCreatingChapter ? "Creating..." : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."}
- 请先添加至少 1 个角色,再生成章节内容。这样 AI 更容易识别出场者、关系变化和情节承接。
- 去角色管理
+ Please add at least 1 character before generating chapter content. This makes it easier for AI to identify players, relationship changes, and plot continuation.
+ Go to character management
) : null}
@@ -228,9 +228,9 @@ export default function ChapterManagementTab(props: ChapterTabViewProps) {
className="flex h-full min-h-0 flex-col"
>
- 动态栏
- 资料诊断
- AI 执行台
+ Dynamic Bar
+ Data diagnosis
+ AI execution platform
- 长度控制与执行回放
+ Length control and execution playback
{lengthControl ? (
<>
{lengthControl.softMinWordCount} - {lengthControl.softMaxWordCount} Character
+
hard cap {lengthControl.hardMaxWordCount} Character
-
执行信号
-
硬停 {lengthControl.hardStopsTriggered} 次
+
execution signal
+
hard stop {lengthControl.hardStopsTriggered} Second-rate
scene {lengthControl.generatedSceneCount}/{lengthControl.plannedSceneCount}
@@ -184,10 +184,10 @@ export function ChapterRuntimeLengthCard(props: {
{lengthControl.lengthRepairPath.length > 0
? lengthControl.lengthRepairPath.join(" -> ")
- : "本次未触发额外长度修整。"}
+ : "No additional length trimming was triggered this time."}
- {lengthControl.overlengthRepairApplied ? "本次触发过超长修整。" : "本次未触发超长修整。"}
+ {lengthControl.overlengthRepairApplied ? "Extra long trimming was triggered this time." : "No extra-long trimming was triggered this time."}
@@ -197,22 +197,22 @@ export function ChapterRuntimeLengthCard(props: {
Scene {scene.sceneIndex}
- {scene.actualWordCount} 字
+ {scene.actualWordCount} Character{buildWordControlModeLabel(scene.wordControlMode)}{scene.sceneStatus}
- 轮次 {scene.roundCount},硬停 {scene.hardStopCount} 次
- {scene.closingPhaseTriggered ? ",包含收尾区控制" : ""}
+ rounds {scene.roundCount}, hard stop {scene.hardStopCount} Second-rate
+ {scene.closingPhaseTriggered ? ", including the closing area control" : ""}
+ First select a role on the left, then enter Archives, Exhibits, Resources, Timeline and Relationship Maintenance.
+
) : (
= {
- protagonist: "主角",
- antagonist: "主对手",
- ally: "同盟",
- foil: "镜像角色",
- mentor: "导师",
- love_interest: "情感牵引",
- pressure_source: "压力源",
- catalyst: "催化者",
+ protagonist: "main character",
+ antagonist: "main opponent",
+ ally: "alliance",
+ foil: "mirror role",
+ mentor: "tutor",
+ love_interest: "emotional pull",
+ pressure_source: "stressor",
+ catalyst: "Catalyst",
};
const CHARACTER_GENDER_LABELS: Record = {
- male: "男",
- female: "女",
- other: "其他",
- unknown: "未知",
+ male: "male",
+ female: "female",
+ other: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ unknown: "unknown",
};
function getCastRoleLabel(castRole?: CharacterCastRole | null): string {
if (!castRole) {
- return "未分类";
+ return "Uncategorized";
}
return CAST_ROLE_LABELS[castRole] ?? castRole;
}
function getCharacterGenderLabel(gender?: CharacterGender | null): string {
if (!gender) {
- return "未知";
+ return "unknown";
}
return CHARACTER_GENDER_LABELS[gender] ?? gender;
}
@@ -81,7 +81,7 @@ function buildCharacterCastApplyConfirmMessage(option: CharacterCastOption, warn
return [
`阵容「${option.title}」和当前故事设定还有不完全匹配的地方。`,
warningText,
- "仍然应用到角色资产工作台吗?应用后可以继续在角色资产里调整。",
+ "Still applied to the Character Asset Workbench? After application, you can continue to adjust it in the character assets.",
].filter((line) => line.trim().length > 0).join("\n\n");
}
@@ -161,7 +161,7 @@ export default function CharacterCastOptionsSection(props: CharacterCastOptionsS
function handleRejectAll() {
const confirmed = window.confirm(
appliedOption
- ? "确认清空当前所有阵容方案记录?已同步的角色与关系不会自动回滚。"
+ ? "Confirm to clear all current lineup plan records? Synchronized roles and relationships will not be automatically rolled back."
: `确认清空当前 ${castOptions.length} 套阵容方案?`,
);
if (!confirmed) {
@@ -195,12 +195,12 @@ export default function CharacterCastOptionsSection(props: CharacterCastOptionsS
: undefined,
}),
onSuccess: async (response) => {
- setStatusMessage(response.message ?? "角色阵容方案已生成。");
+ setStatusMessage(response.message ?? "The character lineup plan has been generated.");
setIsPlannerExpanded(true);
await refreshCastOptions();
},
onError: (error) => {
- setStatusMessage(error instanceof Error ? error.message : "角色阵容方案生成失败。");
+ setStatusMessage(error instanceof Error ? error.message : "Character lineup plan generation failed.");
},
});
@@ -220,7 +220,7 @@ export default function CharacterCastOptionsSection(props: CharacterCastOptionsS
}
const createdCount = response.data?.createdCount ?? 0;
const updatedCount = response.data?.updatedCount ?? 0;
- const backgroundHint = "外显资料和角色动态会在后台补齐,稍后刷新角色资产即可查看。";
+ const backgroundHint = "Explicit information and character dynamics will be completed in the background, and you can view them by refreshing the character assets later.";
setStatusMessage(
response.data?.qualityOverrideApplied
? `已按你的确认应用这套阵容,同步 ${createdCount} 个新角色,更新 ${updatedCount} 个既有角色。${backgroundHint}`
@@ -230,7 +230,7 @@ export default function CharacterCastOptionsSection(props: CharacterCastOptionsS
await refreshAppliedCharacterWorkspace();
},
onError: (error) => {
- setStatusMessage(error instanceof Error ? error.message : "角色阵容方案应用失败。");
+ setStatusMessage(error instanceof Error ? error.message : "Role lineup scheme application failed.");
},
});
@@ -251,14 +251,14 @@ export default function CharacterCastOptionsSection(props: CharacterCastOptionsS
mutationFn: (optionId: string) => deleteCharacterCastOption(novelId, optionId),
onSuccess: async (response) => {
if (response.data?.deletedAppliedOption) {
- setStatusMessage("方案记录已删除;角色库和关系网中的对应数据会保留。");
+ setStatusMessage("The plan record has been deleted; the corresponding data in the role library and relationship network will be retained.");
} else {
- setStatusMessage("这套阵容方案已删除。");
+ setStatusMessage("This lineup plan has been deleted.");
}
await refreshCastOptions();
},
onError: (error) => {
- setStatusMessage(error instanceof Error ? error.message : "删除阵容方案失败。");
+ setStatusMessage(error instanceof Error ? error.message : "Failed to delete lineup plan.");
},
});
@@ -268,7 +268,7 @@ export default function CharacterCastOptionsSection(props: CharacterCastOptionsS
const deletedCount = response.data?.deletedCount ?? 0;
const deletedAppliedCount = response.data?.deletedAppliedCount ?? 0;
if (deletedCount === 0) {
- setStatusMessage("没有可清空的阵容方案。");
+ setStatusMessage("There are no lineup plans to clear.");
} else if (deletedAppliedCount > 0) {
setStatusMessage(`已清空 ${deletedCount} 套阵容方案记录;已同步的角色与关系不会自动回滚。`);
} else {
@@ -278,7 +278,7 @@ export default function CharacterCastOptionsSection(props: CharacterCastOptionsS
await refreshCastOptions();
},
onError: (error) => {
- setStatusMessage(error instanceof Error ? error.message : "清空阵容方案失败。");
+ setStatusMessage(error instanceof Error ? error.message : "The plan to clear the lineup failed.");
},
});
const isWorking =
@@ -293,15 +293,15 @@ export default function CharacterCastOptionsSection(props: CharacterCastOptionsS
- AI 角色阵容方案
+ AI character lineup plan
- 更适合前期搭建角色系统,或在故事方向大改后重新规划阵容。
-
+ It is more suitable for building a character system in the early stage, or re-planning the lineup after a major change in the direction of the story.
+
- {castOptions.length} 套候选方案
- {relations.length} 条角色关系
- {appliedOption ? 已应用方案 : null}
+ {castOptions.length} set of candidates
+ {relations.length} role relationship
+ {appliedOption ? Applied scheme : null}
@@ -311,23 +311,23 @@ export default function CharacterCastOptionsSection(props: CharacterCastOptionsS
+ It can supplement the protagonist's desire, opponent's pressure, relationship tension, or the character direction you want to focus on strengthening.
+
setStoryInput(event.target.value)}
/>
@@ -353,8 +353,8 @@ export default function CharacterCastOptionsSection(props: CharacterCastOptionsS
checked={useWorldContext}
onChange={(event) => setUseWorldContext(event.target.checked)}
/>
- 基于本书世界生成
-
+ Generate a world based on this book
+
{useWorldContext ? (
setForceWorldCompliance(event.target.checked)}
/>
- 检查世界规则合规
-
+ Check World Rules Compliance
+
) : null}
{useWorldContext ? (
{worldSliceQuery.isLoading ? (
-
正在读取本书世界使用范围...
+
Reading this book world usage scope...
) : !hasUsableWorld ? (
- 本书世界还没有准备好。本轮会优先根据书级信息和你的生成指令设计角色。
-
+ The book world is not ready yet. In this round, priority will be given to character design based on book-level information and your generation instructions.
+
) : !hasWorldSlice ? (
- 本书世界存在,但使用范围还未整理。建议先到基础信息页整理本书使用范围,或继续让 AI 按世界手册保守生成。
-
+ The world of this book exists, but its scope of use has not yet been sorted out. It is recommended to first go to the basic information page to sort out the scope of use of this book, or continue to let the AI generate it conservatively according to the world manual.
+
) : null}
- 势力倾向
+ sectarian tendencies setPreferredWorldFaction(event.target.value)}
disabled={!hasWorldSlice || activeWorldForces.length === 0}
>
-
{hasWorldSlice
- ? "角色会优先贴合本书世界的势力、地点、身份边界和禁止搭配。"
- : "本书世界使用范围整理后,可进一步指定势力倾向。"}
+ ? "Characters will give priority to the forces, locations, identity boundaries and prohibited combinations in the world of this book."
+ : "After the world usage scope of this book is sorted out, the influence tendency can be further specified."}
+ After applying a certain lineup, characters will be created/updated simultaneously and the character asset workbench will be refreshed.
+
{statusMessage ? (
+ There is no lineup plan yet. First enter a bit of character direction, then click "Generate 3 lineups".
+
)}
>
@@ -537,26 +537,26 @@ export default function CharacterCastOptionsSection(props: CharacterCastOptionsS
- 角色关系网
+ role network
{selectedCharacter ? (
- 当前聚焦:{selectedCharacter.name}({selectedCharacter.role || "未定义"})
+ Current focus:{selectedCharacter.name}({selectedCharacter.role || "undefined"})
) : (
-
未选中角色时,默认展示最近的关系条目。
+
When the role is not selected, the most recent relationship entries are displayed by default.
+ Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
+
)}
diff --git a/client/src/pages/novels/components/CharacterDynamicsSection.tsx b/client/src/pages/novels/components/CharacterDynamicsSection.tsx
index e3bb37cf0..38c288674 100644
--- a/client/src/pages/novels/components/CharacterDynamicsSection.tsx
+++ b/client/src/pages/novels/components/CharacterDynamicsSection.tsx
@@ -126,7 +126,7 @@ export default function CharacterDynamicsSection(props: CharacterDynamicsSection
const manualStateMutation = useMutation({
mutationFn: () => {
if (!selectedCharacterId) {
- throw new Error("请先选择一个角色。");
+ throw new Error("Please select a character first.");
}
return updateCharacterDynamicState(novelId, selectedCharacterId, {
currentState: manualState.currentState.trim() || undefined,
@@ -147,21 +147,21 @@ export default function CharacterDynamicsSection(props: CharacterDynamicsSection
- 动态角色系统
+ Dynamic character system
- 这里把卷级职责、缺席风险、新角色候选和关系阶段放回角色页主流程,不再依赖你自己手工追踪。
-
+ Here, volume-level responsibilities, absence risks, new role candidates and relationship stages are returned to the main process of the role page, and you no longer rely on you to track them manually.
+
+ There are no confirmed candidates for the new role. After writing a few chapters, the new character entries extracted by AI will be automatically summarized here.
+
)
) : null}
@@ -330,17 +330,17 @@ export default function CharacterDynamicsSection(props: CharacterDynamicsSection
+ There is currently no relationship stage data. This will appear automatically after applying a lineup or completing a chapter.
+
)
) : null}
@@ -357,16 +357,16 @@ export default function CharacterDynamicsSection(props: CharacterDynamicsSection
{assignment?.roleLabel || item.role}
- {assignment?.isCore ? 本卷核心 : null}
+ {assignment?.isCore ? Core of this volume : null}
- {item.absenceRisk === "none" ? "无缺席风险" : `缺席 ${item.absenceSpan} 章`}
+ {item.absenceRisk === "none" ? "No risk of absence" : `缺席 ${item.absenceSpan} 章`}
- } label="身份" value={selectedCharacter.role || "未定义"} />
- } label="状态" value={selectedCharacter.currentState || "待补全"} />
- } label="最近出场" value={lastAppearanceChapter ? `第${lastAppearanceChapter}章` : "暂无"} />
+ } label="identity" value={selectedCharacter.role || "undefined"} />
+ } label="Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." value={selectedCharacter.currentState || "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."} />
+ } label="recent appearances" value={lastAppearanceChapter ? `第${lastAppearanceChapter}章` : "None yet"} />
-
故事作用
+
Story function
- {selectedCharacter.storyFunction || "待补全"}
+ {selectedCharacter.storyFunction || "To be completed"}
-
-
+
+
diff --git a/client/src/pages/novels/components/CollapsibleSummary.tsx b/client/src/pages/novels/components/CollapsibleSummary.tsx
index a21dd9910..7f3fa8430 100644
--- a/client/src/pages/novels/components/CollapsibleSummary.tsx
+++ b/client/src/pages/novels/components/CollapsibleSummary.tsx
@@ -17,8 +17,8 @@ export default function CollapsibleSummary(props: CollapsibleSummaryProps) {
description,
meta,
className,
- collapsedLabel = "展开查看",
- expandedLabel = "收起内容",
+ collapsedLabel = "Expand to view",
+ expandedLabel = "Collapse content",
} = props;
return (
diff --git a/client/src/pages/novels/components/DirectorFactDebugDialog.tsx b/client/src/pages/novels/components/DirectorFactDebugDialog.tsx
index b0378fb18..3461b56ac 100644
--- a/client/src/pages/novels/components/DirectorFactDebugDialog.tsx
+++ b/client/src/pages/novels/components/DirectorFactDebugDialog.tsx
@@ -16,45 +16,7 @@ import {
} from "@/components/ui/dialog";
function formatPercent(ratio: number): string {
- return `${Math.max(0, Math.min(100, Math.round(ratio * 100)))}%`;
-}
-
-function formatStageLabel(stage: string): string {
- if (stage === "candidate_selection") return "开书方向";
- if (stage === "candidate_confirm") return "创建项目";
- if (stage === "story_macro") return "故事宏观规划";
- if (stage === "book_contract") return "书级创作约定";
- if (stage === "character_setup") return "角色准备";
- if (stage === "volume_strategy") return "卷规划";
- if (stage === "structured_outline") return "节奏与拆章";
- if (stage === "chapter_execution") return "正文生成";
- if (stage === "quality_repair") return "质量闭环";
- if (stage === "takeover") return "接手已有项目";
- return stage;
-}
-
-function formatNextAction(action?: string | null): string {
- if (!action) return "当前没有额外动作建议";
- if (action === "run_chapter_detail_generation") return "继续细化剩余章节任务单";
- if (action === "run_chapter_list_generation") return "继续补齐卷拆章列表";
- if (action === "sync_execution_contracts") return "同步章节执行合同";
- const text = action
- .replace(/_/g, " ")
- .replace(/\./g, " ")
- .trim();
- return text || action;
-}
-
-function formatResumeFrom(resumeFrom?: string | null): string {
- if (!resumeFrom) return "按当前现场重新判断";
- if (resumeFrom === "chapter_detail_bundle") return "从剩余未细化章节继续";
- if (resumeFrom === "chapter_list") return "从卷拆章列表继续";
- if (resumeFrom === "beat_sheet") return "从卷节奏板继续";
- if (resumeFrom.startsWith("chapter:")) {
- const rawOrder = resumeFrom.slice("chapter:".length).trim();
- const order = Number(rawOrder);
- if (Number.isFinite(order) && order > 0) {
- return `第 ${order} 章`;
+ return `${Math.max(0, Math.min(100, Math.round(ratio * 100)))}%`; } function formatStageLabel(stage: string): string { if (stage === "candidate_selection") return "Book selection direction"; if (stage === "candidate_confirm") return "Project creation"; if (stage === "story_macro") return "Story macro planning"; if (stage === "book_contract") return "Book-level creation conventions"; if (stage === "character_setup") return "Character preparation"; if (stage === "volume_strategy") return "Volume planning"; if (stage === "structured_outline") return "Rhythm and chapter splitting"; if (stage === "chapter_execution") return "Text generation"; if (stage === "quality_repair") return "Quality closed loop"; if (stage === "takeover") return "Taking over an existing project"; return stage; } function formatNextAction(action?: string | null): string { if (!action) return "No additional action suggestions at present"; if (action === "run_chapter_detail_generation") return "Continue to refine the remaining chapter task list"; if (action === "run_chapter_list_generation") return "Continue to complete the volume chapter list"; if (action === "sync_execution_contracts") return "Synchronize the chapter execution contract"; const text = action .replace(/_/g, " ") .replace(/\./g, " ") .trim(); return text || action; } function formatResumeFrom(resumeFrom?: string | null): string { if (!resumeFrom) return "Re-evaluate based on the current situation"; if (resumeFrom === "chapter_detail_bundle") return "Continue from the remaining unrefined chapters"; if (resumeFrom === "chapter_list") return "Continue from the volume chapter list"; if (resumeFrom === "beat_sheet") return "Continue from the volume rhythm sheet"; if (resumeFrom.startsWith("chapter:")) { const rawOrder = resumeFrom.slice("chapter:".length).trim(); const order = Number(rawOrder); if (Number.isFinite(order) && order > 0) { return `第 ${order} 章`;
}
}
return resumeFrom.replace(/_/g, " ").trim() || resumeFrom;
@@ -68,35 +30,35 @@ function summarizeStep(step: DirectorTaskFactInspectionStep): {
if (step.inspectError) {
return {
tone: "error",
- title: "检查没有完成",
+ title: "Check not completed",
detail: step.inspectError,
};
}
if (step.completed) {
return {
tone: "done",
- title: "已确认完成",
- detail: "系统已经找到这一步对应的真实产出,可以直接复用。",
+ title: "Confirmed completed",
+ detail: "The system has found the real output corresponding to this step and can be reused directly.",
};
}
if (!step.ready) {
return {
tone: "blocked",
- title: "还不能执行",
- detail: step.blockers[0]?.reason || "上游事实还没补齐,所以这一步暂时不能开始。",
+ title: "Can't be executed yet",
+ detail: step.blockers[0]?.reason || "The upstream facts have not been completed yet, so this step cannot be started yet.",
};
}
if (step.isCurrentFactStep) {
return {
tone: "current",
- title: "当前优先补这一段",
- detail: step.progress?.label || "这是系统根据现有事实判断出的下一段主处理步骤。",
+ title: "Currently, priority is given to completing this section",
+ detail: step.progress?.label || "This is the next main processing step determined by the system based on existing facts.",
};
}
return {
tone: "working",
- title: "还没闭环",
- detail: step.progress?.label || "这一步已经具备执行条件,但事实还没有完全闭环。",
+ title: "Not closed yet",
+ detail: step.progress?.label || "This step already has the conditions for execution, but the fact is that the loop is not completely closed yet.",
};
}
@@ -119,8 +81,8 @@ function StepFactCard({ step }: { step: DirectorTaskFactInspectionStep }) {
{formatStageLabel(step.stage)}
- {step.isCurrentFactStep ? 当前判断会先处理这里 : null}
- {step.isActiveRuntimeStep ? 后台此刻正在碰这一步 : null}
+ {step.isCurrentFactStep ? The current judgment will be processed here first : null}
+ {step.isActiveRuntimeStep ? This step is happening in the background right now : null}
{summary.title}
@@ -214,29 +176,29 @@ export default function DirectorFactDebugDialog(input: {
- 调试检查
-
+ debug check
+
- 导演步骤完整度检查
+ Director step completeness check
- 这里展示的是每一步基于真实产出的检查结果。你可以直接看到哪一步已经有结果、哪一步缺前置条件、系统现在准备先补哪里。
-
+ Shown here are the inspection results of each step based on real output. You can directly see which step has results, which step lacks preconditions, and where the system is going to make up for it first.
+
- 已确认完成 {summary.completedCount}/{inspection?.steps.length ?? 0}
+ Confirmed completed {summary.completedCount}/{inspection?.steps.length ?? 0}
0 ? "destructive" : "outline"}>
- 还需补前置条件 {summary.blockedCount}
+ Prerequisites need to be supplemented {summary.blockedCount}
{summary.currentStep ? (
- 当前先看 {summary.currentStep.label}
+ Watch first now {summary.currentStep.label}
) : null}
+ Reading the integrity check results of the current director chain...
+
) : query.isError ? (
- 无法完成这次检查。{query.error instanceof Error ? query.error.message : "请稍后重试。"}
+ This check cannot be completed.{query.error instanceof Error ? query.error.message : "Please try again later."}
) : !inspection ? (
- 当前还没有可检查的导演任务。先启动或接手一次 AI 导演流程,这里才会出现逐步骤检查结果。
-
+ There are currently no director tasks to check. Start or take over the AI director process first, and then the step-by-step inspection results will appear here.
+
) : (
@@ -277,11 +239,11 @@ export default function DirectorFactDebugDialog(input: {
- 当前系统会先补这一段
-
+ The current system will fill in this section first.
+
-
+ Some steps of inspection did not yield complete results. Usually this is because the current mission scene is incomplete, or more factual sources are needed for this paragraph.
+
) : null}
)}
diff --git a/client/src/pages/novels/components/DirectorTakeoverEntryPanel.tsx b/client/src/pages/novels/components/DirectorTakeoverEntryPanel.tsx
index 8ae3aa9ab..337c79c3f 100644
--- a/client/src/pages/novels/components/DirectorTakeoverEntryPanel.tsx
+++ b/client/src/pages/novels/components/DirectorTakeoverEntryPanel.tsx
@@ -25,8 +25,8 @@ export default function DirectorTakeoverEntryPanel({
{entry}
- 接管前会先读取当前项目真实进度,并明确告诉你这次会跳过、继续还是重跑哪些步骤。
-
+ Before taking over, the actual progress of the current project will be read and you will be told exactly which steps you will skip, continue or rerun this time.
+
@@ -215,8 +215,8 @@ export default function NovelAutoDirectorCandidateBatches(props: NovelAutoDirect
- 展开完整设定
-
+ Expand full settings
+
{renderSecondaryCandidateDetails(candidate).map((item) => (
@@ -230,12 +230,12 @@ export default function NovelAutoDirectorCandidateBatches(props: NovelAutoDirect
- 调整书名与方向
-
+ Adjust book title and direction
+
-
可选书名
+
Optional book title
{titleOptions.map((option) => {
const active = option.title === candidate.workingTitle;
@@ -257,7 +257,7 @@ export default function NovelAutoDirectorCandidateBatches(props: NovelAutoDirect
- {option.reason?.trim() || option.angle || "可直接作为这套方向的书名。"}
+ {option.reason?.trim() || option.angle || "It can be directly used as the title of this set of books."}
@@ -273,13 +273,13 @@ export default function NovelAutoDirectorCandidateBatches(props: NovelAutoDirect
- 重做标题组
-
+ Redo title group
+
onTitlePatchFeedbackChange(candidate.id, event.target.value)}
- placeholder="例如:更偏都市冷感,不要像旧式升级文。"
+ placeholder="For example: It is more urban and cool, not like an old-style upgrade article."
/>
@@ -299,13 +299,13 @@ export default function NovelAutoDirectorCandidateBatches(props: NovelAutoDirect
- 调整方向
-
+ Adjust direction
+
onCandidatePatchFeedbackChange(candidate.id, event.target.value)}
- placeholder="例如:保留这套,但主角更主动一点。"
+ placeholder="For example: keep this set, but make the protagonist more proactive."
/>
+ Click a few correction directions and add a sentence about the feeling you want. The system will retain the previous round and generate a new batch of solutions.
+
- 再补一句修正建议
-
+ One more suggestion for correction
+
onFeedbackChange(event.target.value)}
- placeholder="例如:我想要女频成长感更强一点,别太像纯爱文,也不要太黑。"
+ placeholder="For example: I want the female channel to have a stronger sense of growth, not to be too pure love story, and not too dark."
/>
@@ -377,7 +377,7 @@ export default function NovelAutoDirectorCandidateBatches(props: NovelAutoDirect
disabled={isGenerating}
>
- {isGenerating ? "生成中..." : "带修正建议继续生成"}
+ {isGenerating ? "Generating..." : "Continuing to generate with correction suggestions"}
diff --git a/client/src/pages/novels/components/NovelAutoDirectorDialog.shared.ts b/client/src/pages/novels/components/NovelAutoDirectorDialog.shared.ts
index 4394ba4ed..76285b02a 100644
--- a/client/src/pages/novels/components/NovelAutoDirectorDialog.shared.ts
+++ b/client/src/pages/novels/components/NovelAutoDirectorDialog.shared.ts
@@ -14,10 +14,10 @@ export interface DirectorRunModeOption {
export const RUN_MODE_OPTIONS: DirectorRunModeOption[] = [
{
value: "auto_to_ready",
- label: "先完成导演准备",
- description: "AI 会准备书级规划、角色、卷章安排和章节执行资源,再由你选择简易生产或专业生产。",
+ label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ description: "The AI will prepare book-level planning, roles, chapter arrangements, and chapter execution resources, and then you can choose between simple production or professional production.",
recommended: true,
- recommendation: "正文不会提前生成,准备完成后再决定如何生产整本书。",
+ recommendation: "The text will not be generated in advance, and the decision on how to produce the entire book will be made once preparation is complete.",
},
];
diff --git a/client/src/pages/novels/components/NovelAutoDirectorIdeaInspirationPanel.tsx b/client/src/pages/novels/components/NovelAutoDirectorIdeaInspirationPanel.tsx
index 85ea7ee16..069712d55 100644
--- a/client/src/pages/novels/components/NovelAutoDirectorIdeaInspirationPanel.tsx
+++ b/client/src/pages/novels/components/NovelAutoDirectorIdeaInspirationPanel.tsx
@@ -23,11 +23,11 @@ export default function NovelAutoDirectorIdeaInspirationPanel({
- 这些只是临时灵感,使用后仍可继续改。
-
+ These are temporary inspirations only and can be modified after use.
+
- {"\u4efb\u52a1\u8fd0\u884c\u540e\u4f1a\u5728\u8fd9\u91cc\u5199\u5165\u8fdb\u5c55\u8bb0\u5f55\u3002"}
+ {"After the task is run, a progress record will be written here."}
)}
{styleSeed ? (
-
当前命中写法
+
Current hit writing
{styleSeed.title}
{styleSeed.summaryLines.length > 0 ? (
-
本阶段仅生效的写法摘要
+
Only the writing summary that is effective at this stage
{"isCurrent" in step
? (step.status === "attention"
- ? "\u9700\u5904\u7406"
+ ? "Need processing"
: step.status === "running"
- ? "\u8fdb\u884c\u4e2d"
+ ? "in progress"
: step.status === "completed"
- ? "\u5df2\u5b8c\u6210"
- : "\u5f85\u63a8\u8fdb")
+ ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."
+ : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.")
: formatStepStatus(step.status)}
))}
@@ -623,7 +623,7 @@ export default function NovelTaskDrawer({
-
里程碑历史
+
Milestone History
{milestones.length > 0 ? (
{milestones
@@ -633,38 +633,38 @@ export default function NovelTaskDrawer({
{formatCheckpoint(milestone.checkpointType)}
{milestone.summary}
-
记录时间:{formatDate(milestone.createdAt)}
+
Recording time:{formatDate(milestone.createdAt)}
))}
) : (
- 当前还没有可显示的里程碑记录。
-
+ There are currently no milestone records to display.
+
)}
>
) : (
- 当前小说还没有可见的自动导演任务。你可以继续手动创作,或在后台任务中心查看其他任务。
-
+ There are currently no automatic director tasks visible for the novel. You can continue creating manually or view other tasks in the background task center.
+
)}
{primaryAction ? (
handleProjectionAction(primaryAction)}>
- {primaryActionLabel || "继续处理"}
+ {primaryActionLabel || "Continue processing"}
) : null}
{task?.sourceRoute ? (
- 打开来源页面
+ Open source page
) : null}
- 打开后台任务中心
-
+ Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
+
diff --git a/client/src/pages/novels/components/NovelWorkflowRunningIndicator.tsx b/client/src/pages/novels/components/NovelWorkflowRunningIndicator.tsx
index bc3b62f9b..cb42f8bd4 100644
--- a/client/src/pages/novels/components/NovelWorkflowRunningIndicator.tsx
+++ b/client/src/pages/novels/components/NovelWorkflowRunningIndicator.tsx
@@ -11,7 +11,7 @@ export default function NovelWorkflowRunningIndicator(props: NovelWorkflowRunnin
const {
progress,
className,
- label = "AI 正在后台持续推进",
+ label = "AI is continuously advancing in the background.",
} = props;
const percent = normalizeProgressPercent(progress);
diff --git a/client/src/pages/novels/components/NovelWorldManagerCard.tsx b/client/src/pages/novels/components/NovelWorldManagerCard.tsx
index 7cde9b203..d42d5b8df 100644
--- a/client/src/pages/novels/components/NovelWorldManagerCard.tsx
+++ b/client/src/pages/novels/components/NovelWorldManagerCard.tsx
@@ -47,43 +47,43 @@ interface NovelWorldManagerCardProps {
function labelSourceType(sourceType: string | null | undefined): string {
switch (sourceType) {
case "imported":
- return "来自世界库";
+ return "from world library";
case "generated":
- return "根据本书生成";
+ return "Generated based on this book";
case "manual":
- return "自定义世界";
+ return "Custom world";
default:
- return "未设置";
+ return "not set";
}
}
function labelSyncDirection(direction: string | null | undefined): string {
switch (direction) {
case "push":
- return "只推送到世界库";
+ return "Only push to world library";
case "pull":
- return "只从世界库拉取";
+ return "Only pulled from the world library";
case "bidirectional":
- return "可双向同步";
+ return "Can be synchronized in both directions";
default:
- return "不同步";
+ return "Out of sync";
}
}
function sectionLabel(section: string): string {
switch (section) {
case "profile":
- return "世界概要";
+ return "world summary";
case "rules":
- return "核心规则";
+ return "core rules";
case "factions":
- return "阵营";
+ return "camp";
case "forces":
- return "势力";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
case "locations":
- return "地点";
+ return "Place";
case "relations":
- return "关系网络";
+ return "Relationship network";
default:
return section;
}
@@ -137,7 +137,7 @@ function WorldSignal(props: {
function GenerationChain() {
return (
- {["本书世界", "角色", "大纲", "章节"].map((item, index, array) => (
+ {["book world", "Role", "outline", "chapter"].map((item, index, array) => (
{item}
{index < array.length - 1 ? : null}
@@ -175,18 +175,18 @@ export default function NovelWorldManagerCard(props: NovelWorldManagerCardProps)
const activeWorldName = useMemo(() => {
const id = novelWorld?.sourceWorldId ?? props.selectedWorldId;
- return props.worldOptions.find((item) => item.id === id)?.name ?? novelWorld?.title ?? "未选择世界";
+ return props.worldOptions.find((item) => item.id === id)?.name ?? novelWorld?.title ?? "No world selected";
}, [novelWorld?.sourceWorldId, novelWorld?.title, props.selectedWorldId, props.worldOptions]);
const writingStatus = novelWorld
? novelWorld.hasStorySlice
- ? "写作范围已整理"
- : "需要整理本书可用范围"
- : "还未建立本书世界";
+ ? "The writing scope has been organized"
+ : "Need to sort out the available scope of this book"
+ : "The world of this book has not been established yet";
const syncStatus = novelWorld?.syncEnabled
? labelSyncDirection(novelWorld.syncDirection)
: novelWorld?.sourceWorldId
- ? "保留为本书副本"
- : "本书内部使用";
+ ? "Keep a copy of this book"
+ : "For internal use of this book";
const lastSyncedAtText = formatSyncTime(novelWorld?.lastSyncedAt);
const pendingSections = syncDiff?.differences.length
? syncDiff.differences.map((item) => item.section)
@@ -196,7 +196,7 @@ export default function NovelWorldManagerCard(props: NovelWorldManagerCardProps)
const forces = handbook?.forces.length ? handbook.forces : handbook?.factions ?? [];
const summaryText = handbook?.summary
?? novelWorld?.coverSummary
- ?? (novelWorld ? "这本书的世界正在整理中。" : "先创建一份属于这本书的世界副本,后续角色、大纲和章节都会读取这里的设定边界。");
+ ?? (novelWorld ? "The world of this book is being sorted out." : "First create a copy of the world that belongs to this book. Subsequent characters, outlines, and chapters will all read the set boundaries here.");
const themeLine = inlineText([
handbook?.identity ? `身份:${handbook.identity}` : null,
handbook?.tone ? `气质:${handbook.tone}` : null,
@@ -214,37 +214,37 @@ export default function NovelWorldManagerCard(props: NovelWorldManagerCardProps)
- {props.isLoading ? 读取中 : null}
- {novelWorld ? labelSourceType(novelWorld.sourceType) : "未设置来源"}
+ {props.isLoading ? Reading : null}
+ {novelWorld ? labelSourceType(novelWorld.sourceType) : "Source not set"}{writingStatus}{syncStatus}
- {lastSyncedAtText ? 同步 {lastSyncedAtText} : null}
- {pendingSectionText ? 待处理 {pendingSectionText} : null}
+ {lastSyncedAtText ? synchronous {lastSyncedAtText} : null}
+ {pendingSectionText ? Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know. {pendingSectionText} : null}
-
本书世界
+
book world
{activeWorldName}
{novelWorld ? (
<>
openDialog("overview")}>
- 打开完整世界手册
-
+ Open the complete world manual
+
openDialog("usage")}>
- 整理使用范围
-
+ Organize the scope of use
+
>
) : (
- 选择或生成本书世界
+ Select or generate a book world
)}
{hasSyncDiff ? (
openDialog("sync")}>
- 处理同步差异
-
+ Handle sync differences
+
) : null}
@@ -257,37 +257,35 @@ export default function NovelWorldManagerCard(props: NovelWorldManagerCardProps)
-
生成链会读取这份世界
+
The generation chain will read this world
- {novelWorld?.hasStorySlice
- ? "角色、大纲和章节会优先继承本书使用范围里的规则、势力和地点。"
- : "整理本书使用范围后,生成链会读取更精准的世界约束。"}
+ {novelWorld?.hasStorySlice ? "Characters, outlines, and chapters will preferentially inherit the rules, factions, and locations within the scope of this book." : "After reorganizing the scope of this book, the generation chain will read more precise world constraints."}
@@ -296,21 +294,17 @@ export default function NovelWorldManagerCard(props: NovelWorldManagerCardProps)
diff --git a/client/src/pages/novels/components/OutlineTab.tsx b/client/src/pages/novels/components/OutlineTab.tsx
index 16fc013dc..bab1a0d68 100644
--- a/client/src/pages/novels/components/OutlineTab.tsx
+++ b/client/src/pages/novels/components/OutlineTab.tsx
@@ -17,9 +17,9 @@ import type { VolumeBeatImpactItem } from "@ai-novel/shared/types/novel";
type OutlineWorkspaceTab = "current" | "strategy" | "assets";
function versionStatusLabel(status: "draft" | "active" | "frozen"): string {
- if (status === "active") return "已生效";
- if (status === "frozen") return "已冻结";
- return "草稿";
+ if (status === "active") return "Already effective";
+ if (status === "frozen") return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
+ return "draft";
}
function versionStatusVariant(status: "draft" | "active" | "frozen"): "secondary" | "outline" | "default" {
@@ -31,50 +31,50 @@ function versionStatusVariant(status: "draft" | "active" | "frozen"): "secondary
const readinessSteps = [
{
key: "canGenerateStrategy",
- label: "卷战略",
- description: "先拿到推荐卷数、硬/软规划和升级梯度。",
+ label: "volume strategy",
+ description: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
},
{
key: "canGenerateSkeleton",
- label: "卷骨架",
- description: "确认每卷的开卷抓手、压迫源和兑现方式。",
+ label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ description: "Confirm the opening handle, source of pressure and redemption method of each volume.",
},
{
key: "canGenerateBeatSheet",
- label: "节奏板",
- description: "卷骨架稳定后,才适合进入单卷节奏拆分。",
+ label: "rhythm board",
+ description: "After the roll skeleton is stable, it is suitable to enter the single roll rhythm splitting.",
},
{
key: "canGenerateChapterList",
- label: "拆章节",
- description: "节奏板准备好后,才能继续拆到章节级别。",
+ label: "Split chapters",
+ description: "Once the rhythm board is ready, you can proceed to the chapter level.",
},
] as const;
function getNextOutlineAction(readiness: OutlineTabViewProps["readiness"]): string {
- if (!readiness.canGenerateStrategy) return "先生成卷战略建议";
- if (!readiness.canGenerateSkeleton) return "现在适合生成全书卷骨架";
- if (!readiness.canGenerateBeatSheet) return "卷骨架已准备好,下一步进入节奏 / 拆章";
- if (!readiness.canGenerateChapterList) return "先做当前卷节奏板,再拆当前卷章节";
- return "卷战略阶段已齐备,可以继续进入节奏 / 拆章";
+ if (!readiness.canGenerateStrategy) return "Mr. Paper Strategy Advice";
+ if (!readiness.canGenerateSkeleton) return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
+ if (!readiness.canGenerateBeatSheet) return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
+ if (!readiness.canGenerateChapterList) return "Make the rhythm board of the current volume first, then split the chapters of the current volume";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
function getVolumeScaleProfileLabel(profile: OutlineTabViewProps["volumeCountGuidance"]["volumeScaleProfile"]): string {
const labels: Record = {
- short: "短篇结构",
- compact: "紧凑中篇",
- standard: "标准长篇",
- long: "长篇展开",
- epic: "大长篇",
- mega: "超长篇",
+ short: "short story structure",
+ compact: "compact novella",
+ standard: "Standard novel",
+ long: "Long story expansion",
+ epic: "Long story",
+ mega: "Very long story",
};
- return labels[profile] ?? "结构建议";
+ return labels[profile] ?? "Structural suggestions";
}
function getBeatImpactStatusLabel(status: VolumeBeatImpactItem["status"]): string {
- if (status === "locked_with_draft") return "已有正文锁定";
- if (status === "pending") return "待生成";
- return "可接入未写段";
+ if (status === "locked_with_draft") return "Text locked";
+ if (status === "pending") return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
function getBeatImpactStatusVariant(status: VolumeBeatImpactItem["status"]): "secondary" | "outline" | "default" {
@@ -85,7 +85,7 @@ function getBeatImpactStatusVariant(status: VolumeBeatImpactItem["status"]): "se
function formatBeatChapterOrders(chapterOrders: number[]): string {
if (chapterOrders.length === 0) {
- return "待生成章节";
+ return "Chapters to be generated";
}
const sorted = chapterOrders.slice().sort((left, right) => left - right);
return sorted[0] === sorted[sorted.length - 1]
@@ -175,8 +175,8 @@ export default function OutlineTab(props: OutlineTabViewProps) {
return (
@@ -184,28 +184,28 @@ export default function OutlineTab(props: OutlineTabViewProps) {
+ First determine the volume-level advancement method for the entire book, and then review the commitment, pressure, fulfillment of the current volume, and the traction for the next volume.
+
- {isGeneratingStrategy ? "生成中..." : "生成卷战略建议"}
+ {isGeneratingStrategy ? "Generating..." : "Generate Volume Strategy Advice"}
- {isCritiquingStrategy ? "审查中..." : "AI审查卷战略"}
+ {isCritiquingStrategy ? "Under review..." : "AI Review Volume Strategy"}
- {isGeneratingSkeleton ? "生成中..." : volumes.length > 0 ? "重生成全书卷骨架" : "生成全书卷骨架"}
+ {isGeneratingSkeleton ? "Generating..." : volumes.length > 0 ? "Reborn into the skeleton of the entire book" : "Generate the skeleton of the entire book"}
- {isSaving ? "保存中..." : "保存卷工作区"}
+ {isSaving ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : "Save volume workspace"}
- 建议先补齐角色,再生成卷战略和卷骨架。
- 去角色管理
+ It is recommended to complete the characters first, and then create the roll strategy and roll skeleton.
+ Go to character management
Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
{nextOutlineAction}
{outlineStageReady
- ? "当前卷战略阶段已经具备完整推进条件。"
+ ? "The current volume strategic stage has complete conditions for advancement."
: readiness.blockingReasons.length > 0
? `还有 ${readiness.blockingReasons.length} 项阻塞条件需要处理。`
- : "当前可以继续推进本阶段。"}
+ : "This stage can now be continued."}
@@ -258,7 +258,7 @@ export default function OutlineTab(props: OutlineTabViewProps) {
+ Current structure gear:{volumeScaleProfileLabel}。{volumeCountGuidance.volumeCountRationale}
+ Chapter budgets will still be referenced {volumeCountGuidance.targetChapterRange.min}-{volumeCountGuidance.targetChapterRange.max} Chapter/Volume,
+ However, the system will give priority to recommending the number of rolls based on phase commitment, selling point switching, situation escalation and phase fulfillment.
+
change volume {diffResult.changedVolumeCount} | Affected chapters {diffResult.changedChapterCount} | Change the number of rows {diffResult.changedLines}
impact volume {impactResult.affectedVolumeCount} | Affected chapters {impactResult.affectedChapterCount} | Change the number of rows {impactResult.changedLines}
{props.expansion ? (
- 故事引擎原型
+ Story Engine Prototype
- 这里定义故事为什么能一直写下去:主角如何被困、冲突怎样升级、未知如何驱动读者继续读。
-
+ Define here why the story can keep being written: how the protagonist is trapped, how the conflict escalates, and how the unknown drives the reader to continue reading.
+
@@ -91,7 +91,7 @@ export default function StoryMacroPlanTab(props: StoryMacroTabProps) {
-
冲突层
+
Conflict layer
-
外部压迫
+
External oppression
props.onFieldChange("conflict_layers", {
...expansion.conflict_layers,
external: event.target.value,
})}
- placeholder="外部系统、威胁或环境如何持续压迫主角。"
+ placeholder="How external systems, threats, or circumstances continue to oppress the protagonist."
className={textareaClassName("min-h-24")}
/>
-
内部崩塌
+
Internal collapse
props.onFieldChange("conflict_layers", {
...expansion.conflict_layers,
internal: event.target.value,
})}
- placeholder="主角内在恐惧、欲望或误判怎样反噬自己。"
+ placeholder="How the protagonist’s inner fears, desires, or misjudgments come back to bite him."
className={textareaClassName("min-h-24")}
/>
))}
@@ -185,14 +185,14 @@ export default function StoryMacroPlanTab(props: StoryMacroTabProps) {
- 硬约束
+ hard constraints
- 这里的规则会作为后续生成的硬边界,防止故事在下游被写散。
-
+ The rules here will serve as hard boundaries for subsequent generation to prevent the story from being scattered downstream.
+
-
叙事规则
+
narrative rules
line.trim()).filter(Boolean),
)}
- placeholder="每行一条必须遵守的叙事规则。"
+ placeholder="One narrative rule per line that must be followed."
className={textareaClassName("min-h-36")}
/>
@@ -216,29 +216,29 @@ export default function StoryMacroPlanTab(props: StoryMacroTabProps) {
- 约束引擎
+ constraint engine
- 当前保存的是后续角色、主线、章节规划可以直接消费的规则源。
-
+ What is currently saved is the rule source that can be directly consumed by subsequent characters, main lines, and chapter planning.
+
{props.constraintEngine ? (
<>
+ There is no constraint engine yet. Complete the story engine disassembly first, and then click "Build Constraint Engine".
+
)}
- 故事状态
+ story status
- 保存当前阶段和主角处境,方便后续章节推进时复用。
-
+ Save the current stage and the protagonist's situation for easy reuse when advancing in subsequent chapters.
+
-
当前阶段
+
Current stage
-
进度
+
Progress
-
主角当前处境
+
The protagonist’s current situation
props.onStateChange("protagonistState", event.target.value)}
- placeholder="例如:仍在否认真相,但已经无法退出。"
+ placeholder="Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."
/>
+ There are no clear deliverables in this paragraph. It is recommended to go back to the rhythm generation results to add more specific fulfillment goals.
+
- {selectedChapter
- ? "先补标题、摘要、目标和任务单;写本章时如果执行计划缺失,系统会自动基于这里补齐运行时规划。"
- : "先在左侧章节列表中选中一章,再开始细化。"}
+ {selectedChapter ? "First, complete the title, abstract, objectives, and task list; if the execution plan is missing when writing this chapter, the system will automatically complete the runtime plan based on this." : "Select a chapter in the chapter list on the left before starting to refine it."}
@@ -204,11 +202,11 @@ export default function StructuredChapterDetailCard(props: StructuredChapterDeta
onClick={() => onGenerateChapterDetailBundle(selectedVolume.id, selectedChapter.id)}
disabled={isGeneratingChapterDetail || locked}
>
- {currentBundleRunning ? "当前章细化中..." : "细化当前章"}
+ {currentBundleRunning ? "The current chapter is being refined..." : "Refine the current chapter"}
) : null}
- {showChapterAdvanced ? "收起高级设置" : "展开高级设置"}
+ {showChapterAdvanced ? "Collapse Advanced Settings" : "Expand Advanced Settings"}
@@ -219,10 +217,10 @@ export default function StructuredChapterDetailCard(props: StructuredChapterDeta
-
批量细化
+
Batch refinement
- 可以从当前章起按数量连续细化,也可以直接补齐当前可见章节或本卷全部章节。
-
+ You can continuously refine by quantity starting from the current chapter, or you can directly complete the currently visible chapter or all chapters in this volume.
+
- {locked
- ? "请先生成当前卷节奏板,再做整章或批量细化。"
- : batchPlan?.hint ?? "当前卷只有 1 章,先细化当前章。"}
+ {locked ? "Please generate the current volume's pacing first, then refine the entire chapter or batches." : batchPlan?.hint ?? "The current volume only has 1 chapter, refine the current chapter first."}
+ Conflict level, exposure level, word count, prohibitions, and redemption associations have been moved into advanced settings to avoid overwhelming the form right off the bat.
+
)}
>
) : (
- 先在左侧选择一个章节,再开始细化。
-
+ Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
+
)}
diff --git a/client/src/pages/novels/components/StructuredChapterListCard.tsx b/client/src/pages/novels/components/StructuredChapterListCard.tsx
index f1e7c9e35..4d9483ba1 100644
--- a/client/src/pages/novels/components/StructuredChapterListCard.tsx
+++ b/client/src/pages/novels/components/StructuredChapterListCard.tsx
@@ -46,12 +46,12 @@ interface StructuredChapterListCardProps {
function renderChapterDetailStatusBadge(chapter: StructuredChapter) {
const status = getChapterExecutionDetailStatus(chapter);
if (status === "complete") {
- return 已细化;
+ return Refined;
}
if (status === "partial") {
- return 细化中;
+ return Refining;
}
- return 待细化;
+ return To be refined;
}
function isBeatGroupComplete(group: {
@@ -134,18 +134,18 @@ export default function StructuredChapterListCard(props: StructuredChapterListCa
const isGeneratingGroup = isGeneratingCurrentVolume
&& (generatingChapterListMode === "full_volume" || generatingChapterListBeatKey === group.key);
if (isGeneratingGroup) {
- return 生成中;
+ return Generating;
}
if (group.hasDraftContent) {
- return 已有正文锁定;
+ return Text locked;
}
if (group.chapters.length === 0) {
- return 待生成;
+ return Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.;
}
if (group.expectedCount > 0 && group.chapters.length !== group.expectedCount) {
- return 需重试;
+ return Need to retry;
}
- return 已生成;
+ return Generated;
}
return (
@@ -154,11 +154,11 @@ export default function StructuredChapterListCard(props: StructuredChapterListCa
- 节奏 / 章节导航
+ Rhythm/Chapter Navigation
{selectedBeat
? `当前聚焦「${formatBeatDisplayLabel(selectedBeat)}」。点击组头切换节奏,点击章节直接在右侧继续细化。`
- : "按节奏分组显示章节。点击组头可聚焦该节奏,点击章节直接在右侧继续细化。"}
+ : "Displays chapters grouped by rhythm. Click on the group header to focus on the rhythm, click on the chapter to continue refinement directly on the right."}
@@ -174,14 +174,14 @@ export default function StructuredChapterListCard(props: StructuredChapterListCa
}}
disabled={isGeneratingChapterList || locked || !nextChapterListGroup}
>
- {isGeneratingNextChapterListGroup ? "生成中..." : nextChapterListGroup ? "生成下一段章节" : "全部节奏段已生成"}
+ {isGeneratingNextChapterListGroup ? "Generating..." : nextChapterListGroup ? "Generate the next chapter" : "All rhythm sections have been generated"}
onGenerateChapterList(selectedVolume.id, { generationMode: "full_volume" })}
disabled={isGeneratingChapterList || locked}
>
- {isGeneratingFullVolume ? "整卷生成中..." : "高级:生成本卷全部章节标题"}
+ {isGeneratingFullVolume ? "The entire volume is being generated..." : "Advanced: Generate all chapter titles in this volume"}
+ The current volume has been generated {selectedVolumeChapters.length}/{selectedVolumeRequiredChapterCount} Chapter, there are still rhythm sections to be generated. The generated chapters can be refined and written first, and subsequent sections can be generated as needed.
+
+ The current rhythm section is {selectedBeat.chapterSpanHint}, no chapters have been generated yet. The current focus segment can be generated without the need to fill in the entire volume first.
+
+ The current rhythm section is not mapped to a chapter yet. After generating this paragraph, you can continue to refine and write chapters of this paragraph.
+
+ Based on the current rhythm board, this volume is expected to cover {selectedVolumeRequiredChapterCount} chapter. You can generate the next section first, and continue to split subsequent sections as needed.
+
) : null}
- 当前卷还没有章节列表。先生成下一段章节。
-
+ There is no chapter list for the current volume yet. Generate the next chapter first.
+
)}
diff --git a/client/src/pages/novels/components/StructuredOutlineWorkspace.tsx b/client/src/pages/novels/components/StructuredOutlineWorkspace.tsx
index f0186013e..145773282 100644
--- a/client/src/pages/novels/components/StructuredOutlineWorkspace.tsx
+++ b/client/src/pages/novels/components/StructuredOutlineWorkspace.tsx
@@ -29,12 +29,12 @@ type StructuredChapter = StructuredVolume["chapters"][number];
type StructuredBeat = StructuredTabViewProps["beatSheets"][number]["beats"][number];
function actionLabel(action: StructuredTabViewProps["syncPreview"]["items"][number]["action"]) {
- if (action === "create") return "新增";
- if (action === "update") return "更新";
- if (action === "move") return "移动";
- if (action === "keep") return "保留";
- if (action === "delete") return "删除";
- return "待删候选";
+ if (action === "create") return "New";
+ if (action === "update") return "renew";
+ if (action === "move") return "move";
+ if (action === "keep") return "reserve";
+ if (action === "delete") return "delete";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
function getWorkspaceGuidance(params: {
@@ -46,7 +46,7 @@ function getWorkspaceGuidance(params: {
}): string {
const { locked, selectedBeat, selectedChapter, visibleChapterCount, totalChapterCount } = params;
if (locked) {
- return "先为当前卷生成节奏板,系统才能把卷内推进节奏和章节拆分对齐起来。";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
if (selectedBeat) {
const beatLabel = formatBeatDisplayLabel(selectedBeat);
@@ -200,13 +200,13 @@ export default function StructuredOutlineWorkspace(props: StructuredTabViewProps
totalChapterCount: selectedVolumeChapters.length,
});
const tensionCurveViewportOptions: TensionCurveViewportOption[] = [
- { key: "all", label: "整卷" },
+ { key: "all", label: "whole volume" },
...(selectedBeatSheet?.beats.map((beat) => ({ key: beat.key, label: formatBeatDisplayLabel(beat) })) ?? []),
];
const tensionCurveSeries: TensionCurveSeries[] = selectedVolume
? [{
id: "conflictLevel",
- label: "冲突强度",
+ label: "conflict intensity",
color: "#2563eb",
editable: true,
points: selectedVolumeChapters.map((chapter) => ({
@@ -246,16 +246,16 @@ export default function StructuredOutlineWorkspace(props: StructuredTabViewProps
if (volumes.length === 0) {
return (
- 节奏 / 拆章
+ Rhythm / Breaking Chapter
{!hasCharacters ? (
- 请先补角色,再拆节奏和章节。
- 去角色管理
+ Please make up the characters first, and then break down the rhythm and chapters.
+ Go to character management
) : null}
-
先在上一页生成卷战略和卷骨架。
+
First generate the volume strategy and volume skeleton on the previous page.
);
@@ -265,11 +265,11 @@ export default function StructuredOutlineWorkspace(props: StructuredTabViewProps
- 节奏 / 拆章
-
先选卷,再看节奏,再从对应章节里挑当前要细化的一章。
+ Rhythm / Breaking Chapter
+
Select the volume first, then look at the rhythm, and then select the chapter to be detailed from the corresponding chapters.
Want AI to continue taking over your current project?
- 不用回到项目设定,直接在这里重新进入自动导演,让 AI 继续推进节奏拆章或后续自动执行。
-
+ There is no need to go back to the project settings, just re-enter the automatic director here and let the AI continue to advance the rhythm of chapters or subsequent automatic execution.
+
{directorTakeoverEntry}
@@ -291,10 +291,10 @@ export default function StructuredOutlineWorkspace(props: StructuredTabViewProps
+ detected {selectedRebalance.length} Adjacent volume rebalancing recommendations. They affect cross-volume connections but are not part of the current main editing action.
+
+ The current chapter planning is mainly based on "chapter selection + refinement". Batch replenishment of task orders, connection differences, and JSON preview are closed by default to avoid interrupting the main process.
+
+ Here we give priority to helping you find the latest stable version. The system will automatically back up the current state again before restoration.
+
createMutation.mutate()} disabled={createMutation.isPending}>
- {createMutation.isPending ? "保存中..." : "保存当前版本"}
+ {createMutation.isPending ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : "Save current version"}
+ This version is suitable for when you want to return to a more stable state of chapter advancement.
+
{
- const confirmed = window.confirm("恢复前会自动备份当前状态。确认恢复这个版本吗?");
+ const confirmed = window.confirm("The current state will be automatically backed up before restoration. Are you sure you want to restore this version?");
if (confirmed) {
restoreMutation.mutate(snapshot.id);
}
}}
disabled={restoreMutation.isPending}
>
- {isRestoringCurrent ? "恢复中..." : "恢复到这个版本"}
+ {isRestoringCurrent ? "Restoring..." : "Restoring to this version"}
+ There is currently no version record. It is recommended to manually save a version before making major changes in direction, batch generation, or large-scale rewrites.
+
+ Here we only look at the currently selected volume to check whether the matters to be redeemed in this volume are consistent with the chapter redemption arrangements. The full-book-level canonical ledger has been moved to an independent module above.
+
- 第 {selectedVolume.sortOrder} 卷
- 待兑现 {openPayoffRows.length}
- 已挂章节 {linkedOpenPayoffCount}
+ Section {selectedVolume.sortOrder} roll
+ To be cashed {openPayoffRows.length}
+ Chapter has been suspended {linkedOpenPayoffCount} 0 ? "secondary" : "outline"}>
- 待补关联 {unplannedOpenPayoffs.length}
+ Associations to be filled {unplannedOpenPayoffs.length}
- 章节安排 {chapterPayoffGroups.length}
+ Chapter arrangement {chapterPayoffGroups.length}
@@ -78,12 +78,12 @@ export default function VolumePayoffOverviewCard(props: VolumePayoffOverviewCard
-
本卷待兑现事项
+
Items to be redeemed in this volume
{openPayoffRows.length}
- 这里是卷战略里声明的待兑现事项,用来看这一卷到底有哪些坑要埋、哪些点要回收。
-
+ Here are the items to be fulfilled stated in the volume strategy, which can be used to see which pitfalls need to be buried and which points need to be recovered in this volume.
+
+ This item to be redeemed has not yet been linked to a specific chapter in this volume. It is recommended to make up the redemption link when opening the chapter.
+
)}
))
) : (
- 当前卷还没有填写待兑现事项。
-
+ The current volume has not been filled with items to be redeemed.
+
)}
-
本卷章节兑现安排
+
Arrangements for redemption of chapters in this volume
{chapterPayoffGroups.length}
- 这里看的是当前卷已经挂到章节里的兑现关联,用来检查拆章是否真正落到了章节级执行。
-
+ What we look at here is the redemption association that the current volume has been linked to the chapter, which is used to check whether the chapter splitting has actually been executed at the chapter level.
+
+ The redemption relationship has not been filled in for the chapters in the current volume. It will be more difficult to check which foreshadowing should be recycled when the chapter is opened later.
+
diff --git a/client/src/pages/novels/components/basicInfoForm/BookFramingQuickFillButton.tsx b/client/src/pages/novels/components/basicInfoForm/BookFramingQuickFillButton.tsx
index de4d2fa3a..2500f629c 100644
--- a/client/src/pages/novels/components/basicInfoForm/BookFramingQuickFillButton.tsx
+++ b/client/src/pages/novels/components/basicInfoForm/BookFramingQuickFillButton.tsx
@@ -53,7 +53,7 @@ export function BookFramingQuickFillButton(props: BookFramingQuickFillButtonProp
onSuccess: (response) => {
const suggestion = response.data;
if (!suggestion) {
- toast.error("AI 没有返回可用的读者与卖点建议。");
+ toast.error("The AI did not return any available reader and selling point suggestions.");
return;
}
onApplySuggestion({
@@ -63,20 +63,20 @@ export function BookFramingQuickFillButton(props: BookFramingQuickFillButtonProp
bookSellingPoint: suggestion.bookSellingPoint,
first30ChapterPromise: suggestion.first30ChapterPromise,
});
- toast.success("已根据当前书名和概述填入读者与卖点建议。");
+ toast.success("Reader and selling point suggestions have been populated based on the current book title and summary.");
},
onError: (error) => {
- toast.error(error instanceof Error ? error.message : "读者与卖点自动填写失败,请稍后再试。");
+ toast.error(error instanceof Error ? error.message : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.");
},
});
const handleGenerate = () => {
if (!basicForm.title.trim() && !effectiveDescription) {
- toast.error("请先填写书名或一句话概述,再让 AI 帮你填写。");
+ toast.error("Please fill in the book title or one-sentence summary first, and then let AI fill it in for you.");
return;
}
if (hasExistingFramingContent(basicForm)) {
- const confirmed = window.confirm("将用 AI 建议覆盖当前读者与卖点填写,是否继续?");
+ const confirmed = window.confirm("AI suggestions will be used to cover the current readers and fill in the selling points. Do you want to continue?");
if (!confirmed) {
return;
}
@@ -92,7 +92,7 @@ export function BookFramingQuickFillButton(props: BookFramingQuickFillButtonProp
onClick={handleGenerate}
disabled={suggestionMutation.isPending}
>
- {suggestionMutation.isPending ? "填写中..." : "帮我填写"}
+ {suggestionMutation.isPending ? "Filling in..." : "Please fill in this for me"}
);
}
diff --git a/client/src/pages/novels/components/basicInfoForm/BookFramingSection.tsx b/client/src/pages/novels/components/basicInfoForm/BookFramingSection.tsx
index 6ab0ac923..5940e9eaa 100644
--- a/client/src/pages/novels/components/basicInfoForm/BookFramingSection.tsx
+++ b/client/src/pages/novels/components/basicInfoForm/BookFramingSection.tsx
@@ -16,10 +16,10 @@ export function BookFramingSection(props: BookFramingSectionProps) {
+ Make it clear in the most direct terms: Who is this book for, what is the selling point, and what will the reader get out of the first 30 chapters. It doesn’t matter if you don’t know how to write professional planning words, just describe it according to your intuition.
+
{quickFill ?
{quickFill}
: null}
@@ -27,50 +27,50 @@ export function BookFramingSection(props: BookFramingSectionProps) {
);
@@ -87,14 +87,14 @@ export default function BookPositioningStudio(props: BookPositioningStudioProps)
- 书级资产
-
- 后续规划、角色和章节生成会优先读取这里
+ book-level assets
+
+ Subsequent planning, character and chapter generation will be read here first.
-
定位这本书的读者承诺
+
Position the book's readership commitments
- 先把标题、主冲突、读者期待和前 30 章必须兑现的内容收束清楚,再让 AI 继续展开世界、角色和章节。
-
+ First, wrap up the title, main conflict, reader expectations, and what must be fulfilled in the first 30 chapters, and then let the AI continue to develop the world, characters, and chapters.
+
{completedCount} / {POSITIONING_FIELDS.length} Item is ready
{readinessPercent}%
@@ -152,20 +152,20 @@ export default function BookPositioningStudio(props: BookPositioningStudioProps)
}
- label="目标读者"
- value={createPreview(basicForm.targetAudience, "写清楚谁会追这本书")}
+ label="target audience"
+ value={createPreview(basicForm.targetAudience, "Write clearly who will pursue this book")}
ready={hasText(basicForm.targetAudience)}
/>
}
- label="核心卖点"
- value={createPreview(basicForm.bookSellingPoint, "明确最抓人的爽点或悬念")}
+ label="core selling point"
+ value={createPreview(basicForm.bookSellingPoint, "Identify the most gripping points or suspense")}
ready={hasText(basicForm.bookSellingPoint)}
/>
}
- label="前 30 章牵引"
- value={createPreview(basicForm.first30ChapterPromise, "告诉 AI 前期必须兑现什么")}
+ label="Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."
+ value={createPreview(basicForm.first30ChapterPromise, "Tell AI what must be fulfilled in the early stage")}
ready={hasText(basicForm.first30ChapterPromise)}
/>
@@ -173,8 +173,8 @@ export default function BookPositioningStudio(props: BookPositioningStudioProps)
+ The chapter's position in the volume, priority correction tasks, and segments that can be processed directly are being analyzed. You can wait a few seconds before starting.
+
@@ -152,11 +152,11 @@ export default function ChapterEditorDirectorPanel(props: ChapterEditorDirectorP
{recommendedTask
? `${recommendedTask.title}。${recommendedTask.summary}`
- : "AI 暂未生成推荐任务,你可以直接告诉 AI 你的修改想法。"}
+ : "AI has not yet generated recommended tasks. You can directly tell AI your modification ideas."}
>
@@ -178,14 +178,14 @@ export default function ChapterEditorDirectorPanel(props: ChapterEditorDirectorP
onClick={onRunSelectedDiagnostic}
disabled={selectedDiagnosticCard.recommendedScope === "selection" && !canRunSelectionRevision}
>
- 直接用 AI 处理这张问题卡
-
+ Use AI directly to process this question card
+
- 当前目标:{currentTargetDescription}
+ Current goals:{currentTargetDescription}
onInstructionChange(event.target.value)}
/>
{revisionScope === "selection"
- ? "片段模式会优先使用你手动选中的正文;如果没有手动选段,会使用当前问题卡定位的片段。"
- : "整章模式会基于整章内容生成候选,仍然需要你先比较再接受。"}
+ ? "The snippet mode will give priority to the text you manually selected; if there is no manual selection, the snippet positioned by the current question card will be used."
+ : "The whole chapter mode will generate candidates based on the entire chapter content, but you still need to compare them before accepting them."}
- {isGenerating ? "生成中..." : "发起 AI 修正"}
+ {isGenerating ? "Generating..." : "Initiate AI correction"}
{isRecommended ? (
- 推荐先修
-
+ Recommended to take
+
) : null}
@@ -230,21 +199,17 @@ export default function ChapterEditorSidebar(props: ChapterEditorSidebarProps) {
variant={isSelected ? "default" : "outline"}
onClick={() => onFocusDiagnostic(card)}
>
- {isSelected ? "取消定位" : "定位到正文"}
+ {isSelected ? "Cancel selection" : "Select the main text"}
onRunDiagnostic(card)}>
- 直接用 AI 处理
-
+ Processed directly with AI
+
);
}) : (
- {isWorkspaceError
- ? "问题卡暂时加载失败,你可以先在右侧直接输入修改意见,或手动选中片段发起修正。"
- : workspace
- ? "AI 暂时还没有整理出明确的问题卡,你可以先在右侧直接输入修改意见,或手动选中片段发起修正。"
- : "正在加载本章工作区。"}
+ {isWorkspaceError ? "The issue card has temporarily failed to load. You can enter your revision comments directly on the right, or manually select the segment to initiate a correction." : workspace ? "AI has not yet compiled a clear issue card. You can enter your revision comments directly on the right, or manually select the segment to initiate a correction." : "Loading the workspace for this chapter."}
)}
diff --git a/client/src/pages/novels/components/chapterEditor/ChapterTextEditor.tsx b/client/src/pages/novels/components/chapterEditor/ChapterTextEditor.tsx
index 5ea461440..03f545ab3 100644
--- a/client/src/pages/novels/components/chapterEditor/ChapterTextEditor.tsx
+++ b/client/src/pages/novels/components/chapterEditor/ChapterTextEditor.tsx
@@ -117,11 +117,11 @@ function renderLoadingPreview(
-
待改写原文
+
Original text to be rewritten
-
AI 正在生成候选版本
+
AI is generating release candidates
@@ -150,7 +150,7 @@ function renderBlockPreview(
-
改写
+
rewrite
@@ -253,14 +253,14 @@ export default function ChapterTextEditor(props: ChapterTextEditorProps) {
);
const helperText = preview?.mode === "inline"
- ? "当前正在显示细节标记 diff,适合确认具体删改。"
+ ? "The detail mark diff is currently being displayed, suitable for confirming specific deletions."
: preview?.mode === "loading"
- ? "AI 正在基于选中内容生成候选版本,原文位置会持续保留。"
+ ? "AI is generating candidate versions based on the selected content, and the original text location will continue to be retained."
: preview?.mode === "block"
- ? "当前正在显示段落 patch 对比,原文和改写会在正文原位置并排落成红绿块。"
+ ? "Paragraph patch comparison is currently being displayed. The original text and the rewritten text will be placed side by side into red and green blocks at the original position of the text."
: readOnly
- ? "当前有待确认候选,正文暂时锁定,可在右侧切换候选或接受、拒绝。"
- : "可直接编辑正文,选中内容后可发起 AI 改写。";
+ ? "There are currently candidates to be confirmed, and the text is temporarily locked. You can switch candidates or accept or reject on the right."
+ : "The text can be edited directly, and AI rewriting can be initiated after selecting the content.";
useEffect(() => {
const surface = surfaceRef.current;
@@ -360,7 +360,7 @@ export default function ChapterTextEditor(props: ChapterTextEditorProps) {
return (
-
正文
+
text
{helperText}
diff --git a/client/src/pages/novels/components/chapterEditor/SelectionAIFloatingToolbar.tsx b/client/src/pages/novels/components/chapterEditor/SelectionAIFloatingToolbar.tsx
index 35702e813..9566f3d03 100644
--- a/client/src/pages/novels/components/chapterEditor/SelectionAIFloatingToolbar.tsx
+++ b/client/src/pages/novels/components/chapterEditor/SelectionAIFloatingToolbar.tsx
@@ -41,8 +41,8 @@ export default function SelectionAIFloatingToolbar(props: SelectionAIFloatingToo
onMouseDown={(event) => event.preventDefault()}
onClick={() => onRunOperation("polish")}
>
- AI 优化这段
-
+ AI optimizes this section
+
{SECONDARY_OPERATIONS.map((operation) => (
event.preventDefault()}
onClick={() => setIsCustomOpen((current) => !current)}
>
- 告诉 AI 怎么改
-
+ Tell AI how to change
+
) : null}
diff --git a/client/src/pages/novels/components/chapterEditor/chapterEditorUtils.ts b/client/src/pages/novels/components/chapterEditor/chapterEditorUtils.ts
index 1716a5eca..9b21db9ba 100644
--- a/client/src/pages/novels/components/chapterEditor/chapterEditorUtils.ts
+++ b/client/src/pages/novels/components/chapterEditor/chapterEditorUtils.ts
@@ -7,12 +7,12 @@ import type {
} from "./chapterEditorTypes";
export const CHAPTER_EDITOR_OPERATION_LABELS: Record = {
- polish: "优化表达",
- expand: "扩写",
- compress: "精简",
- emotion: "强化情绪",
- conflict: "强化冲突",
- custom: "自定义指令",
+ polish: "Optimize expression",
+ expand: "Expand",
+ compress: "streamline",
+ emotion: "Strengthen mood",
+ conflict: "intensify conflict",
+ custom: "custom directive",
};
export function normalizeEditorText(text: string): string {
@@ -333,13 +333,13 @@ export function buildAiRevisionRequest(input: ChapterEditorRequestBuilderInput)
export function getSaveStatusLabel(status: "idle" | "saving" | "saved" | "error", isDirty: boolean): string {
if (status === "saving") {
- return "保存中";
+ return "Saving";
}
if (status === "saved") {
- return "已保存";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
if (status === "error") {
- return "保存失败";
+ return "Save failed";
}
- return isDirty ? "待保存" : "已同步";
+ return isDirty ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : "Synced";
}
diff --git a/client/src/pages/novels/components/chapterExecution.shared.tsx b/client/src/pages/novels/components/chapterExecution.shared.tsx
index e49c0d17e..ac09a08cf 100644
--- a/client/src/pages/novels/components/chapterExecution.shared.tsx
+++ b/client/src/pages/novels/components/chapterExecution.shared.tsx
@@ -94,13 +94,13 @@ interface ResolveChapterExecutionFlowInput {
}
const CHAPTER_EXECUTION_FLOW_ORDER: Array<{ key: ChapterExecutionFlowStageKey; label: string }> = [
- { key: "execution_plan", label: "执行计划" },
- { key: "writing", label: "正文写作" },
- { key: "review", label: "审核" },
- { key: "repair", label: "修复" },
- { key: "state_sync", label: "状态同步" },
- { key: "payoff_sync", label: "伏笔回填" },
- { key: "ready", label: "可继续推进" },
+ { key: "execution_plan", label: "execution plan" },
+ { key: "writing", label: "text writing" },
+ { key: "review", label: "review" },
+ { key: "repair", label: "repair" },
+ { key: "state_sync", label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
+ { key: "payoff_sync", label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
+ { key: "ready", label: "Can continue to advance" },
];
function hasOpenAuditIssues(reports: AuditReport[]): boolean {
@@ -147,35 +147,35 @@ function buildCurrentStageNote(stage: ChapterExecutionFlowStage): string {
switch (stage.key) {
case "execution_plan":
return stage.status === "done"
- ? "这一章的执行计划已经齐备。"
- : "这章还缺执行计划,系统会先准备任务单或场景拆解。";
+ ? "The execution plan for this chapter is ready."
+ : "This chapter still lacks an execution plan. The system will first prepare a task list or scene breakdown.";
case "writing":
return stage.status === "in_progress"
- ? "AI 正在写这一章的正文。"
- : "执行计划已具备,可以开始写正文。";
+ ? "AI is writing the main text of this chapter."
+ : "The execution plan is now ready and you can start writing the text.";
case "review":
return stage.status === "in_progress"
- ? "正文已生成,系统正在审核。"
- : "正文已有内容,下一步会进入审核。";
+ ? "The text has been generated and is being reviewed by the system."
+ : "The text already contains content, and the next step will be review.";
case "repair":
return stage.status === "in_progress"
- ? "系统正在根据问题修复正文。"
- : "如果审核发现问题,这里会进入修复阶段。";
+ ? "The system is fixing the text based on the problem."
+ : "If problems are found during the review, the repair phase will be entered.";
case "state_sync":
return stage.status === "in_progress"
- ? "正文可读,系统正在回灌本章状态、角色变化和关键资源。"
- : "正文可读后,系统会回灌本章状态和关键资源。";
+ ? "The text is readable, and the system is feeding back the status of this chapter, character changes, and key resources."
+ : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
case "payoff_sync":
return stage.status === "in_progress"
- ? "系统正在校准本章涉及的伏笔账本。"
- : "资产回灌后,系统会按风险和节奏校准伏笔账本。";
+ ? "The system is calibrating the foreshadowing ledger involved in this chapter."
+ : "After the assets are reintroduced, the system will calibrate the foreshadowing ledger according to risk and rhythm.";
case "ready":
default:
return stage.status === "done"
- ? "这章已经达到可继续推进的状态。"
+ ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."
: stage.status === "in_progress"
- ? "这章已经完成当前轮审核。你可以继续编辑,也可以先处理建议。"
- : "完成前面步骤后,这章就可以继续推进。";
+ ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."
+ : "After completing the previous steps, you can move forward with this chapter.";
}
}
@@ -320,55 +320,55 @@ export function resolveDisplayedChapterStatus(chapter: Chapter): Chapter["chapte
export function chapterStatusLabel(status?: Chapter["chapterStatus"] | null): string {
switch (status) {
case "unplanned":
- return "待准备";
+ return "To be prepared";
case "pending_generation":
- return "待写作";
+ return "To be written";
case "generating":
- return "写作中";
+ return "Writing";
case "pending_review":
- return "已审校";
+ return "Reviewed";
case "needs_repair":
- return "建议修复";
+ return "Suggested fixes";
case "completed":
- return "已完成";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
default:
- return "未设置";
+ return "not set";
}
}
export function chapterStatusDescription(status?: Chapter["chapterStatus"] | null): string {
switch (status) {
case "unplanned":
- return "待准备:这章还缺少执行素材,通常要先补章节目标、任务单或场景卡。";
+ return "To be prepared: This chapter still lacks execution materials. Usually, chapter objectives, task lists or scene cards need to be supplemented first.";
case "pending_generation":
- return "待写作:章节计划已基本齐备,可以开始生成正文。";
+ return "To be written: The chapter plan is basically ready and the text can be started.";
case "generating":
- return "写作中:AI 正在生成本章正文,或正在做生成后的收尾处理。";
+ return "Writing: AI is generating the text of this chapter, or is finishing the post-generation process.";
case "pending_review":
- return "已审校:正文已经完成当前轮审核。你可以查看建议、直接继续编辑,或按需处理问题。";
+ return "Reviewed: The text has completed the current round of review. You can view suggestions, continue editing directly, or address issues as needed.";
case "needs_repair":
- return "建议修复:审核发现了问题,但不会阻止继续编辑。你可以一键修复,也可以先继续写。";
+ return "Recommended fix: The review uncovered the problem but does not prevent further editing. You can fix it with one click or continue writing first.";
case "completed":
- return "已完成:本章已通过当前流程,可以继续润色或进入下一章。";
+ return "Completed: This chapter has passed the current process and can be polished or moved on to the next chapter.";
default:
- return "未设置:当前章节还没有明确的流程状态。";
+ return "Not set: The current chapter has no clear process status.";
}
}
export function generationStateLabel(state?: Chapter["generationState"] | null): string {
switch (state) {
case "planned":
- return "已入目录";
+ return "Already cataloged";
case "drafted":
- return "已成稿";
+ return "Completed";
case "reviewed":
- return "已审校";
+ return "Reviewed";
case "repaired":
- return "已修复";
+ return "Fixed";
case "approved":
- return "已确认";
+ return "Confirmed";
case "published":
- return "已发布";
+ return "Published";
default:
return "";
}
@@ -377,17 +377,17 @@ export function generationStateLabel(state?: Chapter["generationState"] | null):
export function generationStateDescription(state?: Chapter["generationState"] | null): string {
switch (state) {
case "planned":
- return "已入目录:章节已进入目录或拆章结果,但还没有正文草稿。";
+ return "Already included in the Table of Contents: The chapter has been included in the Table of Contents or has been split into chapters, but there is no text draft yet.";
case "drafted":
- return "已成稿:已经生成过正文草稿,但还没完成审校确认。";
+ return "Completed: A draft of the text has been generated, but the review and confirmation has not yet been completed.";
case "reviewed":
- return "已审校:已经完成一轮审校,后续可能继续修复或确认。";
+ return "Reviewed: A round of review has been completed and may be repaired or confirmed later.";
case "repaired":
- return "已修复:已经根据问题修过一轮,通常下一步是再次审校或确认。";
+ return "Fixed: It has been repaired based on the problem. Usually the next step is to review or confirm again.";
case "approved":
- return "已确认:本章已通过当前质量门槛,自动执行时会视为已完成并跳过。";
+ return "Confirmed: This chapter has passed the current quality threshold and will be considered completed and skipped when automatically executed.";
case "published":
- return "已发布:本章已进入发布状态,自动执行不会再重复生成。";
+ return "Published: This chapter has entered the published state and will not be regenerated automatically.";
default:
return "";
}
@@ -402,53 +402,7 @@ function isRecord(value: unknown): value is Record {
}
function stringifyRiskLabel(value: unknown): string | null {
- return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
-}
-
-function qualityLoopActionLabel(value: unknown): string | null {
- switch (value) {
- case "continue":
- return "质量可继续";
- case "patch_repair":
- return "建议补写修复";
- case "replan":
- return "建议重规划";
- case "manual_gate":
- return "需要确认修复边界";
- default:
- return null;
- }
-}
-
-function qualityLoopStatusLabel(value: unknown): string | null {
- switch (value) {
- case "risk":
- return "质量有风险";
- case "invalid":
- return "质量需修复";
- case "missing":
- return "质量信息缺失";
- default:
- return null;
- }
-}
-
-function qualityLoopArtifactLabel(value: unknown): string | null {
- switch (value) {
- case "chapter_retention_contract":
- return "留存风险";
- case "continuity_state":
- return "连贯性风险";
- case "rolling_window_review":
- return "章节衔接风险";
- case "prose_quality":
- return "正文自然度/退化检测";
- default:
- return null;
- }
-}
-
-function parseStructuredRiskFlagsObject(input: string): Record | null {
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : null; } function qualityLoopActionLabel(value: unknown): string | null { switch (value) { case "continue": return "Quality can continue"; case "patch_repair": return "Repair is recommended"; case "replan": return "Replanning is recommended"; case "manual_gate": return "Repair boundary needs to be confirmed"; default: return null; } } function qualityLoopStatusLabel(value: unknown): string | null { switch (value) { case "risk": return "Quality is at risk"; case "invalid": return "Quality needs to be repaired"; case "missing": return "Quality information is missing"; default: return null; } } function qualityLoopArtifactLabel(value: unknown): string | null { switch (value) { case "chapter_retention_contract": return "Retention risk"; case "continuity_state": return "Continuity risk"; case "rolling_window_review": return "Chapter transition risk"; case "prose_quality": return "Text naturalness/degradation detection"; default: return null; } } function parseStructuredRiskFlagsObject(input: string): Record | null {
let parsed: unknown;
try {
parsed = JSON.parse(input);
@@ -470,7 +424,7 @@ function parseStructuredRiskFlags(input: string): string[] | null {
if (isRecord(qualityLoop)) {
const qualityLoopRisk = classifyChapterQualityLoopRisk(qualityLoop);
if (qualityLoopRisk === "non_blocking_quality_debt") {
- labels.push("已记录质量债务");
+ labels.push("Recorded quality debt");
} else {
const actionLabel = qualityLoopActionLabel(qualityLoop.recommendedAction);
const statusLabel = qualityLoopStatusLabel(qualityLoop.overallStatus);
@@ -507,79 +461,7 @@ export function parseRiskFlags(input: string | null | undefined): string[] {
return input
.split(/[\n,,;;|]/g)
.map((item) => item.trim())
- .filter((item) => item.length > 0)
- .slice(0, 4);
-}
-
-export function hasText(input: string | null | undefined): boolean {
- return Boolean(input?.trim());
-}
-
-export function chapterHasPreparationAssets(chapter: Chapter): boolean {
- return hasText(chapter.expectation) || hasText(chapter.taskSheet) || hasText(chapter.sceneCards);
-}
-
-export function parseChapterScenePlanForDisplay(chapter: Chapter) {
- return parseChapterScenePlan(chapter.sceneCards, {
- targetWordCount: chapter.targetWordCount ?? undefined,
- });
-}
-
-export function resolveChapterQueuePreview(chapter: Chapter): string {
- if (hasText(chapter.expectation)) {
- return chapter.expectation!.trim();
- }
- if (hasText(chapter.taskSheet)) {
- return chapter.taskSheet!.trim();
- }
- const scenePlan = parseChapterScenePlanForDisplay(chapter);
- if (scenePlan) {
- const firstScene = scenePlan.scenes[0];
- return firstScene
- ? `${firstScene.title} · ${firstScene.purpose}`
- : "这一章已生成场景预算合同。";
- }
- if (hasText(chapter.sceneCards)) {
- return "这章存在旧版场景拆解,建议重新生成。";
- }
- return "这一章还没有明确目标,适合先补章节计划。";
-}
-
-export function chapterSuggestedActionLabel(chapter: Chapter): string {
- if (chapterHasContinuableQualityLoop(chapter)) {
- return hasText(chapter.content) ? "继续下一章" : "写本章";
- }
- const status = resolveDisplayedChapterStatus(chapter);
- if (status === "generating") return "等待生成";
- if (status === "needs_repair") return "一键修复";
- if (status === "pending_review") {
- return chapter.generationState === "reviewed" || chapter.generationState === "approved"
- ? "查看建议"
- : "运行审校";
- }
- if (status === "completed") return "继续润色";
- if (status === "unplanned" || !chapterHasPreparationAssets(chapter)) return "补章节计划";
- if (!hasText(chapter.content) || status === "pending_generation") return "写本章";
- if (chapter.generationState === "drafted") return "运行审校";
- return "打开编辑器";
-}
-
-export function chapterMatchesQueueFilter(chapter: Chapter, filter: QueueFilterKey): boolean {
- const status = resolveDisplayedChapterStatus(chapter);
- if (filter === "all") return true;
- if (filter === "completed") {
- return status === "completed"
- || chapter.generationState === "approved"
- || chapter.generationState === "published";
- }
- if (filter === "review") {
- return status === "pending_review"
- || status === "needs_repair"
- || chapter.generationState === "drafted"
- || chapter.generationState === "reviewed";
- }
- if (filter === "setup") {
- return status === "unplanned" || (!chapterHasPreparationAssets(chapter) && !hasText(chapter.content));
+ .filter((item) => item.length > 0) .slice(0, 4); } export function hasText(input: string | null | undefined): boolean { return Boolean(input?.trim()); } export function chapterHasPreparationAssets(chapter: Chapter): boolean { return hasText(chapter.expectation) || hasText(chapter.taskSheet) || hasText(chapter.sceneCards); } export function parseChapterScenePlanForDisplay(chapter: Chapter) { return parseChapterScenePlan(chapter.sceneCards, { targetWordCount: chapter.targetWordCount ?? undefined, }); } export function resolveChapterQueuePreview(chapter: Chapter): string { if (hasText(chapter.expectation)) { return chapter.expectation!.trim(); } if (hasText(chapter.taskSheet)) { return chapter.taskSheet!.trim(); } const scenePlan = parseChapterScenePlanForDisplay(chapter); if (scenePlan) { const firstScene = scenePlan.scenes[0]; return firstScene ? `${firstScene.title} · ${firstScene.purpose}` : "This chapter has generated a scene budget contract."; } if (hasText(chapter.sceneCards)) { return "This chapter has an old version of scene breakdown, it is recommended to regenerate."; } return "This chapter does not yet have a clear goal, it is suitable to fill in the chapter plan first."; } export function chapterSuggestedActionLabel(chapter: Chapter): string { if (chapterHasContinuableQualityLoop(chapter)) { return hasText(chapter.content) ? "Continue to the next chapter" : "Write this chapter"; } const status = resolveDisplayedChapterStatus(chapter); if (status === "generating") return "Waiting for generation"; if (status === "needs_repair") return "One-click repair"; if (status === "pending_review") { return chapter.generationState === "reviewed" || chapter.generationState === "approved" ? "View suggestions" : "Run proofreading"; } if (status === "completed") return "Continue polishing"; if (status === "unplanned" || !chapterHasPreparationAssets(chapter)) return "Supplementing chapter plan"; if (!hasText(chapter.content) || status === "pending_generation") return "Writing this chapter"; if (chapter.generationState === "drafted") return "Run proofreading"; return "Open editor"; } export function chapterMatchesQueueFilter(chapter: Chapter, filter: QueueFilterKey): boolean { const status = resolveDisplayedChapterStatus(chapter); if (filter === "all") return true; if (filter === "completed") { return status === "completed" || chapter.generationState === "approved" || chapter.generationState === "published"; } if (filter === "review") { return status === "pending_review" || status === "needs_repair" || chapter.generationState === "drafted" || chapter.generationState === "reviewed"; } if (filter === "setup") { return status === "unplanned" || (!chapterHasPreparationAssets(chapter) && !hasText(chapter.content));
}
if (filter === "draft") {
return status === "pending_generation"
diff --git a/client/src/pages/novels/components/chapterInsights/ChapterExecutionInsightsSidebar.tsx b/client/src/pages/novels/components/chapterInsights/ChapterExecutionInsightsSidebar.tsx
index 3c40701f9..fef4b04bd 100644
--- a/client/src/pages/novels/components/chapterInsights/ChapterExecutionInsightsSidebar.tsx
+++ b/client/src/pages/novels/components/chapterInsights/ChapterExecutionInsightsSidebar.tsx
@@ -21,11 +21,11 @@ function DesktopSidebar(props: ChapterExecutionInsightsSidebarProps) {
- 章节侧栏
-
先看本章概览,再查看时间线、角色动态和资源风险。
+ Chapter sidebar
+
Start with an overview of the chapter, then review the timeline, character dynamics, and resource risks.
onAssetTabChange(value as AssetTabKey)}>
- 任务单
- 场景
- 质量
- 修复
- 上下文诊断
+ Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
+ Scene
+ quality
+ repair
+ Context Diagnostics
-
本章任务单
+
Chapter Task List
- {selectedChapter.taskSheet?.trim() || "暂无任务单。你可以先让 AI 生成任务单,再回来继续写这章。"}
+ {selectedChapter.taskSheet?.trim() || "No task sheet available. You can let the AI generate a task sheet first, then come back to continue writing this chapter."}
-
-
+
+
-
场景预算合同
+
Scene budget contract
-
-
+
+
{scenePlan.scenes.map((scene, index) => (
- 场景 {index + 1}
- {scene.targetWordCount} 字
+ Scene {index + 1}
+ {scene.targetWordCount} Character
{scene.title}
{scene.purpose}
-
-
-
-
+
+
+
+
{scene.forbiddenExpansion.length > 0 ? (
- 禁止展开:{scene.forbiddenExpansion.join(";")}
+ Expansion is prohibited:{scene.forbiddenExpansion.join(";")}
) : null}
@@ -190,11 +190,9 @@ export default function ChapterExecutionReferencePanel(props: ChapterExecutionRe
) : (
-
场景拆解
+
Scene dismantling
- {selectedChapter.sceneCards?.trim()
- ? "当前是旧版场景拆解文本,建议重新生成章节执行合同。"
- : "暂无场景拆解。"}
+ {selectedChapter.sceneCards?.trim() ? "Currently using the old scene breakdown text, it is recommended to regenerate the chapter execution contract." : "No scene breakdowns available."}
)}
@@ -202,16 +200,16 @@ export default function ChapterExecutionReferencePanel(props: ChapterExecutionRe
- {chapterResourceContext?.summary ?? "选中章节后会提示本章可用、需铺垫和不可直接使用的资源。"}
+ {chapterResourceContext?.summary ?? "After selecting a chapter, you will be prompted with resources that are available, require foreshadowing, and cannot be used directly in this chapter."}
- {timelineCheck ? `分数 ${Math.round(timelineCheck.score * 100)}` : "章节切换后会读取最新检查结果。"}
+ {timelineCheck ? `分数 ${Math.round(timelineCheck.score * 100)}` : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."}
@@ -199,29 +199,29 @@ export default function TimelinePanel(props: {
{context ? (
<>
}
items={context.openHooks.map((hook) => ({ title: hook.title, summary: hook.description }))}
- emptyText="没有需要承接的遗留钩子。"
+ emptyText="There are no legacy hooks to take over."
tone={context.openHooks.length > 0 ? "warning" : "default"}
/>
}
items={context.plannedEventsThisChapter.map((event) => ({ title: event.title, summary: event.summary }))}
- emptyText="本章暂未设置计划推进。"
+ emptyText="This chapter has no plans to advance."
/>
}
items={context.forbiddenEvents.map((item) => ({ title: item.title, summary: item.reason }))}
- emptyText="本章没有提前发生限制。"
+ emptyText="There are no early occurrence restrictions in this chapter."
tone={context.forbiddenEvents.length > 0 ? "critical" : "default"}
/>
- 最近关键事件
+ recent key events
{context.previousEvents.length > 0 ? (
@@ -233,18 +233,18 @@ export default function TimelinePanel(props: {
))}
) : (
-
没有可显示的最近事件。
+
No recent events to display.
)}
-
最新时间线检测
+
Latest timeline detection
>
) : (
- 选中章节后,这里显示本章时间锚点、上一章钩子、计划推进和禁止事项。
-
+ After selecting a chapter, the time anchor of this chapter, hook of the previous chapter, plan advancement and prohibited items are displayed here.
+
)}
);
diff --git a/client/src/pages/novels/components/characterAssetWorkspace.helpers.ts b/client/src/pages/novels/components/characterAssetWorkspace.helpers.ts
index 2cc5b93a6..c0a2275cf 100644
--- a/client/src/pages/novels/components/characterAssetWorkspace.helpers.ts
+++ b/client/src/pages/novels/components/characterAssetWorkspace.helpers.ts
@@ -1,33 +1,22 @@
import type { Character, CharacterCastRole, CharacterGender } from "@ai-novel/shared/types/novel";
-const CAST_ROLE_LABELS: Record = {
- protagonist: "主角",
- antagonist: "主对手",
- ally: "同盟",
- foil: "镜像角色",
- mentor: "导师",
- love_interest: "情感牵引",
- pressure_source: "压力源",
- catalyst: "催化者",
-};
-
-const CHARACTER_GENDER_LABELS: Record = {
- male: "男",
- female: "女",
- other: "其他",
- unknown: "未知",
+const CAST_ROLE_LABELS: Record = { protagonist: "protagonist", antagonist: "antagonist", ally: "ally", foil: "mirror character", mentor: "mentor", love_interest: "emotional attraction", pressure_source: "pressure source", catalyst: "catalyst", }; const CHARACTER_GENDER_LABELS: Record = {
+ male: "male",
+ female: "female",
+ other: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ unknown: "unknown",
};
export function getCastRoleLabel(castRole?: CharacterCastRole | null): string {
if (!castRole) {
- return "未定义";
+ return "undefined";
}
return CAST_ROLE_LABELS[castRole] ?? castRole;
}
export function getCharacterGenderLabel(gender?: CharacterGender | null): string {
if (!gender) {
- return "未知";
+ return "unknown";
}
return CHARACTER_GENDER_LABELS[gender] ?? gender;
}
diff --git a/client/src/pages/novels/components/characterPanel.utils.ts b/client/src/pages/novels/components/characterPanel.utils.ts
index 3869ca3ef..619cc6e05 100644
--- a/client/src/pages/novels/components/characterPanel.utils.ts
+++ b/client/src/pages/novels/components/characterPanel.utils.ts
@@ -1,9 +1,9 @@
import type { Character, CharacterTimeline } from "@ai-novel/shared/types/novel";
-const RELATION_POSITIVE_KEYWORDS = ["伙伴", "盟友", "信任", "守护", "亲密", "喜欢", "合作"];
-const RELATION_NEGATIVE_KEYWORDS = ["敌对", "对立", "怀疑", "背叛", "利用", "冲突", "压制"];
-const TREND_UP_KEYWORDS = ["升温", "缓和", "靠近", "修复", "合作加深", "信任增加"];
-const TREND_DOWN_KEYWORDS = ["恶化", "破裂", "紧张", "决裂", "冲突升级", "敌意加深"];
+const RELATION_POSITIVE_KEYWORDS = ["Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.", "ally", "trust", "guard", "close", "like", "cooperate"];
+const RELATION_NEGATIVE_KEYWORDS = ["hostility", "opposition", "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.", "betray", "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.", "conflict", "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."];
+const TREND_UP_KEYWORDS = ["heating up", "ease", "near", "repair", "Cooperation deepens", "Trust increases"];
+const TREND_DOWN_KEYWORDS = ["Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.", "rupture", "nervous", "break", "Conflict escalates", "Hostility deepens"];
function compactText(input: string | null | undefined): string {
return (input ?? "").trim();
@@ -55,7 +55,7 @@ export function buildCharacterProfileFromWizard(payload: QuickCharacterCreatePay
.split(/[,,\s]+/g)
.map((item) => item.trim())
.filter((item) => item.length > 0);
- const keywordText = keywordList.length > 0 ? keywordList.join("、") : "待补充";
+ const keywordText = keywordList.length > 0 ? keywordList.join("、") : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
const personality = `核心特征:${keywordText}`;
const background = joinSegments([
@@ -73,39 +73,39 @@ export function buildCharacterProfileFromWizard(payload: QuickCharacterCreatePay
personality: personality || undefined,
background: background || undefined,
development: development || undefined,
- currentState: payload.relationToProtagonist ? `关系推进中(${payload.relationToProtagonist})` : "待上场",
- currentGoal: payload.storyFunction || "推动主线关键节点",
+ currentState: payload.relationToProtagonist ? `关系推进中(${payload.relationToProtagonist})` : "Waiting to play",
+ currentGoal: payload.storyFunction || "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
};
}
function inferCurrentRelation(source: string): string {
if (!source) {
- return "待定义";
+ return "To be defined";
}
const positiveHits = countHits(source, RELATION_POSITIVE_KEYWORDS);
const negativeHits = countHits(source, RELATION_NEGATIVE_KEYWORDS);
if (positiveHits > negativeHits) {
- return "合作 / 亲近";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
if (negativeHits > positiveHits) {
- return "对立 / 紧张";
+ return "opposition / tension";
}
- return "复杂 / 待观察";
+ return "Complex / to be seen";
}
function inferTrend(source: string): string {
if (!source) {
- return "待观察";
+ return "To be seen";
}
const upHits = countHits(source, TREND_UP_KEYWORDS);
const downHits = countHits(source, TREND_DOWN_KEYWORDS);
if (upHits > downHits) {
- return "升温";
+ return "heating up";
}
if (downHits > upHits) {
- return "恶化";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
- return "平稳";
+ return "smooth";
}
function includesCharacterName(source: string, characterName: string): boolean {
@@ -117,7 +117,7 @@ function includesCharacterName(source: string, characterName: string): boolean {
function buildLatestEvidence(event?: CharacterTimeline): string {
if (!event) {
- return "暂无章节证据";
+ return "No chapter evidence yet";
}
const excerpt = compactText(event.content).slice(0, 36);
return excerpt.length > 0 ? excerpt : event.title;
diff --git a/client/src/pages/novels/components/characterPanel/CharacterCreateDialog.tsx b/client/src/pages/novels/components/characterPanel/CharacterCreateDialog.tsx
index ad63917c4..787ca45b1 100644
--- a/client/src/pages/novels/components/characterPanel/CharacterCreateDialog.tsx
+++ b/client/src/pages/novels/components/characterPanel/CharacterCreateDialog.tsx
@@ -73,21 +73,21 @@ export default function CharacterCreateDialog(props: CharacterCreateDialogFullPr
- 新增角色
+ Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
- 适合快速补齐阵容占位。创建后可在角色资产控制台继续完善档案、外显、资源和事件。
-
+ Suitable for quickly filling up the lineup. After creation, you can continue to improve the profile, appearance, resources and events in the character asset console.
+
-
快速创建
+
Create quickly
- 先建立可上场的人物,再让 AI 补齐性格、背景和当前目标。
-
+ Create a playable character first, and then let the AI complete the personality, background, and current goals.
+
setSupplementalPrompt(event.target.value)}
/>
@@ -215,18 +215,18 @@ export default function SupplementalCharacterDialog(props: SupplementalCharacter
checked={supplementalUseWorldContext}
onChange={(event) => setSupplementalUseWorldContext(event.target.checked)}
/>
- 基于本书世界生成
-
+ Generate a world based on this book
+
- {isGeneratingSupplementalCharacters ? "生成中..." : "生成补充角色候选"}
+ {isGeneratingSupplementalCharacters ? "Generating..." : "Generate candidates for complementary roles"}
- 数量不选时由 AI 判断
- 关系角色会优先围绕现有角色补位
+ If the quantity is not selected, it will be judged by AI.
+ Relationship roles will be filled first around existing roles.
Relationship with the protagonist:{candidate.relationToProtagonist || "AI unspecified"}
+
External goals:{candidate.outerGoal || "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."}
+
Current goals:{candidate.currentGoal || "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."}
-
第一印象:{candidate.firstImpression || "待补全"}
-
核心恐惧:{candidate.fear || "待补全"}
-
错误信念:{candidate.misbelief || "待补全"}
-
补位原因:{candidate.whyNow || "AI 未额外说明"}
+
First impression:{candidate.firstImpression || "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."}
+
Core Fear:{candidate.fear || "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."}
+
False belief:{candidate.misbelief || "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."}
+
Reason for replacement:{candidate.whyNow || "AI No additional explanation"}
AI will combine the character profile, relationships, plots that have occurred, and the current situation to sort out the character perspective that needs to be understood for this conversation.
{props.isRefreshing ? "Organizing..." : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."}
{props.error ?
{props.error instanceof Error ? props.error.message : "Scene organization cannot be completed temporarily, please try again later."}
+ This maintains the most stable data when the character enters the chapter generation. The current status and current goals will affect the judgment of actions in subsequent chapters.
+
{mode === "current" ? (
- 当前角色在左侧,直接关系向右展开;点击连线查看关系细节。
-
+ The current role is on the left, and the direct relationship expands to the right; click on the connection to view the relationship details.
+
) : null}
@@ -274,7 +274,7 @@ function CharacterRelationshipNode(props: NodeProps) {
const character = graphNode.character;
const isProtagonist = isProtagonistCharacter(character);
const tone = getNodeTone(character);
- const shortName = character.name.trim().slice(0, 2) || "角";
+ const shortName = character.name.trim().slice(0, 2) || "horn";
return (
- 核心
-
+ core
+
) : null}
- {character.currentGoal || character.storyFunction || character.relationToProtagonist || "待补全角色目标"}
+ {character.currentGoal || character.storyFunction || character.relationToProtagonist || "Character goals to be completed"}
@@ -396,8 +396,8 @@ function RelationshipDetailPanel(props: {
) : (
- 点击角色或关系线查看详情。
-
+ Click on a character or relationship line to view details.
+
)}
);
@@ -416,17 +416,17 @@ function NodeDetail(props: { node: RelationshipGraphNode }) {
Reader information:{resource.readerKnows ? "Informed" : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."}
+ Key props, clues, identity credentials or trump cards will be deposited here after the chapter is written; temporary characters only retain resources that will affect subsequent chapters.
+
)}
);
diff --git a/client/src/pages/novels/components/characterWorkspace/CharacterTimelineTab.tsx b/client/src/pages/novels/components/characterWorkspace/CharacterTimelineTab.tsx
index 638f5cc70..31ab63fe5 100644
--- a/client/src/pages/novels/components/characterWorkspace/CharacterTimelineTab.tsx
+++ b/client/src/pages/novels/components/characterWorkspace/CharacterTimelineTab.tsx
@@ -18,17 +18,17 @@ export default function CharacterTimelineTab(props: CharacterTimelineTabProps) {
-
角色事件流
+
character event flow
- 用最近章节事件观察角色处境变化,必要时同步时间线后再继续写作。
-
+ Use the events of recent chapters to observe the changes in the character's situation, and synchronize the timeline if necessary before continuing to write.
+
- {isSyncingTimeline ? "同步中..." : "同步角色时间线"}
+ {isSyncingTimeline ? "Syncing..." : "Syncing character timeline"}
- {isSyncingAllTimeline ? "同步中..." : "同步全部角色时间线"}
+ {isSyncingAllTimeline ? "Syncing..." : "Syncing all character timelines"}
+ Working on "{selectedCharacter.name}"Organize appearance, body posture, voice and appearance memory points.
+
) : null}
{hasVisibleProfileSuggestionForSelected && visibleProfileSuggestion ? (
@@ -101,21 +101,21 @@ export default function CharacterVisibleProfileTab(props: CharacterVisibleProfil
: `“${visibleProfileSuggestion.characterName}”当前没有可写入的外显资料`}
- 请先看下面差异,确认后点击保存到角色卡。
-
+ Please look at the differences below first, confirm and click Save to Character Card.
+
- {isApplyingVisibleProfile ? "保存中..." : "保存到角色卡"}
+ {isApplyingVisibleProfile ? "Saving..." : "Saving to Character Card"}
{visibleProfileSuggestion.warnings.length > 0 ? (
+ After clicking "AI Complete Explicit Data", the differences to be saved will be displayed here first; after confirmation, they will be saved to the character card.
+
) : null}
{batchVisibleProfileResult ? (
-
批量建议:{batchApplicableCount} 个角色可写入
+
Bulk suggestions:{batchApplicableCount} characters can be written
{selectedCharacter[field.key] || "To be completed"}
))}
diff --git a/client/src/pages/novels/components/characterWorkspace/characterRelationshipGraphModel.ts b/client/src/pages/novels/components/characterWorkspace/characterRelationshipGraphModel.ts
index f98726e9c..ed525b18a 100644
--- a/client/src/pages/novels/components/characterWorkspace/characterRelationshipGraphModel.ts
+++ b/client/src/pages/novels/components/characterWorkspace/characterRelationshipGraphModel.ts
@@ -89,9 +89,9 @@ export function buildRelationshipGraphModel(input: {
id: pairKey,
source: relation.sourceCharacterId,
target: relation.targetCharacterId,
- sourceName: relation.sourceCharacterName || characterById.get(relation.sourceCharacterId)?.name || "未知角色",
- targetName: relation.targetCharacterName || characterById.get(relation.targetCharacterId)?.name || "未知角色",
- label: compactText(relation.dynamicLabel) || compactText(relation.surfaceRelation) || "关系",
+ sourceName: relation.sourceCharacterName || characterById.get(relation.sourceCharacterId)?.name || "unknown role",
+ targetName: relation.targetCharacterName || characterById.get(relation.targetCharacterId)?.name || "unknown role",
+ label: compactText(relation.dynamicLabel) || compactText(relation.surfaceRelation) || "relation",
staticRelation: relation,
dynamicStages: [],
isDynamic: false,
@@ -118,9 +118,9 @@ export function buildRelationshipGraphModel(input: {
id: pairKey,
source: stage.sourceCharacterId,
target: stage.targetCharacterId,
- sourceName: stage.sourceCharacterName || characterById.get(stage.sourceCharacterId)?.name || "未知角色",
- targetName: stage.targetCharacterName || characterById.get(stage.targetCharacterId)?.name || "未知角色",
- label: compactText(stage.stageLabel) || "关系阶段",
+ sourceName: stage.sourceCharacterName || characterById.get(stage.sourceCharacterId)?.name || "unknown role",
+ targetName: stage.targetCharacterName || characterById.get(stage.targetCharacterId)?.name || "unknown role",
+ label: compactText(stage.stageLabel) || "relationship stage",
dynamicStages: [stage],
isDynamic: true,
isHighTension: stageTension,
@@ -142,7 +142,7 @@ export function buildRelationshipGraphModel(input: {
target: character.id,
sourceName: protagonist.name,
targetName: character.name,
- label: compactText(character.relationToProtagonist) || "与主角关系",
+ label: compactText(character.relationToProtagonist) || "relationship with protagonist",
dynamicStages: [],
isDynamic: false,
isHighTension: /敌|压|冲突|背叛|利用|怀疑|监视|威胁|秘密|隐瞒/.test(character.relationToProtagonist ?? ""),
diff --git a/client/src/pages/novels/components/characterWorkspace/characterWorkspace.helpers.ts b/client/src/pages/novels/components/characterWorkspace/characterWorkspace.helpers.ts
index 0b8bece05..b8c74c3a8 100644
--- a/client/src/pages/novels/components/characterWorkspace/characterWorkspace.helpers.ts
+++ b/client/src/pages/novels/components/characterWorkspace/characterWorkspace.helpers.ts
@@ -7,34 +7,34 @@ export const VISIBLE_PROFILE_FIELDS: Array<{
label: string;
placeholder: string;
}> = [
- { key: "appearance", label: "样貌记忆点", placeholder: "眉眼、发型、表情习惯等能被读者记住的样貌特征" },
- { key: "physique", label: "体态基底", placeholder: "年龄感、身形、行动姿态、身体状态基底" },
- { key: "attireStyle", label: "常见穿着", placeholder: "日常穿着、身份外观、阶层或职业痕迹" },
- { key: "signatureDetail", label: "标志细节", placeholder: "标志物、动作、微习惯、气味或反复可用的细节" },
- { key: "voiceTexture", label: "声音口吻", placeholder: "声线、说话节奏、句式习惯、口吻" },
- { key: "presenceImpression", label: "登场印象", placeholder: "首次或常规登场时给读者的直观感受" },
+ { key: "appearance", label: "Appearance memory point", placeholder: "Eyebrows, hairstyle, facial expressions and other facial features that can be remembered by readers" },
+ { key: "physique", label: "body base", placeholder: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
+ { key: "attireStyle", label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.", placeholder: "Daily wear, status appearance, class or occupation traces" },
+ { key: "signatureDetail", label: "Logo details", placeholder: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
+ { key: "voiceTexture", label: "tone of voice", placeholder: "Voice, speaking rhythm, sentence patterns, tone of voice" },
+ { key: "presenceImpression", label: "First impression", placeholder: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
];
export function getSecretStatus(selectedCharacter?: Character): string {
if (!selectedCharacter) {
- return "暂无";
+ return "None yet";
}
if (selectedCharacter.secret?.trim()) {
- return "存在明确秘密";
+ return "There is a clear secret";
}
const runtimeSignal = `${selectedCharacter.currentState ?? ""} ${selectedCharacter.currentGoal ?? ""}`;
- return /秘密|隐瞒|卧底|伪装/.test(runtimeSignal) ? "已隐藏关键信息" : "暂无显性秘密";
+ return /秘密|隐瞒|卧底|伪装/.test(runtimeSignal) ? "Key information has been hidden" : "No explicit secret yet";
}
export function getEmotionSignal(selectedCharacter?: Character): string {
const runtimeSignal = `${selectedCharacter?.currentState ?? ""} ${selectedCharacter?.currentGoal ?? ""}`;
if (/愤|怒|焦虑|崩溃|绝望/.test(runtimeSignal)) {
- return "高压";
+ return "high pressure";
}
if (/平静|稳|冷静|从容/.test(runtimeSignal)) {
- return "平稳";
+ return "smooth";
}
- return "待观察";
+ return "To be seen";
}
export function getResourceDisplayMode(character?: Character): {
@@ -46,16 +46,16 @@ export function getResourceDisplayMode(character?: Character): {
const roleText = `${character?.role ?? ""} ${character?.castRole ?? ""}`;
if (isProtagonistCharacter(character)) {
return {
- label: "主角完整资源",
- helper: "主角会完整展示道具、线索、身份凭证、底牌和消耗状态,后续章节会优先参考这些行动边界。",
+ label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ helper: "The protagonist will fully display the props, clues, identity credentials, trump cards and consumption status. Subsequent chapters will give priority to referring to these action boundaries.",
limit: 10,
shouldShowResource: () => true,
};
}
if (/临时|路人|客串|一次性/.test(roleText)) {
return {
- label: "临时角色资源",
- helper: "临时角色只展示会跨章复用、牵动冲突、绑定伏笔或被主角带走的资源。",
+ label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ helper: "Temporary characters only display resources that will be reused across chapters, affect conflicts, bind foreshadowing, or be taken away by the protagonist.",
limit: 5,
shouldShowResource: (item) => (
item.narrativeFunction === "promise"
@@ -66,8 +66,8 @@ export function getResourceDisplayMode(character?: Character): {
};
}
return {
- label: "长期角色关键资源",
- helper: "长期角色优先展示会改变行动选择、关系筹码、读者知情或伏笔兑现的资源。",
+ label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ helper: "Long-term characters prioritize resources that change action choices, relationship stakes, reader knowledge, or foreshadowing.",
limit: 6,
shouldShowResource: (item) => item.status !== "stale",
};
@@ -75,30 +75,30 @@ export function getResourceDisplayMode(character?: Character): {
export function getResourceStatusLabel(status: CharacterResourceLedgerItem["status"]): string {
const labels: Record = {
- available: "可用",
- hidden: "隐藏",
- borrowed: "借用",
- transferred: "转交",
- lost: "丢失",
- consumed: "已消耗",
- damaged: "受损",
- destroyed: "毁坏",
- stale: "淡出",
+ available: "Available",
+ hidden: "hide",
+ borrowed: "borrow",
+ transferred: "transfer",
+ lost: "lost",
+ consumed: "Consumed",
+ damaged: "damaged",
+ destroyed: "destruction",
+ stale: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
};
return labels[status] ?? status;
}
export function getResourceFunctionLabel(value: CharacterResourceLedgerItem["narrativeFunction"]): string {
const labels: Record = {
- tool: "工具",
- clue: "线索",
- weapon: "武器",
- proof: "证据",
- key: "钥匙",
- cost: "代价",
- promise: "伏笔",
- hidden_card: "底牌",
- constraint: "限制",
+ tool: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ clue: "clue",
+ weapon: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ proof: "evidence",
+ key: "key",
+ cost: "cost",
+ promise: "Foreshadowing",
+ hidden_card: "trump card",
+ constraint: "limit",
};
return labels[value] ?? value;
}
diff --git a/client/src/pages/novels/components/cover/NovelCoverCard.tsx b/client/src/pages/novels/components/cover/NovelCoverCard.tsx
index 8862e72d4..edac94958 100644
--- a/client/src/pages/novels/components/cover/NovelCoverCard.tsx
+++ b/client/src/pages/novels/components/cover/NovelCoverCard.tsx
@@ -57,26 +57,26 @@ export function NovelCoverCard(props: NovelCoverCardProps) {
-
小说封面主画面
+
Novel cover main screen
- 先生成这本书的封面主画面。当前阶段不直接生成可用书名字体,后续仍可继续排版成正式封面。
-
+ First generate the main cover image of this book. The current stage does not directly generate usable book title fonts, but it can continue to be typeset into a formal cover in the future.
+
+ There is no main cover screen yet. Click the button above, and the system will first organize a draft of the cover based on the current novel information.
+
) : null}
{primaryAsset ? (
@@ -85,7 +85,7 @@ export function NovelCoverCard(props: NovelCoverCardProps) {
@@ -95,14 +95,11 @@ export function NovelCoverCard(props: NovelCoverCardProps) {
- 当前主封面
-
- 共 {assets.length} 张候选图
-
-
-
- 主封面会随图片域里的 `isPrimary` 切换,不会把封面状态写死到小说主表里。
-
+ Current main cover
+
+ A total of {assets.length} candidate images
+ The main cover will be switched with `isPrimary` in the image field, and the cover status will not be written into the main table of the novel.
+
diff --git a/client/src/pages/novels/components/cover/NovelCoverDialog.tsx b/client/src/pages/novels/components/cover/NovelCoverDialog.tsx
index a0c50138d..40d902c1c 100644
--- a/client/src/pages/novels/components/cover/NovelCoverDialog.tsx
+++ b/client/src/pages/novels/components/cover/NovelCoverDialog.tsx
@@ -33,16 +33,7 @@ import {
} from "./novelCoverDraft";
import SelectControl from "@/components/common/SelectControl";
-const IMAGE_STATUS_TEXT: Record = {
- queued: "排队中",
- running: "生成中",
- succeeded: "生成成功",
- failed: "生成失败",
- cancelled: "已取消",
-};
-
-type DirectPromptSource = "optimized" | "manual";
-type CoverSize = NonNullable;
+const IMAGE_STATUS_TEXT: Record = { queued: "queued", running: "generating", succeeded: "generating successfully", failed: "generating failed", cancelled: "cancelled", }; type DirectPromptSource = "optimized" | "manual"; type CoverSize = NonNullable;
interface GenreOption {
id: string;
@@ -187,8 +178,8 @@ export function NovelCoverDialog(props: NovelCoverDialogProps) {
const hasDirectPrompt = directPrompt.trim().length > 0;
const currentSendModeLabel = promptMode === "direct"
- ? (directPromptSource === "optimized" ? "AI优化 Prompt" : "手动编辑 Prompt")
- : "原链路 Prompt";
+ ? (directPromptSource === "optimized" ? "AI Optimization Prompt" : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.")
+ : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
const currentSendModeClass = promptMode === "direct"
? (directPromptSource === "optimized"
? "rounded-full bg-emerald-50 px-3 py-1 text-emerald-700"
@@ -264,7 +255,7 @@ export function NovelCoverDialog(props: NovelCoverDialogProps) {
const generateMutation = useMutation({
mutationFn: async () => {
if (!imageForm.provider) {
- throw new Error("请先在系统设置里配置支持图像生成的厂商和模型。");
+ throw new Error("Please configure the manufacturers and models that support image generation in the system settings first.");
}
return generateNovelCover({
sceneType: "novel_cover",
@@ -309,7 +300,7 @@ export function NovelCoverDialog(props: NovelCoverDialogProps) {
const activeTask = activeTaskQuery.data?.data;
const handleDeleteAsset = async (asset: ImageAsset) => {
- const confirmed = window.confirm("确认删除这张封面图?如果它是当前主封面,系统会自动补一张新的主图。");
+ const confirmed = window.confirm("Are you sure you want to delete this cover image? If it is the current main cover, the system will automatically add a new main image.");
if (!confirmed) {
return;
}
@@ -329,37 +320,37 @@ export function NovelCoverDialog(props: NovelCoverDialogProps) {
- 生成小说封面主画面
- {promptContext.title ? `:${promptContext.title}` : ""}
+ Generate novel cover main screen
+ {promptContext.title ? `:${promptContext.title}` : ""}
{imageProviderOptions.length === 0 ? (
-
还不能开始生成
+
Cannot start generating yet
- 当前没有已配置的图像模型。请先到
- {" "}
+ There are currently no image models configured. Please come first
+ {" "}
- 系统设置
-
+ System settings
+
{" "}
- 补全支持图像生成的厂商和模型,再回到这里继续。
-
+ Complete the vendors and models that support image generation, and then come back here to continue.
+
+ The system has compiled a draft of the cover input based on the basic information of the current novel. You can change it directly, or you can click "AI Optimization Prompt" first and then continue to adjust it manually.
+
- 当前发送模式
+ Current sending mode{currentSendModeLabel}
@@ -423,10 +414,10 @@ export function NovelCoverDialog(props: NovelCoverDialogProps) {
-
最终发送 Prompt 预览
+
Finally send Prompt preview
- 这里展示最终会发送给图像模型的 prompt。你可以直接编辑,也可以在 AI 优化后继续做细调。
-
+ Shown here is the prompt that will eventually be sent to the image model. You can edit directly or continue to make fine adjustments after AI optimization.
+
+ It will automatically return here after the generation is successful. The first successful image will be automatically set as the main image if there is currently no main cover.
+
+ No cover image yet. Submit the generation task first, and it will appear here after success.
+
) : null}
{assets.length > 0 ? (
@@ -576,7 +567,7 @@ export function NovelCoverDialog(props: NovelCoverDialogProps) {
) : null}
@@ -342,10 +342,10 @@ export function DirectorAutoExecutionPlanFields({
-
正文生成后自动审核
+
Automatically review the text after it is generated
- 关闭后,正文生成完成即结束当前章节,质量校验交给你手动处理。
-
+ After closing, the text generation is completed and the current chapter ends, and the quality verification is left to you to handle manually.
+
-
审核不通过时自动修复
+
Automatically repair when the review fails
- 只在开启自动审核后生效;关闭时会保留问题,等待你手动处理或重跑。
-
+ It only takes effect after automatic review is turned on; when it is turned off, the problem will be retained, waiting for you to handle it manually or rerun it.
+
onChange({ autoRepair: checked })}
- aria-label="切换审核后是否自动修复"
+ aria-label="Switch whether to automatically repair after auditing"
/>
- 系统会按你选定的范围,自动准备节奏板、拆章和章节执行资源,再继续写作。
- 当前质量策略:{reviewLabel}。
+ The system will automatically prepare the rhythm board, chapter breaking and chapter execution resources according to the range you selected, and then continue writing.
+ Current quality strategy:{reviewLabel}。
- {props.hasAnyNovel ? "没有符合筛选条件的小说" : "还没有小说项目"}
+ {props.hasAnyNovel ? "No novels matching the filter criteria" : "No novel projects yet"}
- {props.hasAnyNovel
- ? "可以切换上方筛选条件,或者创建一个新的小说项目。"
- : "第一次使用时,推荐让 AI 自动导演先整理方向、角色、世界观和章节准备。"}
+ {props.hasAnyNovel ? "You can switch the filter criteria above, or create a new novel project." : "For first-time use, it's recommended to let the AI director organize the direction, characters, world view, and chapter preparation."}
- AI 自动导演开书
+ AI automatic director opens book
- 手动创建小说
+ Create a novel manually
diff --git a/client/src/pages/novels/components/list/NovelListFilterBar.tsx b/client/src/pages/novels/components/list/NovelListFilterBar.tsx
index c3c02ff6d..240811fa1 100644
--- a/client/src/pages/novels/components/list/NovelListFilterBar.tsx
+++ b/client/src/pages/novels/components/list/NovelListFilterBar.tsx
@@ -9,15 +9,15 @@ export function NovelListFilterBar(props: {
}) {
return (
-
- props.onStatusChange("all")}>全部
- props.onStatusChange("draft")}>草稿
- props.onStatusChange("published")}>已发布
+
+ props.onStatusChange("all")}>Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
+ props.onStatusChange("draft")}>draft
+ props.onStatusChange("published")}>Published
-
- props.onWritingModeChange("all")}>全部
- props.onWritingModeChange("original")}>原创
- props.onWritingModeChange("continuation")}>续写
+
+ props.onWritingModeChange("all")}>Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
+ props.onWritingModeChange("original")}>Original
+ props.onWritingModeChange("continuation")}>Continue writing
);
diff --git a/client/src/pages/novels/components/list/NovelListHeader.tsx b/client/src/pages/novels/components/list/NovelListHeader.tsx
index aa1307763..97140fa93 100644
--- a/client/src/pages/novels/components/list/NovelListHeader.tsx
+++ b/client/src/pages/novels/components/list/NovelListHeader.tsx
@@ -21,10 +21,10 @@ export function NovelListHeader(props: {
-
小说列表
+
Novel list
- 管理正在推进的小说项目,快速判断哪些可以继续写、哪些需要先处理状态。
-
+ Manage ongoing novel projects and quickly determine which ones can be continued and which ones need to be processed first.
+
@@ -32,18 +32,18 @@ export function NovelListHeader(props: {
- AI 自动导演开书
-
+ AI automatic director opens book
+
- 手动创建小说
+ Create a novel manually
+ There is no rush to write chapters here, but to first define why readers follow the story, how the long-term confrontation escalates, how the protagonist changes, and what nodes must be fulfilled in the early, middle and late stages.
+
- {tab.isDecomposing ? "生成中..." : tab.hasPlan ? "重新生成故事引擎" : "生成故事引擎"}
+ {tab.isDecomposing ? "Generating..." : tab.hasPlan ? "Regenerate the story engine" : "Generate story engine"}
- {tab.isBuilding ? "构建中..." : "构建约束引擎"}
+ {tab.isBuilding ? "Under construction..." : "Build a constraint engine"}
- {tab.isSaving ? "保存中..." : "保存修改"}
+ {tab.isSaving ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : "Save changes"}
-
故事想法输入
+
Story idea input
tab.onStoryInputChange(event.target.value)}
- placeholder="用自然语言描述故事想法、想要的压迫感、想避免的风格和结局倾向。"
+ placeholder="Use natural language to describe story ideas, desired pressures, styles and ending tendencies you want to avoid."
className={textareaClassName("min-h-36")}
/>
{tab.message ? (
@@ -236,21 +236,21 @@ export default function StoryEngineStudio({ tab }: StoryEngineStudioProps) {
-
读者追更理由
+
Reasons for readers to follow updates
- {previewText(tab.decomposition.selling_point, "等待生成一句话卖点")}
+ {previewText(tab.decomposition.selling_point, "Waiting to generate a sentence selling point")}
-
长期压力源
+
chronic stressors
- {previewText(tab.decomposition.core_conflict, "等待生成长期对立")}
+ {previewText(tab.decomposition.core_conflict, "Waiting to generate long-term confrontation")}
+ This set of fields will enter subsequent characters, volume strategies, rhythm chapters and chapter tasks, and is a core asset that determines whether the story can continue to advance.
+
- 满意的字段先锁定
+ The fields you are satisfied with are locked first.
- 再局部重生成
+ regenerate locally
- {target.summary} 可输入范围:第 {target.startOrder}-{target.maxOrder} 章。
+ The input range for {target.summary} is: Chapters {target.startOrder}-{target.maxOrder}.
The current project information will be input as an automatic director
{lines.length > 0 ? lines.map((line) => (
@@ -16,8 +16,8 @@ export default function TakeoverContextSummaryPanel({ lines }: TakeoverContextSu
)) : (
- 当前信息较少,建议至少补一句故事概述或书级卖点后再接管。
-
+ There is currently little information, so it is recommended to add at least one story summary or book-level selling point before taking over.
+
)}
diff --git a/client/src/pages/novels/components/takeover/TakeoverDiagnosisPanel.tsx b/client/src/pages/novels/components/takeover/TakeoverDiagnosisPanel.tsx
index e4b289f8e..8dab8f9d3 100644
--- a/client/src/pages/novels/components/takeover/TakeoverDiagnosisPanel.tsx
+++ b/client/src/pages/novels/components/takeover/TakeoverDiagnosisPanel.tsx
@@ -47,8 +47,8 @@ export default function TakeoverDiagnosisPanel({
- {isLoadingTaskSnapshot ? "正在读取当前任务的详细进度..." : inspection.summary}
+ {isLoadingTaskSnapshot ? "Reading the detailed progress of the current task..." : inspection.summary}
+ Failed to read the detailed progress of the current task. The project asset physical examination has been displayed first.
+
) : null}
);
diff --git a/client/src/pages/novels/components/titleWorkshop/NovelCreateTitleQuickFill.tsx b/client/src/pages/novels/components/titleWorkshop/NovelCreateTitleQuickFill.tsx
index ef37f3f19..7233864fd 100644
--- a/client/src/pages/novels/components/titleWorkshop/NovelCreateTitleQuickFill.tsx
+++ b/client/src/pages/novels/components/titleWorkshop/NovelCreateTitleQuickFill.tsx
@@ -74,7 +74,7 @@ function renderLibraryDescription(entry: TitleLibraryEntry): string {
if (entry.keywords?.trim()) {
return `关键词:${truncateText(entry.keywords, 80)}`;
}
- return "标题库候选,可直接写入当前创建表单。";
+ return "Title library candidates can be written directly to the current creation form.";
}
function joinKeywords(...values: Array): string | null {
@@ -132,7 +132,7 @@ export default function NovelCreateTitleQuickFill({
const generateMutation = useMutation({
mutationFn: async () => {
if (!hasGenerationContext) {
- throw new Error("请先填写一句标题简报,或补一个参考标题后再生成。");
+ throw new Error("Please fill in a brief title first, or fill in a reference title before generating it.");
}
const response = await generateTitleIdeas({
mode: generationMode,
@@ -164,36 +164,36 @@ export default function NovelCreateTitleQuickFill({
}),
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: queryKeys.titles.all });
- toast.success("标题已加入标题库。");
+ toast.success("The title has been added to the title library.");
},
});
const handleApplyTitle = (title: string, source: "generated" | "library") => {
onApplyTitle(title);
setOpen(false);
- toast.success(source === "generated" ? "标题候选已写入创建表单。" : "标题库标题已写入创建表单。");
+ toast.success(source === "generated" ? "Title candidates are written to the create form." : "The title bank title has been written to the create form.");
};
const handleCopySuggestion = async (suggestion: TitleFactorySuggestion) => {
await navigator.clipboard.writeText(suggestion.title);
- toast.success("标题已复制到剪贴板。");
+ toast.success("Title copied to clipboard.");
};
return (
<>
setOpen(true)}>
- 标题快速选填
-
+ Quick title selection
+
- 标题快速选填
+ Quick title selection
- 不做绑定关系,只是帮你更快把标题写进创建表单。可以直接生成候选,也可以从标题库挑一个回填。
-
+ There is no binding relationship, it just helps you write the title into the creation form faster. Candidates can be generated directly, or you can backfill by selecting one from the title library.
+
- 快速生成
- 标题库选择
+ Quickly generate
+ Title library selection
+ Priority will be given to reading the introduction, subject matter, writing style, rhythm and narrative perspective that have been filled in the currently created page. You can also add a brief summary below without having to go back to fill in the form first.
+
setManualBrief(event.target.value)}
- placeholder="例如:末世废土里,一个被流放的维修师意外掌握古代机甲核心,想要标题更有硬核设定感和命运感。"
+ placeholder="For example: In the apocalyptic wasteland, an exiled maintenance engineer accidentally mastered the core of an ancient mecha. He wanted the title to have a more hard-core setting and sense of destiny."
/>
- 这里只影响这一次生成,不会自动回写到小说创建表单。
-
+ This only affects this generation and will not automatically write back to the novel creation form.
+
@@ -241,33 +241,33 @@ export default function NovelCreateTitleQuickFill({
htmlFor="novel-create-title-reference"
className="text-sm font-medium text-foreground"
>
- 参考标题
-
+ Reference title
+
setReferenceTitle(event.target.value)}
- placeholder="可选,填了会按参考改编式生成"
+ placeholder="Optional, if filled in, it will be generated according to the reference adaptation formula."
/>
{referenceTitle.trim()
- ? "当前会参考你输入的标题节奏和命名结构,再结合这本小说的信息重新产出候选。"
- : "留空时会按简报直接生成。如果你心里已经有一个风格方向,可以在这里填参考标题。"}
+ ? "Currently, candidates will be regenerated based on the title rhythm and naming structure you entered, and combined with the information of the novel."
+ : "If left blank, it will be generated directly according to the briefing. If you already have a style direction in mind, you can fill in a reference title here."}
-
当前已自动读取的创建页信息
+
The current created page information that has been automatically read
- {autoBrief || "创建页里暂时还没有足够的信息。你可以直接在上面的“补充标题简报”里写一句题材、卖点或冲突再生成。"}
+ {autoBrief || "There is not enough information in the creation page yet. You can directly write a theme, selling point or conflict in the \"Supplementary Title Brief\" above and then generate it."}
+ At least add a title summary first, or fill in a reference title; if there is already an introduction, type or style of writing on the creation page, it will also be automatically generated.
+
) : null}
handleApplyTitle(suggestion.title, "generated")}
onCopy={handleCopySuggestion}
onSave={(suggestion) => saveMutation.mutate(suggestion)}
savingTitle={saveMutation.isPending ? saveMutation.variables?.title ?? "" : ""}
- emptyMessage="可以直接在上面的补充标题简报里写一句题材或卖点,再点一次生成,结果会直接作为创建页的标题候选。"
+ emptyMessage="Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."
/>
-
从标题库快速选用
+
Quickly select from title library
- 默认按点击率排序
- {basicForm.genreId ? ",并按当前题材基底过滤" : ""}
+ Sort by click rate by default
+ {basicForm.genreId ? ", and filter based on the current theme" : ""}
。
setSearch(event.target.value)}
- placeholder="搜索标题关键词"
+ placeholder="Search title keywords"
className="md:max-w-xs"
/>
@@ -365,8 +365,8 @@ export default function NovelCreateTitleQuickFill({
handleApplyTitle(entry.title, "library")}>
- 填入标题
-
+ Fill in the title
+
diff --git a/client/src/pages/novels/components/titleWorkshop/NovelTitleWorkshop.tsx b/client/src/pages/novels/components/titleWorkshop/NovelTitleWorkshop.tsx
index 071e62008..ab9cd9634 100644
--- a/client/src/pages/novels/components/titleWorkshop/NovelTitleWorkshop.tsx
+++ b/client/src/pages/novels/components/titleWorkshop/NovelTitleWorkshop.tsx
@@ -58,7 +58,7 @@ export default function NovelTitleWorkshop({
}),
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: queryKeys.titles.all });
- toast.success("标题已加入标题库。");
+ toast.success("The title has been added to the title library.");
},
});
@@ -71,14 +71,14 @@ export default function NovelTitleWorkshop({
}),
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: queryKeys.titles.all });
- toast.success("当前标题已加入标题库。");
+ toast.success("The current title has been added to the title library.");
},
});
const handleCopy = async (suggestion: TitleFactorySuggestion) => {
await navigator.clipboard.writeText(suggestion.title);
setSelectedTitle(suggestion.title);
- toast.success("标题已复制到剪贴板。");
+ toast.success("Title copied to clipboard.");
};
const handleApply = (suggestion: TitleFactorySuggestion) => {
@@ -92,20 +92,20 @@ export default function NovelTitleWorkshop({
-
项目内标题工坊
+
In-project title workshop
- 基于当前已保存的小说简介和类型生成候选。如果刚修改过简介或类型,建议先保存基本信息再生成。
-
+ Generate candidates based on the currently saved novel synopsis and genre. If you have just modified the introduction or type, it is recommended to save the basic information before generating it.
+
saveCurrentMutation.mutate()}>
- {saveCurrentMutation.isPending ? "保存中..." : "保存当前标题"}
+ {saveCurrentMutation.isPending ? "Saving..." : "Saving the current title"}
@@ -230,8 +230,8 @@ export default function MobileNovelEditView(props: NovelEditViewProps) {
{isTakeoverLoading ? (
- AI 自动导演接管
-
+ AI automatic director takes over
+
) : activeStepTakeoverEntry}
) : null}
diff --git a/client/src/pages/novels/mobile/MobileNovelStepNav.tsx b/client/src/pages/novels/mobile/MobileNovelStepNav.tsx
index 7afc09ace..79826589e 100644
--- a/client/src/pages/novels/mobile/MobileNovelStepNav.tsx
+++ b/client/src/pages/novels/mobile/MobileNovelStepNav.tsx
@@ -21,7 +21,7 @@ export default function MobileNovelStepNav({
const steps = [...NOVEL_WORKSPACE_FLOW_STEPS, ...NOVEL_WORKSPACE_TOOL_TABS];
return (
-
+
{steps.map((step, index) => {
const isActive = activeTab === step.key;
const isRecommended = workflowCurrentTab === step.key && workflowCurrentTab !== activeTab;
@@ -46,8 +46,8 @@ export default function MobileNovelStepNav({
{step.label}
{isRecommended ? (
- 流程推荐
-
+ Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
+
) : null}
diff --git a/client/src/pages/novels/mobile/mobileNovelWorkspaceUtils.ts b/client/src/pages/novels/mobile/mobileNovelWorkspaceUtils.ts
index 3f53e5a4e..3b8372605 100644
--- a/client/src/pages/novels/mobile/mobileNovelWorkspaceUtils.ts
+++ b/client/src/pages/novels/mobile/mobileNovelWorkspaceUtils.ts
@@ -28,16 +28,16 @@ export function getMobileNovelSaveState(
case "basic":
return {
visible: true,
- label: "保存基本信息",
- savingLabel: "保存中...",
+ label: "Save basic information",
+ savingLabel: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
isSaving: props.basicTab.isSaving,
onSave: props.basicTab.onSave,
};
case "story_macro":
return {
visible: true,
- label: "保存故事规划",
- savingLabel: "保存中...",
+ label: "Save story plan",
+ savingLabel: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
isSaving: props.storyMacroTab.isSaving,
onSave: props.storyMacroTab.onSaveEdits,
};
@@ -52,24 +52,24 @@ export function getMobileNovelSaveState(
case "character":
return {
visible: true,
- label: "保存角色",
- savingLabel: "保存中...",
+ label: "save character",
+ savingLabel: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
isSaving: props.characterTab.isSavingCharacter,
onSave: props.characterTab.onSaveCharacter,
};
case "outline":
return {
visible: true,
- label: "保存卷工作区",
- savingLabel: "保存中...",
+ label: "Save volume workspace",
+ savingLabel: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
isSaving: props.outlineTab.isSaving,
onSave: props.outlineTab.onSave,
};
case "structured":
return {
visible: true,
- label: "保存拆章",
- savingLabel: "保存中...",
+ label: "Save the seal",
+ savingLabel: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
isSaving: props.structuredTab.isSaving,
onSave: props.structuredTab.onSave,
};
diff --git a/client/src/pages/novels/novelBasicInfo.shared.ts b/client/src/pages/novels/novelBasicInfo.shared.ts
index 705f1fcc8..79de47f97 100644
--- a/client/src/pages/novels/novelBasicInfo.shared.ts
+++ b/client/src/pages/novels/novelBasicInfo.shared.ts
@@ -45,191 +45,39 @@ export interface BasicInfoOption {
export const DEFAULT_ESTIMATED_CHAPTER_COUNT = 80;
-export const WRITING_MODE_OPTIONS: BasicInfoOption[] = [
- {
- value: "original",
- label: "原创",
- summary: "从零开始创建世界、角色和主线,适合大多数新项目。",
- recommended: true,
- },
- {
- value: "continuation",
- label: "续写",
- summary: "基于已有小说或知识文档继续创作,后续会优先注入既有设定和拆书内容。",
- },
-];
-
-export const PROJECT_MODE_OPTIONS: BasicInfoOption[] = [
- {
- value: "co_pilot",
- label: "AI 副驾",
- summary: "你定方向,AI 提方案和草稿,适合前期打磨和高频人工决策。",
- recommended: true,
- },
- {
- value: "ai_led",
- label: "AI 接管",
- summary: "AI 负责主推进,你在关键节点审核,适合已有明确目标的项目。",
- },
- {
- value: "draft_mode",
- label: "草稿优先",
- summary: "先快速产出文本和方向,结构约束较弱,适合试故事和找感觉。",
- },
- {
- value: "auto_pipeline",
- label: "流水线优先",
- summary: "适合设定较完整后按规划、生成、审计、修复连续推进。",
- },
-];
-
-export const READER_CHANNEL_OPTIONS: BasicInfoOption[] = [
- {
- value: "ai_judge",
- label: "AI 判断",
- summary: "让 AI 根据题材、卖点和起始想法判断默认读者频道倾向,适合作为默认选择。",
- recommended: true,
- },
- {
- value: "male_oriented",
- label: "男频向",
- summary: "更强调目标、升级、竞争、爽点兑现和外部事件推进。",
- },
- {
- value: "female_oriented",
- label: "女频向",
- summary: "更强调关系线、情绪牵引、人物选择和细腻的阶段性反馈。",
- },
- {
- value: "general",
- label: "泛读者 / 不限定",
- summary: "不限定频道倾向,让 AI 优先按故事本身和目标读者描述来规划。",
- },
-];
-
-export const POV_OPTIONS: BasicInfoOption[] = [
- {
- value: "third_person",
- label: "第三人称",
- summary: "最稳,适合多角色和复杂主线。",
- recommended: true,
- },
- {
- value: "first_person",
- label: "第一人称",
- summary: "代入感强,但信息受限,适合强主角视角叙事。",
- },
- {
- value: "mixed",
- label: "混合视角",
- summary: "更灵活,但更容易失控,适合成熟项目。",
- },
-];
-
-export const PACE_OPTIONS: BasicInfoOption[] = [
- {
- value: "balanced",
- label: "均衡",
- summary: "推进和铺垫兼顾,适合作为默认选择。",
- recommended: true,
- },
- {
- value: "slow",
- label: "慢节奏",
- summary: "更重铺垫、氛围和情绪发酵。",
- },
- {
- value: "fast",
- label: "快节奏",
- summary: "更重事件驱动、钩子和连续推进。",
- },
-];
-
-export const EMOTION_OPTIONS: BasicInfoOption[] = [
- {
- value: "medium",
- label: "中情绪浓度",
- summary: "保留起伏但不过载,适合作为默认值。",
- recommended: true,
- },
- {
- value: "low",
- label: "低情绪浓度",
- summary: "更克制,适合冷静叙事或偏理性作品。",
- },
- {
- value: "high",
- label: "高情绪浓度",
- summary: "更强调爆发、冲突和强刺激场面。",
- },
-];
-
-export const AI_FREEDOM_OPTIONS: BasicInfoOption[] = [
- {
- value: "medium",
- label: "中自由度",
- summary: "允许 AI 在设定内补充细节和局部推进,适合作为默认值。",
- recommended: true,
- },
- {
- value: "low",
- label: "低自由度",
- summary: "严格按设定和规划执行,适合前期控盘。",
- },
- {
- value: "high",
- label: "高自由度",
- summary: "允许 AI 主动扩展剧情和细节,适合中后期稳定项目。",
- },
-];
-
-export const PUBLICATION_STATUS_OPTIONS: BasicInfoOption[] = [
- {
- value: "draft",
- label: "草稿",
- summary: "仍在开发和打磨阶段,适合绝大多数项目。",
- recommended: true,
- },
- {
- value: "published",
- label: "已发布",
- summary: "用于标记已成型或已对外发布的作品。",
- },
-];
-
-export const PROJECT_STATUS_OPTIONS: Array<{ value: NovelBasicFormState["projectStatus"]; label: string }> = [
- { value: "not_started", label: "未开始" },
- { value: "in_progress", label: "进行中" },
- { value: "completed", label: "已完成" },
- { value: "rework", label: "返工" },
- { value: "blocked", label: "阻塞" },
+export const WRITING_MODE_OPTIONS: BasicInfoOption[] = [ { value: "original", label: "Original", summary: "Create the world, characters, and main storyline from scratch, suitable for most new projects.", recommended: true, }, { value: "continuation", label: "Continuation", summary: "Continue the creation based on an existing novel or knowledge document, and will prioritize the injection of existing settings and book-breaking content.", }, ]; export const PROJECT_MODE_OPTIONS: BasicInfoOption[] = [ { value: "co_pilot", label: "AI Co-pilot", summary: "You set the direction, AI provides solutions and drafts, suitable for early polishing and high-frequency human decision-making.", recommended: true, }, { value: "ai_led", label: "AI Takes Over", summary: "AI is responsible for the main drive, you review at key nodes, suitable for projects with clear goals.", }, { value: "draft_mode", label: "draft priority", summary: "Quickly produce text and direction first, with weaker structural constraints, suitable for testing stories and getting a feel for the project.", }, { value: "auto_pipeline", label: "pipeline priority", summary: "Suitable for continuous progress according to planning, generation, auditing, and repair after a relatively complete setup.", }, ]; export const READER_CHANNEL_OPTIONS: BasicInfoOption[] = [ { value: "ai_judge", label: "AI Judgment", summary: "Let AI judge the default reader channel preference based on the theme, selling points, and initial idea, suitable as the default selection.", recommended: true, }, { value: "male_oriented", label: "Male-oriented", summary: "Emphasis on goals, upgrades, competition, fulfillment of satisfying moments, and external event progression.", }, { value: "female_oriented", label: "Female-oriented", summary: "Emphasis on relationship lines, emotional pull, character selection, and subtle stage-by-stage feedback.", }, { value: "general", label: "General Readers/Unrestricted", summary: "Unrestricted channel preference, let AI prioritize planning based on the story itself and the target reader description.", }, ]; export const POV_OPTIONS: BasicInfoOption[] = [ { value: "third_person", label: "Third-person perspective", summary: "Most stable, suitable for multiple characters and complex storylines.", recommended: true, }, { value: "first_person", label: "First-person perspective", summary: "Strong sense of immersion, but limited information, suitable for strong protagonist-centric narratives.", }, { value: "mixed", label: "Mixed perspective", summary: "More flexible, but more prone to loss of control, suitable for mature projects.", }, ]; export const PACE_OPTIONS: BasicInfoOption[] = [ { value: "balanced", label: "Balanced", summary: "Balances both progression and setup, suitable as the default choice.", recommended: true, }, { value: "slow", label: "Slow pace", summary: "Emphasis on setup, atmosphere, and emotional build-up.", }, { value: "fast", label: "Fast pace", summary: "Emphasis on event-driven, hook-based, and continuous progression.", }, ]; export const EMOTION_OPTIONS: BasicInfoOption[] = [ { value: "medium", label: "Medium emotional intensity", summary: "Retains fluctuations but is not overloaded, suitable as a default value.", recommended: true, }, { value: "low", label: "Low emotional intensity", summary: "More restrained, suitable for calm narratives or more rational works.", }, { value: "high", label: "High emotional intensity", summary: "Emphasis on explosions, conflicts, and highly stimulating scenes.", }, ]; export const AI_FREEDOM_OPTIONS: BasicInfoOption[] = [ { value: "medium", label: "Medium freedom", summary: "Allows AI to supplement details and make local progress within the settings, suitable as the default value.", recommended: true, }, { value: "low", label: "Low freedom", summary: "Strictly follows the settings and plans, suitable for early-stage control.", }, { value: "high", label: "High freedom", summary: "Allows AI to actively expand the plot and details, suitable for mid-to-late-stage stable projects.", }, ]; export const PUBLICATION_STATUS_OPTIONS: BasicInfoOption[] = [ { value: "draft", label: "draft", summary: "Still under development and polishing, suitable for most projects.", recommended: true, }, { value: "published", label: "published", summary: "Used to mark completed or publicly released works.", }, ]; export const PROJECT_STATUS_OPTIONS: Array<{ value: NovelBasicFormState["projectStatus"]; label: string }> = [
+ { value: "not_started", label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
+ { value: "in_progress", label: "in progress" },
+ { value: "completed", label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
+ { value: "rework", label: "Rework" },
+ { value: "blocked", label: "block" },
];
export const BASIC_INFO_FIELD_HINTS = {
- writingMode: "决定项目是从零开始,还是基于已有作品继续创作。它会直接影响后续优先使用哪些上下文来源。",
- targetAudience: "说明这本书最主要写给谁看。不会写专业人群画像也没关系,按直觉描述即可。",
- bookSellingPoint: "写清楚这本书最抓人的点,例如关系拉扯、逆袭爽点、悬念推进或设定新鲜感。",
- competingFeel: "写成读者会联想到的阅读感,不是要求你模仿具体作品。",
- first30ChapterPromise: "写清楚前 30 章一定要让读者看到什么、爽到什么、相信什么。",
- commercialTagsText: "用逗号分隔 3-6 个标签即可,例如逆袭、强冲突、悬念拉满、职场博弈。",
- projectMode: "决定你和 AI 的协作方式。会影响后续哪些步骤自动推进、哪些步骤更依赖人工确认。",
- readerChannelPreference: "帮助 AI 判断默认爽点、情绪重心和关系线权重。不确定时保持 AI 判断。",
- narrativePov: "决定章节生成默认采用哪种叙述视角,也会影响信息分发方式。",
- pacePreference: "决定章节规划时是偏铺垫还是偏推进,会影响场景密度和钩子强度。",
- emotionIntensity: "决定后续生成时情绪爆发和冲突的频率,不是越高越好。",
- aiFreedom: "决定 AI 可以偏离既有规划和设定的程度。前期建议保持低或中。",
- postGenerationStyleReviewEnabled: "控制正文生成后的去 AI 味检测与自动修正。生成前的写法和反 AI 提示仍按规则库执行。",
- defaultChapterLength: "这是章节规划和生成时的参考字数,不是硬限制。常见推荐值是 2500 到 3500。",
- estimatedChapterCount: "这是项目预估的总章节数,会作为结构化大纲、剧情拍点和流水线默认范围的参考,不是硬限制。",
- resourceReadyScore: "用于标记设定、角色、主线资料是否充分。数值越高,越适合进入自动化生产阶段。",
- styleTone: "写几个关键词即可,例如冷峻、克制、黑色幽默。它会影响生成的语言风格。",
- genreId: "题材基底回答“这是什么书”,例如修仙、都市、历史架空。它会影响规划、标题和整体卖点倾向,建议尽量尽早确定。",
- primaryStoryModeId: "主推进模式回答“这本书靠什么持续推进和兑现”,例如系统流、无敌流、种田流。后续规划和生成会优先服从它。",
- secondaryStoryModeId: "副推进模式只负责补充风味,例如在治愈日常中叠加小店经营感,在无敌流中叠加马甲感,不能覆盖主模式的边界。",
- worldId: "这里只记录一个参考样本,方便初始化本书世界。小说生成会优先读取页面上方“本书世界”卡片中的内容。",
- status: "只是作品生命周期标记,不影响基础创作能力,但会影响列表和项目管理状态。",
- continuationSourceType: "续写时选择是引用站内小说,还是知识库里的文档版本。",
- continuationBookAnalysis: "拆书内容会作为高权重结构化上下文,适合续写项目保持风格和设定一致。",
+ writingMode: "Decide whether to start the project from scratch or build on existing work. It directly affects which contextual sources are prioritized for subsequent use.",
+ targetAudience: "Explain who this book is primarily written for. It doesn’t matter if you don’t know how to write portraits of professional people, just describe them intuitively.",
+ bookSellingPoint: "Write down clearly the most gripping points of the book, such as the relationship pull, the excitement of counterattack, the suspense advancement, or the freshness of the setting.",
+ competingFeel: "Writing to create a reading experience that readers will relate to does not require you to imitate specific works.",
+ first30ChapterPromise: "Write clearly what the readers will see, feel happy, and believe in the first 30 chapters.",
+ commercialTagsText: "Just use commas to separate 3-6 tags, such as counterattack, strong conflict, suspense, and workplace game.",
+ projectMode: "Decide how you and AI work together. It will affect which subsequent steps are automatically advanced and which steps rely more on manual confirmation.",
+ readerChannelPreference: "Help AI judgment to default to cool points, emotional focus and relationship line weights. Maintain AI judgment when in doubt.",
+ narrativePov: "Deciding which narrative perspective to use by default for chapter generation also affects how information is distributed.",
+ pacePreference: "Deciding whether to focus on foreshadowing or advancement when planning chapters will affect scene density and hook strength.",
+ emotionIntensity: "Determining the frequency of emotional outbursts and conflicts during subsequent generation, higher is not better.",
+ aiFreedom: "Determine how far the AI can deviate from established plans and settings. It is recommended to keep it low or medium in the early stage.",
+ postGenerationStyleReviewEnabled: "Control AI deodorization detection and automatic correction after text generation. The writing method and anti-AI prompts before generation are still executed according to the rule base.",
+ defaultChapterLength: "This is a reference word count when planning and generating chapters, not a hard limit. Common recommendations are 2500 to 3500.",
+ estimatedChapterCount: "This is the estimated total number of chapters for the project, which will be used as a reference for the structured outline, plot points, and the default scope of the pipeline. It is not a hard limit.",
+ resourceReadyScore: "Used to mark whether settings, characters, and main story information are sufficient. The higher the value, the more suitable it is to enter the automated production stage.",
+ styleTone: "Just write a few keywords, such as coldness, restraint, and black humor. It affects the generated language style.",
+ genreId: "Answer \"What kind of book is this\" based on the subject matter, such as cultivating immortality, city, and historical fiction. It will affect the planning, title and overall selling point tendency, so it is recommended to determine it as early as possible.",
+ primaryStoryModeId: "The main promotion mode answers \"What does this book rely on to continue to promote and realize its fulfillment\", such as system flow, invincible flow, and farming flow. Subsequent planning and generation will obey it first.",
+ secondaryStoryModeId: "The secondary promotion mode is only responsible for supplementing the flavor, such as superimposing the sense of shop management in daily healing, and superimposing the sense of vest in invincible flow, and cannot cover the boundaries of the main mode.",
+ worldId: "Only a reference sample is recorded here to facilitate initializing the world of this book. The novel generation will give priority to reading the content in the \"Book World\" card at the top of the page.",
+ status: "It is just a work life cycle mark and does not affect basic creative capabilities, but will affect the list and project management status.",
+ continuationSourceType: "When continuing, choose whether to cite the novel on the site or the document version in the knowledge base.",
+ continuationBookAnalysis: "The content of the split book will serve as a high-weight structured context, suitable for continued projects to maintain a consistent style and setting.",
} satisfies Record;
export function createDefaultNovelBasicFormState(): NovelBasicFormState {
diff --git a/client/src/pages/novels/novelEditTakeover.shared.ts b/client/src/pages/novels/novelEditTakeover.shared.ts
index 34a629e54..7a9b17f66 100644
--- a/client/src/pages/novels/novelEditTakeover.shared.ts
+++ b/client/src/pages/novels/novelEditTakeover.shared.ts
@@ -22,27 +22,27 @@ export function formatTakeoverCheckpoint(
task: UnifiedTaskDetail | null,
): string {
if (checkpoint === "candidate_selection_required") {
- return "等待确认书级方向";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
if (checkpoint === "book_contract_ready") {
- return "Book Contract 待确认";
+ return "Book Contract (To be confirmed)";
}
if (checkpoint === "character_setup_required") {
- return "角色准备待审核";
+ return "Role preparation pending review";
}
if (checkpoint === "volume_strategy_ready") {
- return "卷战略 / 卷骨架待审核";
+ return "Volume Strategy/Volume Skeleton Pending Review";
}
if (checkpoint === "chapter_batch_ready") {
return `${resolveAutoExecutionScopeLabel(task)}自动执行已暂停`;
}
if (checkpoint === "replan_required") {
- return "等待处理重规划建议";
+ return "Awaiting processing of re-planning suggestions";
}
if (checkpoint === "workflow_completed") {
- return "主流程已完成";
+ return "The main process has been completed";
}
- return "导演流程进行中";
+ return "Director process in progress";
}
export function buildTakeoverTitle(input: {
@@ -100,42 +100,42 @@ export function buildTakeoverDescription(input: {
}
if (input.mode === "waiting" || input.mode === "action_required") {
if (input.checkpointType === "candidate_selection_required") {
- return "书级方向候选已经生成。请先回到书级方向确认页选定或修正方案,自动导演才能继续推进后续主链。";
+ return "Book-level direction candidates have been generated. Please return to the book-level direction confirmation page to select or modify the plan before the automatic director can continue to advance the subsequent main chain.";
}
if (input.checkpointType === "character_setup_required") {
- return "角色准备已经生成。你可以先检查核心角色、关系和当前目标,确认后再继续自动导演。";
+ return "Character preparation has been generated. You can check the core characters, relationships, and current goals first and confirm before continuing with autodirecting.";
}
if (input.checkpointType === "volume_strategy_ready") {
- return "当前可以审核并微调卷战略 / 卷骨架。确认后再继续自动生成节奏板、拆章和已选章节批次的细化资源。";
+ return "Volume strategy/volume skeleton can currently be reviewed and fine-tuned. After confirmation, continue to automatically generate detailed resources for rhythm boards, split chapters, and selected chapter batches.";
}
if (input.checkpointType === "workflow_completed") {
return `自动导演已经完成${input.scopeLabel}的章节执行、审核与修复。你可以直接进入章节执行继续写作,也可以完成并退出导演模式。`;
}
if (input.checkpointType === "replan_required") {
- return "AI 在已选章节批次执行后判断后续章节需要重规划。你可以先进入质量修复区处理建议,也可以把本次问题留到后续质量回收,先继续自动执行后面的章节。";
+ return "After the selected chapters are executed in batches, the AI determines that subsequent chapters need to be replanned. You can either go to the quality repair area to handle the suggestions first, or leave the issue for later quality recap and continue automatically executing the subsequent chapters.";
}
if (input.reviewScope) {
- return "自动导演已到达审核点。请先检查当前阶段产物,再决定是否继续推进。";
+ return "AutoDirector has reached the review point. Please check the current stage of the product first before deciding whether to proceed.";
}
}
if (input.mode === "failed") {
if (input.checkpointType === "chapter_batch_ready") {
return `${input.scopeLabel}自动执行已暂停。可以先查看执行详情或质量修复区,再决定是否继续自动执行。`;
}
- return "后台导演流程已中断。可以先查看执行详情,再决定是否从最近进度点恢复。";
+ return "The backend director process has been interrupted. You can check the execution details before deciding whether to resume from the latest progress point.";
}
if (input.mode === "loading") {
- return "正在同步当前自动导演状态。";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
- return "AI 正在后台接管这本书的开书流程。你可以继续手动操作当前项目;如果与自动导演同时改同一块内容,以最新写入结果为准。";
+ return "AI is taking over the book's creation process in the background. You can continue to manually work on the current project; if you are making changes to the same content as the automated director at the same time, the latest result will prevail.";
}
export function buildContinueAutoExecutionActionLabel(scopeLabel: string, isPending: boolean): string {
- return isPending ? "继续执行中..." : `继续自动执行${scopeLabel}`;
+ return isPending ? "Continue to execute..." : `继续自动执行${scopeLabel}`;
}
export function buildSkipQualityRepairActionLabel(scopeLabel: string, isPending: boolean): string {
- return isPending ? "继续执行中..." : `跳过本次建议,继续${scopeLabel}`;
+ return isPending ? "Continue to execute..." : `跳过本次建议,继续${scopeLabel}`;
}
export function buildContinueAutoExecutionToast(scopeLabel: string): string {
diff --git a/client/src/pages/novels/novelWorkspaceNavigation.ts b/client/src/pages/novels/novelWorkspaceNavigation.ts
index a6cad10d0..4a6c67518 100644
--- a/client/src/pages/novels/novelWorkspaceNavigation.ts
+++ b/client/src/pages/novels/novelWorkspaceNavigation.ts
@@ -14,18 +14,18 @@ export type NovelWorkspaceFlowTab =
export type NovelWorkspaceTab = NovelWorkspaceFlowTab | "history";
export const NOVEL_WORKSPACE_FLOW_STEPS: Array<{ key: NovelWorkspaceFlowTab; label: string }> = [
- { key: "basic", label: "项目设定" },
- { key: "story_macro", label: "故事宏观规划" },
- { key: "world", label: "世界观准备" },
- { key: "character", label: "角色准备" },
- { key: "outline", label: "卷战略 / 卷骨架" },
- { key: "structured", label: "节奏 / 拆章" },
- { key: "chapter", label: "章节执行" },
- { key: "pipeline", label: "质量修复" },
+ { key: "basic", label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
+ { key: "story_macro", label: "Big story plan" },
+ { key: "world", label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
+ { key: "character", label: "role preparation" },
+ { key: "outline", label: "Volume Strategy / Volume Skeleton" },
+ { key: "structured", label: "Rhythm / Breaking Chapter" },
+ { key: "chapter", label: "Chapter execution" },
+ { key: "pipeline", label: "Quality fix" },
];
export const NOVEL_WORKSPACE_TOOL_TABS: Array<{ key: Extract; label: string }> = [
- { key: "history", label: "版本历史" },
+ { key: "history", label: "Version history" },
];
const NOVEL_WORKSPACE_TAB_SET = new Set([
@@ -69,7 +69,7 @@ export function getNextNovelWorkspaceFlowTab(value: string | null | undefined):
export function getNovelWorkspaceTabLabel(value: string | null | undefined): string {
const normalized = normalizeNovelWorkspaceTab(value);
- return [...NOVEL_WORKSPACE_FLOW_STEPS, ...NOVEL_WORKSPACE_TOOL_TABS].find((item) => item.key === normalized)?.label ?? "项目设定";
+ return [...NOVEL_WORKSPACE_FLOW_STEPS, ...NOVEL_WORKSPACE_TOOL_TABS].find((item) => item.key === normalized)?.label ?? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
export function scopeFromWorkspaceTab(tab: string): DirectorLockScope | null {
diff --git a/client/src/pages/novels/simpleCreation/SimpleCreationMaterialsPanel.tsx b/client/src/pages/novels/simpleCreation/SimpleCreationMaterialsPanel.tsx
index 9f1df98c4..9cd9790fc 100644
--- a/client/src/pages/novels/simpleCreation/SimpleCreationMaterialsPanel.tsx
+++ b/client/src/pages/novels/simpleCreation/SimpleCreationMaterialsPanel.tsx
@@ -33,7 +33,7 @@ export default function SimpleCreationMaterialsPanel({
}: SimpleCreationMaterialsPanelProps) {
const sellingPoint = materials.story.coreSellingPoint
|| materials.description
- || "AI 正在沉淀整书方向。";
+ || "AI is shaping the direction of the entire book.";
const readingPromise = materials.story.readingPromise
|| materials.story.protagonistFantasy;
const displayedCharacters = materials.characters.slice(0, 6);
@@ -50,76 +50,76 @@ export default function SimpleCreationMaterialsPanel({
-
AI 已准备的创作资源
+
AI prepared creative resources
- 只读查看故事承诺、世界、角色和分卷成果;AI 会继续用这些资源保持后续章节一致。
-
+ Read-only view of story commitments, world, characters, and volume outcomes; AI will continue to use these resources to keep subsequent chapters consistent.
+
Easy Creation · Read-OnlyCompleted {shelf.progress.completedChapters}/{shelf.progress.totalChapters || "To be planned"} Chapters{shelf.progress.currentAction}
{shelf.progress.canRetry ? ( retryMutation.mutate()} disabled={retryMutation.isPending}> {retryMutation.isPending ? : null} Continue according to AI suggestion ) : null} exportMutation.mutate()} disabled={exportMutation.isPending}> Export completed chapters setConvertOpen(true)}> Convert to professional creation
{shelf.progress.safetyMessage ? (
-
AI 已暂停以保护作品
{shelf.progress.safetyMessage}
+
AI has been suspended to protect the work
{shelf.progress.safetyMessage}
) : null}
{selectedChapter?.content ? (
<>
-
第 {selectedChapter.order} 章
{selectedChapter.title}
+
Section {selectedChapter.order} chapter
{selectedChapter.title}
{selectedChapter.content}
>
) : (
-
章节正在路上
AI 会先完成规划,再逐章写作、审校和修复。完成后的章节会自动出现在左侧书架。
+
Chapter is on the way
AI will complete the planning first, then write, review and repair chapter by chapter. The completed chapters will automatically appear on the left bookshelf.
)}
@@ -212,10 +173,10 @@ export default function SimpleNovelShelfPage() {
- 转为专业创作?
- 转换后可编辑设定、规划和章节正文,现有内容与后台任务都会保留。此操作不能切回简易创作。
+ Turning professionally into creative work?
+ After conversion, settings, planning, and chapter text can be edited, and existing content and background tasks will be retained. This operation cannot switch back to simple creation.
-
setConvertOpen(false)}>Continue to use simple creation convertMutation.mutate()} disabled={convertMutation.isPending}>{convertMutation.isPending ? "Converting..." : "Confirm to switch to professional creation"}
diff --git a/client/src/pages/novels/structuredOutline.utils.ts b/client/src/pages/novels/structuredOutline.utils.ts
index ae46017fc..dd98e9234 100644
--- a/client/src/pages/novels/structuredOutline.utils.ts
+++ b/client/src/pages/novels/structuredOutline.utils.ts
@@ -222,7 +222,7 @@ function compactList(items: string[] | undefined): string {
export function buildTaskSheetFromStructuredChapter(chapter: StructuredChapter): string {
const lines: string[] = [];
- lines.push(`章节目标:${chapter.purpose || chapter.summary || "推动主线"}`);
+ lines.push(`章节目标:${chapter.purpose || chapter.summary || "Promote the main line"}`);
if (chapter.keyEvents && chapter.keyEvents.length > 0) {
lines.push(`关键事件:${compactList(chapter.keyEvents)}`);
}
diff --git a/client/src/pages/novels/structuredOutlineSync.utils.ts b/client/src/pages/novels/structuredOutlineSync.utils.ts
index c4c4fb6b0..9190248c5 100644
--- a/client/src/pages/novels/structuredOutlineSync.utils.ts
+++ b/client/src/pages/novels/structuredOutlineSync.utils.ts
@@ -84,25 +84,25 @@ function compareNullableNumber(a: number | null | undefined, b: number | null |
function getChangedFields(existing: OutlineSyncChapter, chapter: StructuredChapter): string[] {
const changed: string[] = [];
if (!compareNullableString(existing.title, chapter.title)) {
- changed.push("标题");
+ changed.push("Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.");
}
if (!compareNullableString(existing.expectation, chapter.summary)) {
- changed.push("摘要");
+ changed.push("summary");
}
if (!compareNullableNumber(existing.targetWordCount, chapter.targetWordCount)) {
- changed.push("目标字数");
+ changed.push("target word count");
}
if (!compareNullableNumber(existing.conflictLevel, chapter.conflictLevel)) {
- changed.push("冲突等级");
+ changed.push("conflict level");
}
if (!compareNullableNumber(existing.revealLevel, chapter.revealLevel)) {
- changed.push("揭露等级");
+ changed.push("reveal level");
}
if (!compareNullableString(existing.mustAvoid, chapter.mustAvoid)) {
- changed.push("禁止事项");
+ changed.push("Prohibited matters");
}
if (chapter.taskSheet?.trim() && !compareNullableString(existing.taskSheet, chapter.taskSheet)) {
- changed.push("任务单");
+ changed.push("Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.");
}
return changed;
}
@@ -133,7 +133,7 @@ export function buildStructuredOutlineSyncPreview(
order: chapter.order,
nextTitle: chapter.title,
hasContent: false,
- changedFields: ["新章节"],
+ changedFields: ["new chapter"],
});
continue;
}
@@ -180,7 +180,7 @@ export function buildStructuredOutlineSyncPreview(
order: chapter.order,
nextTitle: chapter.title,
hasContent,
- changedFields: ["从大纲移除"],
+ changedFields: ["Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."],
});
} else {
deleteCandidateCount += 1;
@@ -189,7 +189,7 @@ export function buildStructuredOutlineSyncPreview(
order: chapter.order,
nextTitle: chapter.title,
hasContent,
- changedFields: ["待确认删除"],
+ changedFields: ["Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."],
});
}
}
diff --git a/client/src/pages/novels/volumePlan.utils.ts b/client/src/pages/novels/volumePlan.utils.ts
index dfc1c49d2..0778709db 100644
--- a/client/src/pages/novels/volumePlan.utils.ts
+++ b/client/src/pages/novels/volumePlan.utils.ts
@@ -35,17 +35,17 @@ export function buildVolumePlanningReadiness(params: {
const { volumes, strategyPlan, critiqueReport, beatSheets } = params;
const blockingReasons: string[] = [];
if (!strategyPlan) {
- blockingReasons.push("请先生成卷战略建议,再确认卷骨架。");
+ blockingReasons.push("Please first create a volume strategy suggestion, and then confirm the volume skeleton.");
}
const hasHighRiskCritique = critiqueReport?.overallRisk === "high";
if (hasHighRiskCritique) {
- blockingReasons.push("当前卷战略审查为高风险,请先重新生成或修订卷战略。");
+ blockingReasons.push("The current volume strategy review is high risk, please regenerate or revise the volume strategy first.");
}
if (volumes.length === 0) {
- blockingReasons.push("当前还没有卷骨架。");
+ blockingReasons.push("There is currently no volume skeleton.");
}
if (!beatSheets.some((sheet) => sheet.beats.length > 0)) {
- blockingReasons.push("当前卷还没有节奏板,默认不能直接拆章节列表。");
+ blockingReasons.push("There is no rhythm board in the current volume, so the chapter list cannot be directly opened by default.");
}
return {
canGenerateStrategy: true,
@@ -114,7 +114,7 @@ export function createEmptyChapter(chapterOrder: number): VolumeChapterPlan {
export function buildTaskSheetFromVolumeChapter(chapter: VolumeChapterPlan): string {
const lines = [
- `章节目标:${chapter.purpose || chapter.summary || "推进主线"}`,
+ `章节目标:${chapter.purpose || chapter.summary || "Advance the main line"}`,
typeof chapter.conflictLevel === "number" ? `冲突等级:${chapter.conflictLevel}` : "",
typeof chapter.revealLevel === "number" ? `揭露等级:${chapter.revealLevel}` : "",
typeof chapter.targetWordCount === "number" ? `目标字数:${chapter.targetWordCount}` : "",
@@ -164,8 +164,7 @@ export function buildOutlinePreviewFromVolumes(volumes: VolumePlan[]): string {
.map((volume) => {
const chapterSpan = volume.chapters.length > 0
? `${volume.chapters[0]?.chapterOrder ?? "-"}-${volume.chapters[volume.chapters.length - 1]?.chapterOrder ?? "-"}`
- : "未拆章";
- return [
+ : "Unopened seal"; return [
`【第${volume.sortOrder}卷】${volume.title}`,
volume.summary?.trim() ? `卷摘要:${volume.summary.trim()}` : "",
volume.openingHook?.trim() ? `开卷抓手:${volume.openingHook.trim()}` : "",
@@ -256,14 +255,14 @@ function compareNumber(a: number | null | undefined, b: number | null | undefine
}
function getChangedFields(existing: ExistingOutlineChapter, chapter: VolumeChapterPlan, action: "update" | "move"): string[] {
- const changed: string[] = action === "move" ? ["章节顺序"] : [];
- if (!compareText(existing.title, chapter.title)) changed.push("标题");
- if (!compareText(existing.expectation, chapter.summary)) changed.push("摘要");
- if (!compareNumber(existing.targetWordCount, chapter.targetWordCount)) changed.push("目标字数");
- if (!compareNumber(existing.conflictLevel, chapter.conflictLevel)) changed.push("冲突等级");
- if (!compareNumber(existing.revealLevel, chapter.revealLevel)) changed.push("揭露等级");
- if (!compareText(existing.mustAvoid, chapter.mustAvoid)) changed.push("禁止事项");
- if (!compareText(existing.taskSheet, chapter.taskSheet)) changed.push("任务单");
+ const changed: string[] = action === "move" ? ["Chapter order"] : [];
+ if (!compareText(existing.title, chapter.title)) changed.push("Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.");
+ if (!compareText(existing.expectation, chapter.summary)) changed.push("summary");
+ if (!compareNumber(existing.targetWordCount, chapter.targetWordCount)) changed.push("target word count");
+ if (!compareNumber(existing.conflictLevel, chapter.conflictLevel)) changed.push("conflict level");
+ if (!compareNumber(existing.revealLevel, chapter.revealLevel)) changed.push("reveal level");
+ if (!compareText(existing.mustAvoid, chapter.mustAvoid)) changed.push("Prohibited matters");
+ if (!compareText(existing.taskSheet, chapter.taskSheet)) changed.push("Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.");
return changed;
}
@@ -317,7 +316,7 @@ export function buildVolumeSyncPreview(
chapterOrder: entry.chapter.chapterOrder,
nextTitle: entry.chapter.title,
hasContent: false,
- changedFields: ["新章节"],
+ changedFields: ["new chapter"],
});
continue;
}
@@ -371,23 +370,23 @@ export function buildVolumeSyncPreview(
deleteCount += 1;
items.push({
action: "delete",
- volumeTitle: "未匹配",
+ volumeTitle: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
chapterOrder: chapter.order,
nextTitle: chapter.title,
previousTitle: chapter.title,
hasContent,
- changedFields: ["从卷纲移除"],
+ changedFields: ["Removed from syllabus"],
});
} else {
deleteCandidateCount += 1;
items.push({
action: "delete_candidate",
- volumeTitle: "未匹配",
+ volumeTitle: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
chapterOrder: chapter.order,
nextTitle: chapter.title,
previousTitle: chapter.title,
hasContent,
- changedFields: ["待确认删除"],
+ changedFields: ["Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."],
});
}
}
diff --git a/client/src/pages/promptWorkbench/PromptWorkbenchPage.tsx b/client/src/pages/promptWorkbench/PromptWorkbenchPage.tsx
index b303252d8..6638a4b5e 100644
--- a/client/src/pages/promptWorkbench/PromptWorkbenchPage.tsx
+++ b/client/src/pages/promptWorkbench/PromptWorkbenchPage.tsx
@@ -234,28 +234,28 @@ export default function PromptWorkbenchPage() {
-
编辑模式
+
Edit mode
- 安全槽位适合稳定调整,高级模板适合本书正文写作自定义。
-
+ The safety slot is suitable for stable adjustment, and the advanced template is suitable for customization of the text of this book.
+
+ After selecting a novel, you can set independent slot coverage for this book; when no novel is selected, you can only view the inheritance value and generate a general preview.
+
+ Advanced templates can be edited after selecting text writing prompts, book scope, and specific novels.
+
);
}
@@ -135,8 +135,8 @@ export function AdvancedPromptTemplateEditor(props: {
- 高级模板会影响本书正文生成;必需上下文缺失时生成会停止。
-
+ Advanced templates affect book text generation; generation stops when required context is missing.
+
- 恢复官方模板
-
+ Restore official template
+
templateState.saveMutation.mutate()}
@@ -156,15 +156,15 @@ export function AdvancedPromptTemplateEditor(props: {
className="bg-[#0f766e] text-white hover:bg-[#0b5f59]"
>
- 保存为新版本
-
+ Save as new version
+
- 版本说明
-
+ Release Notes
+
templateState.setNotes(event.target.value)}
- placeholder="说明本次模板调整目标"
+ placeholder="Explain the goals of this template adjustment"
className="mt-2 border-[#cbdad6]"
disabled={disabled || isBusy}
/>
@@ -216,13 +216,13 @@ export function AdvancedPromptTemplateEditor(props: {
@@ -405,8 +405,8 @@ function PromptOfficialVersionPanel(props: {
onClick={() => onApplyOfficial([item.key])}
className="border-[#b8d9d0] bg-white text-[#0f5f59] hover:bg-[#eaf7f2]"
>
- 恢复官方当前版
-
+ Restore the official current version
+
onKeepMine([item.key])}
className="text-[#52606d] hover:bg-[#f4faf7]"
>
- 保留我的设置
-
+ keep my settings
+
@@ -470,11 +470,11 @@ function ToggleSlotControl(props: {
- {checked ? "已启用" : "已关闭"}
+ {checked ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : "Closed"}
{checked ? (
- 启用后追加:{slot.copy}
+ Append after enabling:{slot.copy}
) : null}
@@ -528,8 +528,8 @@ function ContextReferenceChips(props: {
- 该提示词没有声明可编辑槽位。可以查看最终 messages 与上下文注入,但不能直接替换 system prompt 或修改上下文策略。
-
+ Prompt word read only
+
+ This prompt word does not declare an editable slot. Final messages and context injection can be viewed, but system prompts cannot be directly replaced or context policies modified.
+
) : (
<>
{controlSections.length > 0 ? (
@@ -195,8 +194,8 @@ export function PromptEditorShell(props: PromptEditorShellProps) {
- 可编辑槽位
-
+ Editable slots
+
{prompt.slots.length > 0 ? prompt.slots.map((slot) => (
·{SLOT_KIND_LABELS[slot.kind] ?? slot.kind}
)) : (
- 该提示词未开放表达槽位。
+ This prompt word does not open an expression slot.
)}
+ The model is being called using the current draft, and the test output will be displayed when completed.
+
);
}
if (error) {
@@ -42,13 +42,13 @@ export function PromptTestRunResultPanel(props: {
+ The test output will call the real model and consume credits; the results are only used for debugging and will not be saved as the chapter text.
+
diff --git a/client/src/pages/promptWorkbench/components/VisualTemplateEditor.tsx b/client/src/pages/promptWorkbench/components/VisualTemplateEditor.tsx
index 6f7cb02a1..cb95c831f 100644
--- a/client/src/pages/promptWorkbench/components/VisualTemplateEditor.tsx
+++ b/client/src/pages/promptWorkbench/components/VisualTemplateEditor.tsx
@@ -31,10 +31,10 @@ const PromptTokenPlugin = createSlatePlugin({
});
const REFERENCE_GROUP_LABELS: Record = {
- required_context: "必需上下文",
- optional_context: "可选上下文",
- input: "运行变量",
- slot: "槽位",
+ required_context: "required context",
+ optional_context: "optional context",
+ input: "run variables",
+ slot: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
};
function groupReferences(items: PromptTemplateReferenceItem[], query: string) {
@@ -67,13 +67,13 @@ function TokenMenu(props: {
props.onQueryChange(event.target.value)}
- placeholder="搜索上下文、变量或槽位"
+ placeholder="Search context, variable or slot"
className="h-8 border-[#cbdad6]"
/>
@@ -450,7 +450,7 @@ export function VisualTemplateEditor(props: {
props.onFocusRole(props.role)}
onKeyDown={handleKeyDown}
diff --git a/client/src/pages/promptWorkbench/hooks/usePromptDraftSlots.ts b/client/src/pages/promptWorkbench/hooks/usePromptDraftSlots.ts
index 52fc09c15..61846b3a3 100644
--- a/client/src/pages/promptWorkbench/hooks/usePromptDraftSlots.ts
+++ b/client/src/pages/promptWorkbench/hooks/usePromptDraftSlots.ts
@@ -81,20 +81,20 @@ function getSectionSource(input: {
const { globalOverride, novelOverride, scope } = input;
if (scope === "novel") {
if (isOfficialDefaultEntry(novelOverride)) {
- return { source: "novel_official_default", sourceLabel: "本书使用官方默认" };
+ return { source: "novel_official_default", sourceLabel: "This book uses the official default" };
}
if (novelOverride) {
- return { source: "novel", sourceLabel: "本书覆盖" };
+ return { source: "novel", sourceLabel: "This book covers" };
}
if (globalOverride && !isOfficialDefaultEntry(globalOverride)) {
- return { source: "global", sourceLabel: "全局覆盖" };
+ return { source: "global", sourceLabel: "global coverage" };
}
- return { source: "official", sourceLabel: "官方默认" };
+ return { source: "official", sourceLabel: "Official default" };
}
if (globalOverride && !isOfficialDefaultEntry(globalOverride)) {
- return { source: "global", sourceLabel: "全局覆盖" };
+ return { source: "global", sourceLabel: "global coverage" };
}
- return { source: "official", sourceLabel: "官方默认" };
+ return { source: "official", sourceLabel: "Official default" };
}
export function buildPromptEditorSections(input: {
@@ -221,7 +221,7 @@ export function usePromptDraftSlots(prompt: PromptCatalogItem | null) {
useEffect(() => {
if (saveMutation.isError) {
const error = saveMutation.error;
- setSaveError(error instanceof Error ? error.message : "保存失败,请重试。");
+ setSaveError(error instanceof Error ? error.message : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.");
}
}, [saveMutation.error, saveMutation.isError]);
diff --git a/client/src/pages/promptWorkbench/hooks/usePromptPreview.ts b/client/src/pages/promptWorkbench/hooks/usePromptPreview.ts
index 0b4580bcd..24f8ba105 100644
--- a/client/src/pages/promptWorkbench/hooks/usePromptPreview.ts
+++ b/client/src/pages/promptWorkbench/hooks/usePromptPreview.ts
@@ -35,14 +35,14 @@ function buildPreviewExtraContextBlocks(prompt: PromptCatalogItem) {
group: "chapter_mission",
priority: 100,
content: [
- "Chapter mission: 示例章节",
- "Objective: 让主角发现旧仓库暗号,并确认有人正在逼近。",
- "Expectation: 本章需要推进线索发现、制造外部压力,并在结尾留下追踪钩子。",
+ "Chapter mission: Example chapter",
+ "Objective: To have the protagonist discover the old warehouse code and confirm that someone is approaching.",
+ "Expectation: This chapter requires advancing the discovery of clues, creating external pressure, and leaving a trail at the end.",
"Must advance",
- "- 主角发现墙上暗号并判断它指向旧城档案站。",
- "- 门外脚步声逼近,迫使主角做出即时选择。",
+ "The protagonist discovers a coded message on the wall and deduces that it points to the Old Town Archives.",
+ "Footsteps approached from outside the door, forcing the protagonist to make an immediate choice.",
"Must preserve",
- "- 暗号是真实线索,不是幻觉或普通涂鸦。",
+ "- The code is a real clue, not a hallucination or ordinary graffiti.",
].join("\n"),
},
{
@@ -52,15 +52,15 @@ function buildPreviewExtraContextBlocks(prompt: PromptCatalogItem) {
required: true,
content: [
"Chapter boundary:",
- "Exclusive event: 主角第一次在旧仓库发现上一任调查员留下的暗号。",
- "Entry state: 主角独自进入旧仓库,尚未确认暗号含义。",
- "Ending state: 主角确认暗号指向旧城档案站,同时意识到追踪者已经到门外。",
- "Next chapter entry state: 主角必须在暴露前决定带走证据还是设伏反查。",
+ "Exclusive event: The protagonist discovers a coded message left by the previous investigator for the first time in the old warehouse.",
+ "Entry state: The protagonist enters the old warehouse alone, and the meaning of the code has not yet been confirmed.",
+ "Ending state: The protagonist confirms that the cipher points to the Old City Archives and realizes that the pursuers are already outside the door.",
+ "Next chapter entry state: The protagonist must decide whether to take away the evidence or set up an ambush to investigate before being exposed.",
"Do not cross",
- "- 不得在本章直接揭开旧城组织的真实首领。",
- "- 不得让追踪者当场完整解释暗号系统。",
+ "- The true leader of the Old City organization must not be revealed directly in this chapter.",
+ "- The tracker must not be allowed to fully explain the cipher system on the spot.",
"Protected reveals",
- "- 上一任调查员的真实身份。",
+ "- The true identity of the previous investigator.",
].join("\n"),
},
{
@@ -70,22 +70,22 @@ function buildPreviewExtraContextBlocks(prompt: PromptCatalogItem) {
required: true,
content: [
"Structure obligations",
- "- 必须检查本章是否完成线索发现、压力逼近和章末选择点。",
- "- 必须检查主角行动动机是否连续,不能凭空知道暗号答案。",
- "- 必须检查结尾是否形成新的悬念或追踪压力。",
+ "- You must check whether the chapter has completed the discovery of clues, the approach of pressure, and the choice at the end of the chapter.",
+ "- It is necessary to check whether the protagonist's motivation is consistent; the answer to the code cannot be known out of thin air.",
+ "- It is necessary to check whether the ending creates new suspense or follow-up pressure.",
].join("\n"),
},
{
id: "local_state",
group: "local_state",
priority: 89,
- content: "Local state before review:\n主角身处旧仓库内部,外部追踪者正在靠近,暗号含义尚未完全确认。",
+ content: "Local state before review:\nThe protagonist is inside an old warehouse, with pursuers approaching from the outside, and the meaning of the coded message has not yet been fully confirmed.",
},
{
id: "world_rules",
group: "world_rules",
priority: 84,
- content: "Relevant world rules\n- 旧城暗号系统只由少数调查员和地下组织成员掌握。",
+ content: "Relevant world rules: The Old City Cipher System is known only to a select few investigators and members of underground organizations.",
},
];
}
@@ -114,19 +114,19 @@ function buildPreviewPromptInput(
? previewChapter.content?.trim()
|| previewChapter.taskSheet?.trim()
|| previewChapter.expectation?.trim()
- || "当前章节暂无正文。"
- : "主角走进旧仓库,发现墙上残留着上一任调查员留下的暗号。门外脚步声逼近,他必须在暴露前判断暗号指向哪里。";
+ || "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."
+ : "The protagonist walks into the old warehouse and finds the code left by the previous investigator on the wall. Footsteps were approaching outside the door, and he had to determine where the code was pointing before he was exposed.";
return {
- novelTitle: previewNovel?.title || "示例小说",
+ novelTitle: previewNovel?.title || "Sample novel",
chapterTitle: previewChapter
- ? `第 ${previewChapter.order ?? "?"} 章 ${previewChapter.title || "未命名章节"}`
- : "示例章节",
+ ? `第 ${previewChapter.order ?? "?"} 章 ${previewChapter.title || "Unnamed chapter"}`
+ : "Example chapter",
requestedTypes: ["plot", "character", "continuity"],
storyModeContext: previewNovel
- ? "使用所选小说的章节任务、章节边界和结构义务进行本书预览。"
- : "本书偏连载网文节奏,章节需要持续推进冲突并保留章末钩子。",
+ ? "Conduct a book preview using the chapter assignments, chapter boundaries, and structural obligations of your chosen novel."
+ : "This book prefers the rhythm of a serialized online novel, and the chapters need to continuously advance the conflict and retain the hook at the end of the chapter.",
content: chapterContent,
- ragContext: "无额外检索补充。",
+ ragContext: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
};
}
@@ -135,9 +135,9 @@ function buildPreviewPromptInput(
const softMinWordCount = Math.max(800, Math.round(targetWordCount * 0.86));
const softMaxWordCount = Math.max(softMinWordCount + 200, Math.round(targetWordCount * 1.14));
return {
- novelTitle: previewNovel?.title || "示例小说",
+ novelTitle: previewNovel?.title || "Sample novel",
chapterOrder: previewChapter?.order ?? 1,
- chapterTitle: previewChapter?.title || "示例章节",
+ chapterTitle: previewChapter?.title || "Example chapter",
mode: "draft",
targetWordCount,
minWordCount: softMinWordCount,
@@ -147,15 +147,15 @@ function buildPreviewPromptInput(
if (prompt.id === "novel.chapter_editor.workspace_diagnosis") {
return {
- chapterTitle: "示例章节",
- chapterMission: "让主角发现关键线索。",
- volumePositionLabel: "第一卷中段",
- volumePhaseLabel: "冲突展开",
- paceDirective: "加快推进",
- previousChapterBridge: "上一章留下追踪线索。",
- nextChapterBridge: "下一章进入正面对抗。",
- activePlotThreads: ["追踪档案站"],
- paragraphs: [{ index: 1, text: "主角走进旧仓库。" }],
+ chapterTitle: "Example chapter",
+ chapterMission: "Let the protagonist discover key clues.",
+ volumePositionLabel: "middle part of volume one",
+ volumePhaseLabel: "Conflict unfolds",
+ paceDirective: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ previousChapterBridge: "The previous chapter left a trail of clues.",
+ nextChapterBridge: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ activePlotThreads: ["Tracking Archive Station"],
+ paragraphs: [{ index: 1, text: "The protagonist walks into an old warehouse." }],
openIssues: [],
};
}
@@ -165,14 +165,14 @@ function buildPreviewPromptInput(
generationDepth: "standard",
selectedDimensions: ["basic", "personality", "arc"],
character: {
- name: "林澈",
- role: "主角",
- briefDescription: "被迫追查旧仓库暗号的年轻调查员。",
+ name: "Lin Che",
+ role: "main character",
+ briefDescription: "A young investigator forced to track down the secret code of an old warehouse.",
importance: "high",
- occurringChapters: ["第 1 章"],
+ occurringChapters: ["Chapter 1"],
},
- characterSystemContext: "主角承担揭开旧城秘密的推进职责。",
- notesText: "第 1 章中,林澈发现旧仓库暗号,并意识到有人正在追踪他。",
+ characterSystemContext: "The protagonist is responsible for advancing the secrets of the old city.",
+ notesText: "In Chapter 1, Lin Che discovered the secret code of the old warehouse and realized that someone was following him.",
ragEvidenceText: "",
};
}
@@ -181,42 +181,44 @@ function buildPreviewPromptInput(
return {
generationDepth: "standard",
selectedDimensions: ["basic", "personality", "arc"],
- characterNames: ["林澈", "沈雾"],
- characterSystemContext: "核心角色围绕旧城秘密和追踪压力形成关系网。",
- notesText: "林澈发现暗号,沈雾掌握旧城线索,两人暂时互不信任。",
+ characterNames: ["Lin Che", "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."],
+ characterSystemContext: "The core characters form a network around the secrets of the old city and the pressures of tracking them down.",
+ notesText: "Lin Che discovered the code and Chen Wu had clues about the old city. The two temporarily distrusted each other.",
};
}
if (prompt.id === "image.novel_cover.brief") {
return {
- sourcePrompt: "旧城仓库、墙上暗号、门外脚步声、悬疑感强的竖版封面。",
- title: "旧城暗号",
- description: "年轻调查员在旧城废仓中发现改变命运的暗号。",
- targetAudience: "喜欢都市悬疑和强钩子开篇的读者。",
- bookSellingPoint: "每章都围绕一个可追查的线索推进。",
- competingFeel: "紧张、克制、带一点冷色电影感。",
- first30ChapterPromise: "揭开旧城暗号背后的组织,并让主角卷入更大的阴谋。",
- commercialTags: ["都市悬疑", "线索追查", "高压开局"],
- genreLabel: "都市悬疑",
- primaryStoryModeLabel: "线索推进",
- secondaryStoryModeLabel: "身份谜团",
- worldName: "旧城",
- worldSummary: "一座表面平静、地下线索交错的旧城区。",
- styleTone: "冷峻、紧凑、画面感强",
- narrativePovLabel: "第三人称有限视角",
- pacePreferenceLabel: "中快节奏",
- emotionIntensityLabel: "高压克制",
+ sourcePrompt: "The old city warehouse, the secret code on the wall, the footsteps outside the door, and the suspenseful vertical cover.",
+ title: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ description: "A young investigator discovers a code that changes his fate in an abandoned warehouse in the old city.",
+ targetAudience: "Readers who like urban suspense and strong hooks.",
+ bookSellingPoint: "Each chapter progresses around a traceable clue.",
+ competingFeel: "Tense, restrained, with a bit of a cold movie feel.",
+ first30ChapterPromise: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ commercialTags: ["Urban suspense", "Clues tracing", "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."],
+ genreLabel: "Urban suspense",
+ primaryStoryModeLabel: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ secondaryStoryModeLabel: "identity mystery",
+ worldName: "old city",
+ worldSummary: "An old city with a calm surface and intertwined underground clues.",
+ styleTone: "Cold, compact, and strong image",
+ narrativePovLabel: "third person limited perspective",
+ pacePreferenceLabel: "Medium to fast pace",
+ emotionIntensityLabel: "High-pressure restraint",
};
}
if (prompt.id === "novel.character.castAuto.relations") {
return {
- storyInput: "主角在旧城追查暗号,逐步发现身边人的隐瞒与组织压力。",
- optionTitle: "旧城追踪阵容",
- optionSummary: "主角、线索提供者和压力来源围绕旧城秘密形成互相试探的关系网。",
- protagonistName: "林澈",
- memberNames: ["林澈", "沈雾", "顾衡"],
- memberRosterText: "林澈:主角,年轻调查员。\n沈雾:线索提供者,知道旧城暗号来源。\n顾衡:压力来源,试图阻止调查。",
+ storyInput: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ optionTitle: "Old Town Tracking Lineup",
+ optionSummary: "The protagonist, the clue giver and the source of pressure form a network of mutual exploration around the secrets of the old city.",
+ protagonistName: "Lin Che",
+ memberNames: ["Lin Che", "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.", "Gu Heng"],
+ memberRosterText: "Lin Che: Protagonist, young investigator.\
+Chen Wu: Provider of clues, knows the source of the old city’s secret code.\
+Gu Heng: Source of pressure, trying to stop the investigation.",
};
}
@@ -224,17 +226,17 @@ function buildPreviewPromptInput(
return {
layerKey: "foundation",
targetFields: ["background", "geography"],
- worldName: "旧城",
- worldType: "都市异闻",
- templateName: "都市悬疑",
- templateDescription: "现实城市表层下隐藏长期运转的秘密秩序。",
- classicElements: ["旧城区", "地下组织", "线索暗号"],
- pitfalls: ["不要把所有谜团一次解释完", "不要让规则只停留在概念"],
- axioms: "旧城的暗号系统真实存在,并会影响人物行动。",
- summary: "旧城由表面生活区和地下线索网络构成。",
- blueprintPromptBlock: "核心舞台是废弃仓库、老街和被遮蔽的档案站。",
+ worldName: "old city",
+ worldType: "Urban anecdotes",
+ templateName: "Urban suspense",
+ templateDescription: "There is a long-running secret order hidden under the surface of the real city.",
+ classicElements: ["old town", "underground organization", "clue code"],
+ pitfalls: ["Don't explain all the mysteries at once", "Don’t let rules remain concepts"],
+ axioms: "The secret code system in the old city really exists and will affect the characters' actions.",
+ summary: "The old city consists of surface living areas and a network of underground clues.",
+ blueprintPromptBlock: "The central stage is an abandoned warehouse, an old street and a covered archival station.",
existingJson: "{}",
- ragContext: "无额外参考。",
+ ragContext: "No additional references.",
};
}
@@ -252,38 +254,38 @@ function buildPreviewPromptInput(
if (prompt.id === "writingFormula.extract.stream") {
return {
extractLevel: "standard",
- focusAreas: ["节奏", "句式", "画面感"],
- sourceText: "门外脚步声停住了。林澈按住呼吸,指尖擦过墙上的暗号,忽然明白这不是警告,而是邀请。",
+ focusAreas: ["Rhythm", "Sentence pattern", "Picture sense"],
+ sourceText: "The footsteps outside the door stopped. Lin Che held her breath and brushed the code on the wall with her fingertips. She suddenly understood that this was not a warning, but an invitation.",
};
}
if (prompt.id === "novel.chapter_editor.rewrite_candidates") {
return {
operation: "polish",
- operationLabel: "润色选中片段",
+ operationLabel: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
scope: "selection",
customInstruction: "",
- selectedText: "门外脚步声停住了。林澈按住呼吸,指尖擦过墙上的暗号。",
- beforeParagraphs: ["旧仓库里只剩一盏忽明忽暗的灯。"],
- afterParagraphs: ["下一秒,铁门被人从外面轻轻推开。"],
- goalSummary: "让主角发现关键线索,并用外部压力制造章末紧张感。",
- chapterSummary: "主角进入旧仓库,发现暗号,同时意识到追踪者已经逼近。",
- styleSummary: "冷峻、克制、动作细节清晰。",
- characterStateSummary: "主角警惕但仍愿意冒险推进调查。",
- worldConstraintSummary: "旧城暗号是真实线索,不是幻觉或普通涂鸦。",
- macroContextSummary: "本章负责把主角卷入旧城秘密的第一层门槛。",
- resolvedIntentSummary: "让片段更自然,并加强悬疑压力。",
- constraintsText: "不改变暗号存在、门外有人逼近和主角正在调查这三个事实。",
+ selectedText: "The footsteps outside the door stopped. Lin Che held her breath and brushed the code on the wall with her fingertips.",
+ beforeParagraphs: ["There was only one flickering light left in the old warehouse."],
+ afterParagraphs: ["Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."],
+ goalSummary: "Let the protagonist discover key clues and use external pressure to create end-of-chapter tension.",
+ chapterSummary: "The protagonist enters an old warehouse, discovers a secret code, and realizes that the pursuers are approaching.",
+ styleSummary: "Cold, restrained, and clear in action details.",
+ characterStateSummary: "The protagonist is wary but still willing to take risks to advance the investigation.",
+ worldConstraintSummary: "Old City Codes are real clues, not hallucinations or ordinary graffiti.",
+ macroContextSummary: "This chapter is responsible for the first threshold that involves the protagonist in the secrets of the old city.",
+ resolvedIntentSummary: "Make the sequence more natural and heighten the suspenseful tension.",
+ constraintsText: "The three facts that the code exists, that someone is approaching outside the door, and that the protagonist is investigating do not change.",
};
}
return {
- goal: "查看提示词预览",
+ goal: "View prompt word preview",
messages: [],
contextMode: "novel",
novelId: "novel-1",
- chapterTitle: "示例章节",
- chapterMission: "让主角发现关键线索。",
+ chapterTitle: "Example chapter",
+ chapterMission: "Let the protagonist discover key clues.",
};
}
@@ -312,7 +314,7 @@ export function usePromptPreview(input: UsePromptPreviewInput) {
const buildPayload = useCallback((): PromptPreviewPayload => {
if (!prompt) {
- throw new Error("请选择提示词后再生成预览。");
+ throw new Error("Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.");
}
const executionNovelId = novelId || "novel-1";
const executionChapterId = chapterId || previewChapter?.id || (novelId ? undefined : "chapter-1");
@@ -324,7 +326,7 @@ export function usePromptPreview(input: UsePromptPreviewInput) {
entrypoint,
novelId: executionNovelId,
chapterId: executionChapterId,
- userGoal: "查看提示词预览",
+ userGoal: "View prompt word preview",
resourceBindings: {
novelId: executionNovelId,
...(executionChapterId ? { chapterId: executionChapterId } : {}),
diff --git a/client/src/pages/promptWorkbench/hooks/usePromptSlotPersistence.ts b/client/src/pages/promptWorkbench/hooks/usePromptSlotPersistence.ts
index b33e4a91f..32e4ef64a 100644
--- a/client/src/pages/promptWorkbench/hooks/usePromptSlotPersistence.ts
+++ b/client/src/pages/promptWorkbench/hooks/usePromptSlotPersistence.ts
@@ -59,7 +59,7 @@ export function usePromptSlotPersistence(input: UsePromptSlotPersistenceInput) {
const saveMutation = useMutation({
mutationFn: (updates: Record) => {
if (!prompt) {
- throw new Error("请选择提示词后再保存。");
+ throw new Error("Please select the prompt word before saving.");
}
return saveSlotOverride({
scope,
@@ -78,7 +78,7 @@ export function usePromptSlotPersistence(input: UsePromptSlotPersistenceInput) {
const resetMutation = useMutation({
mutationFn: (slotKeys: string[]) => {
if (!prompt) {
- throw new Error("请选择提示词后再重置。");
+ throw new Error("Please select the prompt word and then reset.");
}
return deleteSlotOverride({
scope,
@@ -97,7 +97,7 @@ export function usePromptSlotPersistence(input: UsePromptSlotPersistenceInput) {
const adoptMutation = useMutation({
mutationFn: (slotKeys: string[]) => {
if (!prompt) {
- throw new Error("请选择提示词后再处理更新。");
+ throw new Error("Please select the prompt word before processing the update.");
}
return applyOfficialSlots({
promptId: prompt.id,
@@ -115,7 +115,7 @@ export function usePromptSlotPersistence(input: UsePromptSlotPersistenceInput) {
const keepMutation = useMutation({
mutationFn: (slotKeys: string[]) => {
if (!prompt) {
- throw new Error("请选择提示词后再处理更新。");
+ throw new Error("Please select the prompt word before processing the update.");
}
return keepMySlots({
promptId: prompt.id,
diff --git a/client/src/pages/promptWorkbench/promptWorkbenchLabels.ts b/client/src/pages/promptWorkbench/promptWorkbenchLabels.ts
index 9e93de530..6b5f412a3 100644
--- a/client/src/pages/promptWorkbench/promptWorkbenchLabels.ts
+++ b/client/src/pages/promptWorkbench/promptWorkbenchLabels.ts
@@ -1,121 +1,58 @@
import type { NovelMaterialImportance, PromptCatalogItem } from "@/api/promptWorkbench";
-export const LOCKED_FIELD_LABELS: Record = {
- outputSchema: "输出格式",
- postValidate: "输出校验",
- postValidateFailureRecovery: "校验失败恢复",
- semanticRetryPolicy: "语义重试策略",
- taskType: "任务类型",
- mode: "输出模式",
- contextPolicy: "上下文策略",
- toolCatalog: "工具目录",
- approvalBoundary: "审批边界",
+export const LOCKED_FIELD_LABELS: Record = { outputSchema: "Output Format", postValidate: "Output Validation", postValidateFailureRecovery: "Validation Failure Recovery", semanticRetryPolicy: "Semantic Retry Policy", taskType: "Task Type", mode: "Output Mode", contextPolicy: "Context Policy", toolCatalog: "Tool Catalog", approvalBoundary: "Approval Boundary", }; export const SLOT_KIND_LABELS: Record = { replace: "Rewrite", append: "Add Constraints", choice: "Options", toggle: "Switch", token: "Inline Value", }; export const CONTEXT_GROUP_LABELS: Record = {
+ book_contract: "full book contract",
+ chapter_boundary: "Chapter boundaries",
+ chapter_mission: "Tasks in this chapter",
+ character_dynamics: "Role relationship dynamics",
+ character_hard_facts: "character hard facts",
+ character_resource_context: "Role resource status",
+ continuation_constraints: "continuation constraints",
+ custom_slot: "Custom constraints",
+ historical_issues: "Historical review issues",
+ incremental_round_context: "Incremental generation rounds",
+ local_state: "current situation",
+ narrative_progress_hint: "Narrative progress prompts",
+ obligation_contract: "obligation contract",
+ open_conflicts: "open conflict",
+ opening_constraints: "opening constraints",
+ participant_subset: "participation role",
+ payoff_directives: "Foreshadowing operation instructions",
+ payoff_ledger: "Foreshadowing account",
+ previous_chapter_hook: "Chapter hook",
+ previous_chapter_tail: "End of last chapter",
+ rag_context: "Search supplement",
+ recent_chapters: "Summary of recent chapters",
+ repair_boundaries: "Revision scope constraints",
+ repair_issues: "List of revision questions",
+ state_goal: "status and goals",
+ story_macro: "macro story structure",
+ structure_obligations: "structural obligations",
+ style_contract: "style contract",
+ timeline_context: "timeline",
+ volume_window: "Volume level progress",
+ world_rules: "world rules",
+ world_slice: "world fragment",
};
-export const SLOT_KIND_LABELS: Record = {
- replace: "改写",
- append: "追加约束",
- choice: "选项",
- toggle: "开关",
- token: "内联值",
-};
-
-export const CONTEXT_GROUP_LABELS: Record = {
- book_contract: "全书合约",
- chapter_boundary: "章节边界",
- chapter_mission: "本章任务",
- character_dynamics: "角色关系动态",
- character_hard_facts: "角色硬事实",
- character_resource_context: "角色资源状态",
- continuation_constraints: "续写约束",
- custom_slot: "自定义约束",
- historical_issues: "历史审校问题",
- incremental_round_context: "增量生成轮次",
- local_state: "当前局面",
- narrative_progress_hint: "叙事进度提示",
- obligation_contract: "义务合约",
- open_conflicts: "开放冲突",
- opening_constraints: "开篇约束",
- participant_subset: "参与角色",
- payoff_directives: "伏笔操作指令",
- payoff_ledger: "伏笔台账",
- previous_chapter_hook: "上章钩子",
- previous_chapter_tail: "上章结尾",
- rag_context: "检索补充",
- recent_chapters: "近期章节摘要",
- repair_boundaries: "修文范围约束",
- repair_issues: "修文问题清单",
- state_goal: "状态与目标",
- story_macro: "宏观故事架构",
- structure_obligations: "结构义务",
- style_contract: "风格合约",
- timeline_context: "时间线",
- volume_window: "卷级进度",
- world_rules: "世界规则",
- world_slice: "世界片段",
-};
-
-export const SOURCE_TYPE_LABELS: Record = {
- novel: "小说",
- chapter: "章节",
- plan: "计划",
- state: "状态",
- character: "角色",
- world: "世界设定",
- style: "风格",
- audit: "审校",
- task: "任务",
-};
-
-export const MESSAGE_ROLE_LABELS: Record = {
- system: "系统",
- human: "用户",
- assistant: "模型",
- ai: "模型",
-};
-
-export const TASK_TYPE_LABELS: Record = {
- writer: "写作",
- light_review: "轻审校",
- critical_review: "完整审校",
- repair: "修文",
- summary: "摘要",
- planning: "规划",
- translation: "翻译",
- analysis: "分析",
- classification: "分类",
-};
-
-export const OUTPUT_TYPE_LABELS: Record = {
- structured: "结构化输出",
- text: "文本输出",
-};
-
-export const ENTRYPOINT_OPTIONS = [
- { value: "creative_hub", label: "创作中枢" },
- { value: "auto_director", label: "自动导演" },
- { value: "chapter_pipeline", label: "章节流水线" },
- { value: "manual_test", label: "手动测试" },
-];
-
-export const MANAGEMENT_STATUS_LABELS: Record = {
- complete: "元数据完整",
- missing_context_requirements: "缺上下文需求",
- missing_slots: "缺槽位声明",
+export const SOURCE_TYPE_LABELS: Record = { novel: "novel", chapter: "chapter", plan: "plan", state: "state", character: "character", world: "world setting", style: "style", audit: "auditor", task: "task", }; export const MESSAGE_ROLE_LABELS: Record = { system: "System", human: "User", assistant: "Model", ai: "Model", }; export const TASK_TYPE_LABELS: Record = { writer: "writing", light_review: "light review", critical_review: "critical review", repair: "repair", summary: "summary", planning: "planning", translation: "translation", analysis: "analysis", classification: "classification", }; export const OUTPUT_TYPE_LABELS: Record = { structured: "Structured Output", text: "Text Output", }; export const ENTRYPOINT_OPTIONS = [ { value: "creative_hub", label: "Creative Hub" }, { value: "auto_director", label: "Auto Director" }, { value: "chapter_pipeline", label: "Chapter Pipeline" }, { value: "manual_test", label: "Manual Test" }, ]; export const MANAGEMENT_STATUS_LABELS: Record = {
+ complete: "Metadata is complete",
+ missing_context_requirements: "Missing contextual requirements",
+ missing_slots: "Missing slot statement",
};
export const MATERIAL_IMPORTANCE_LABELS: Record = {
- must: "必需",
- high: "重要",
- medium: "辅助",
- low: "参考",
+ must: "required",
+ high: "important",
+ medium: "Auxiliary",
+ low: "reference",
};
export const CONTEXT_STATUS_LABELS = {
- selected: "已注入",
- dropped: "已裁剪",
- summarized: "已摘要",
- available: "候选",
+ selected: "Injected",
+ dropped: "Cropped",
+ summarized: "Summary",
+ available: "candidate",
} as const;
export const LOCKED_CONTEXT_GROUPS = new Set([
diff --git a/client/src/pages/settings/AutoDirectorApprovalPreferenceCard.tsx b/client/src/pages/settings/AutoDirectorApprovalPreferenceCard.tsx
index 3f2c9a979..334bbd773 100644
--- a/client/src/pages/settings/AutoDirectorApprovalPreferenceCard.tsx
+++ b/client/src/pages/settings/AutoDirectorApprovalPreferenceCard.tsx
@@ -24,16 +24,16 @@ export function AutoDirectorApprovalPreferenceCard(props: {
onSave,
isSaving,
} = props;
- const toggleLabel = isOpen ? "收起审批授权偏好" : "展开审批授权偏好";
+ const toggleLabel = isOpen ? "收起审批授权偏好" : "Expand approval authorization preferences";
return (
- 审批授权偏好
+ Approval authorization preferences
- 自动导演按范围执行时,会先带入这里的默认授权。未授权的审批点仍会停下等待你确认。
-
+ When the automatic director is executed according to the scope, the default authorization here will be brought in first. Unauthorized approval points will still be stopped waiting for your confirmation.
+
- 当前默认:{summarizeDirectorAutoApprovalPoints(draftCodes)}
+ Current default:{summarizeDirectorAutoApprovalPoints(draftCodes)}
- {isSaving ? "保存中..." : "保存审批授权偏好"}
+ {isSaving ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."}
diff --git a/client/src/pages/settings/AutoDirectorBrowserNotificationSettingsCard.tsx b/client/src/pages/settings/AutoDirectorBrowserNotificationSettingsCard.tsx
index 625f57bf1..bd6a78a1e 100644
--- a/client/src/pages/settings/AutoDirectorBrowserNotificationSettingsCard.tsx
+++ b/client/src/pages/settings/AutoDirectorBrowserNotificationSettingsCard.tsx
@@ -16,13 +16,13 @@ import { AUTO_DIRECTOR_MOBILE_CLASSES } from "@/mobile/autoDirector";
function formatPermission(permission: BrowserNotificationPermissionState): string {
switch (permission) {
case "granted":
- return "已允许";
+ return "allowed";
case "denied":
- return "已阻止";
+ return "blocked";
case "default":
- return "待授权";
+ return "Pending authorization";
case "unsupported":
- return "不支持";
+ return "Not supported";
}
}
@@ -53,7 +53,7 @@ export function AutoDirectorBrowserNotificationSettingsCard(props: {
if (nextPermission === "unsupported") {
setAutoDirectorPauseNotificationEnabled(false);
refreshState();
- onActionResult("当前浏览器不支持桌面提醒。");
+ onActionResult("Current browsers do not support desktop reminders.");
return;
}
if (nextPermission === "default") {
@@ -62,19 +62,19 @@ export function AutoDirectorBrowserNotificationSettingsCard(props: {
if (nextPermission !== "granted") {
setAutoDirectorPauseNotificationEnabled(false);
refreshState();
- onActionResult("浏览器未允许通知,自动导演暂停时不会发送桌面提醒。");
+ onActionResult("The browser does not allow notifications and desktop alerts will not be sent when Auto Director is paused.");
return;
}
setAutoDirectorPauseNotificationEnabled(true);
refreshState();
- onActionResult("自动导演暂停提醒已开启。");
+ onActionResult("Automatic director pause reminder is on.");
};
const handleToggle = (checked: boolean) => {
if (!checked) {
setAutoDirectorPauseNotificationEnabled(false);
refreshState();
- onActionResult("自动导演暂停提醒已关闭。");
+ onActionResult("Automatic director pause reminder is turned off.");
return;
}
void handleEnable();
@@ -89,35 +89,35 @@ export function AutoDirectorBrowserNotificationSettingsCard(props: {
- 自动导演暂停提醒
+ Automatic director pause reminder
- 当自动导演等待确认、需要恢复或被校验拦住时,通过浏览器通知提醒你回到跟进中心。
-
+ When the automatic director is waiting for confirmation, needs to be restored, or is blocked by verification, a browser notification will remind you to return to the follow-up center.
+
-
桌面提醒
+
Desktop reminder
- 只影响这台电脑上的当前浏览器。
-
+ Only affects the current browser on this computer.
+
-
通知权限:{permissionLabel}
+
Notification permissions:{permissionLabel}
- 若浏览器已阻止通知,请在地址栏权限设置中允许本网站发送通知。
-
+ If your browser has blocked notifications, please allow this website to send notifications in the address bar permission settings.
+
{canRequestPermission ? (
void handleEnable()}
>
- 授权浏览器通知
-
+ Authorize browser notifications
+
) : null}
diff --git a/client/src/pages/settings/AutoDirectorChannelSettingsCard.tsx b/client/src/pages/settings/AutoDirectorChannelSettingsCard.tsx
index 13c140cd6..c0e0400ae 100644
--- a/client/src/pages/settings/AutoDirectorChannelSettingsCard.tsx
+++ b/client/src/pages/settings/AutoDirectorChannelSettingsCard.tsx
@@ -27,7 +27,7 @@ function AutoDirectorEventMultiSelect(props: {
onClick={() => setOpen((prev) => !prev)}
>
{summarizeSelectedAutoDirectorEvents(value)}
- {open ? "收起" : "展开"}
+ {open ? "Collapse" : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."}
{open ? (
- 导演跟进通道配置
+ Director follow-up channel configuration
- 集中配置钉钉与企微的 webhook、回调 token、用户映射和事件订阅。未配完整回调能力时,消息会自动降级成仅跳转站内。
-
+ Centrally configure webhooks, callback tokens, user mappings and event subscriptions for DingTalk and Qiwei. When the complete callback capability is not configured, the message will be automatically downgraded to only jump within the site.
+
+ Used for the "Open Follow-up Center/View Details" link in DingTalk/Qiwei messages. If not filled in, it will fall back to the site address in the server environment.
+
{(["dingtalk", "wecom"] as const).map((channelType) => (
-
{channelType === "dingtalk" ? "钉钉" : "企业微信"}
+
{channelType === "dingtalk" ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : "Enterprise WeChat"}
Webhook URL
@@ -130,16 +130,16 @@ export function AutoDirectorChannelSettingsCard(props: {
/>
{enabled ? "Opening" : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."}
-
生效基准时间
+
Effective base time
{baselineLabel}
- 存量待确认提案不进入自动放行范围。提案命中未解决冲突时会继续保留为待确认,等待人工处理。
-
+ Proposals that are in stock yet to be confirmed will not be included in the automatic release range. When a proposal hits an unresolved conflict, it will remain pending confirmation and wait for manual processing.
+
@@ -112,8 +112,8 @@ export function AutoDirectorPendingReviewAutoPromotionCard(props: {
}}
>
- 取消
-
+ Cancel
+
- {isSaving ? "保存中..." : "确认开启"}
+ {isSaving ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : "Confirm to open"}
>
)}
>
+ After it is turned on, the system will only process proposals generated after the effective baseline time; existing proposals that have yet to be confirmed will not enter the automatic release range. Proposals that meet the conditions will be submitted as official facts and will not be automatically revoked by the system.
+
setAcknowledgedRisks(event.target.checked)}
/>
- 我了解这项能力会自动提交待确认的状态变更,并会通过导演留痕记录每次动作。
-
+ I understand that this ability automatically submits state changes for confirmation and records each action through a Director's Trace.
+
onPatch({ model: value })}
options={modelOptions.map((model) => ({ value: model }))}
- placeholder="选择模型"
- searchPlaceholder="搜索模型"
+ placeholder="Select model"
+ searchPlaceholder="Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."
emptyText={modelEmptyText}
/>
- 模型厂商
+ Model manufacturer
- 先保证至少一个文本模型可用;余额明细、请求限制和模型列表可以在高级详情里检查。
-
+ First ensure that at least one text model is available; balance details, request limits and model list can be checked in advanced details.
+
- 新增自定义厂商
-
+ Add custom manufacturer
+
{viewModels.map((item) => (
diff --git a/client/src/pages/settings/components/ProviderStatusCard.tsx b/client/src/pages/settings/components/ProviderStatusCard.tsx
index 9bc0d6c63..3d628e85b 100644
--- a/client/src/pages/settings/components/ProviderStatusCard.tsx
+++ b/client/src/pages/settings/components/ProviderStatusCard.tsx
@@ -28,15 +28,15 @@ function getBalanceSummary(input: {
}) {
const { provider, balance, isBalanceLoading } = input;
if (provider.kind === "custom") {
- return "自定义厂商暂不接入余额查询。";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
if (isBalanceLoading) {
- return "正在查询余额...";
+ return "Checking balance...";
}
if (balance?.status === "available") {
return `余额 ${formatBalanceAmount(balance.availableBalance, balance.currency)}`;
}
- return balance?.error ?? balance?.message ?? (provider.isConfigured ? "当前暂未获取余额信息。" : "请先配置 API Key。");
+ return balance?.error ?? balance?.message ?? (provider.isConfigured ? "Balance information is currently not available." : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.");
}
export default function ProviderStatusCard(props: {
@@ -61,12 +61,12 @@ export default function ProviderStatusCard(props: {
const [advancedOpen, setAdvancedOpen] = useState(false);
const [modelsOpen, setModelsOpen] = useState(false);
const imageModelLabel = provider.supportsImageGeneration
- ? provider.currentImageModel || provider.defaultImageModel || "未设置"
- : "不支持图像生成";
+ ? provider.currentImageModel || provider.defaultImageModel || "not set"
+ : "Image generation is not supported";
const visibleModels = modelsOpen ? provider.models : provider.models.slice(0, 8);
const canUseProvider = provider.isConfigured && provider.isActive && Boolean(provider.currentModel);
- const testDisabledReason = provider.isConfigured ? "" : "配置 API Key 后可以测试连接。";
- const refreshDisabledReason = provider.isConfigured ? "" : "配置 API Key 后可以刷新模型列表。";
+ const testDisabledReason = provider.isConfigured ? "" : "After configuring the API Key, you can test the connection.";
+ const refreshDisabledReason = provider.isConfigured ? "" : "After configuring the API Key, you can refresh the model list.";
return (
- {canUseProvider ? "可用于创作任务。" : "完成配置后可用于创作任务。"}
+ {canUseProvider ? "Can be used for creative tasks." : "After completing the configuration, it can be used for creative tasks."}
- API 地址:{provider.currentBaseURL || "-"}
+ API address:{provider.currentBaseURL || "-"}
-
思考功能
+
thinking function
{provider.reasoningEnabled
- ? "当前会返回并展示模型思考内容。"
- : "当前会隐藏思考内容;MiniMax 会自动清洗正文里的 thinking 内容。"}
+ ? "Currently returns and displays the model thinking content."
+ : "The thinking content is currently hidden; MiniMax will automatically clean the thinking content in the text."}
Gift amount:{formatBalanceAmount(balance.grantedBalance, balance.currency)}
: null}
) : (
- {balance?.error ?? balance?.message ?? (provider.isConfigured ? "当前暂未获取余额信息。" : "请先配置 API Key。")}
+ {balance?.error ?? balance?.message ?? (provider.isConfigured ? "Balance information is currently not available." : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.")}
diff --git a/client/src/pages/settings/components/SettingsMaintenanceSection.tsx b/client/src/pages/settings/components/SettingsMaintenanceSection.tsx
index b98dee5b9..391a4675a 100644
--- a/client/src/pages/settings/components/SettingsMaintenanceSection.tsx
+++ b/client/src/pages/settings/components/SettingsMaintenanceSection.tsx
@@ -9,10 +9,10 @@ export default function SettingsMaintenanceSection() {
return (
- 系统维护
+ System maintenance
- 当前环境没有需要处理的桌面维护事项。
-
+ There are no desktop maintenance matters that need to be addressed in the current environment.
+
);
@@ -22,14 +22,14 @@ export default function SettingsMaintenanceSection() {
- 系统维护
+ System maintenance
- 检查桌面更新或导入本机旧数据;这些操作不会影响当前创作配置。
-
+ Check for desktop updates or import native legacy data; these operations will not affect the current authoring configuration.
+
- 没有维护需求时,可以直接回到上面的创作配置。
-
+ When there is no need for maintenance, you can directly return to the creation configuration above.
+
diff --git a/client/src/pages/settings/components/SettingsNavigationCards.tsx b/client/src/pages/settings/components/SettingsNavigationCards.tsx
index 37e89e133..52cc04e7d 100644
--- a/client/src/pages/settings/components/SettingsNavigationCards.tsx
+++ b/client/src/pages/settings/components/SettingsNavigationCards.tsx
@@ -27,52 +27,46 @@ export default function SettingsNavigationCards(props: {
{mode === "all" || mode === "knowledge" ? (
- 知识库向量设置
+ Knowledge base vector set
- 不配置也可以开始创作;启用后,长篇设定、资料和上下文召回会更稳。
-
+ You can start creating without configuration; when enabled, long-form settings, data, and context recall will be more stable.
+
- 打开知识库设置
-
-
-
- ) : null}
-
- {mode === "all" || mode === "routes" ? (
+ Open Knowledge Base Settings ) : null} {mode === "all" || mode === "routes" ? (
- 模型路由
+ model routing
- 为开书、拆章、正文生成和审核任务选择可用模型。
-
+ Choose from available models for book opening, chapter opening, text generation, and review tasks.
+
- 路由健康时,自动导演和章节生产会按任务自动选择模型。
-
+ When routing is healthy, Auto Director and Chapter Production will automatically select models by task.
+
- 进入模型路由管理
+ Enter model routing management
diff --git a/client/src/pages/settings/components/SettingsReadinessCard.tsx b/client/src/pages/settings/components/SettingsReadinessCard.tsx
index 11aaeb7de..bd03bf7c2 100644
--- a/client/src/pages/settings/components/SettingsReadinessCard.tsx
+++ b/client/src/pages/settings/components/SettingsReadinessCard.tsx
@@ -35,13 +35,13 @@ function getReadinessIcon(state: SettingsReadinessItem["state"]) {
function getReadinessBadge(state: SettingsReadinessItem["state"]) {
switch (state) {
case "ready":
- return "可用";
+ return "Available";
case "checking":
- return "检查中";
+ return "Under inspection";
case "optional":
- return "可选增强";
+ return "Optional enhancements";
case "warning":
- return "需要处理";
+ return "Need to be processed";
}
}
@@ -79,37 +79,37 @@ export function buildSettingsReadinessItems(input: {
return [
{
key: "model",
- title: "正文模型",
+ title: "Text model",
state: runnableProviders.length > 0 ? "ready" : "warning",
description: runnableProviders.length > 0
? `已可使用 ${runnableProviders[0].name} 进行正文与规划生成。`
- : "先配置一个可用模型,就可以开始开书和生成章节。",
+ : "First configure an available model, and then you can start opening the book and generating chapters.",
},
{
key: "routes",
- title: "模型路由",
+ title: "model routing",
state: isModelRoutesChecking ? "checking" : hasRoutes && failedRouteCount === 0 ? "ready" : "warning",
description: isModelRoutesChecking
- ? "正在检查开书、拆章、正文生成和审核任务的模型兼容性。"
+ ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."
: hasRoutes && failedRouteCount === 0
- ? "创作任务已有可用路由,后续流程会按任务选择模型。"
- : "部分创作任务还需要补齐或修复模型路由。",
+ ? "The authoring task already has available routes, and subsequent processes will select models by task."
+ : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
},
{
key: "rag",
- title: "知识库增强",
+ title: "Knowledge base enhancement",
state: ragSettings?.enabled && currentRagProvider?.isConfigured && currentRagProvider?.isActive ? "ready" : "optional",
description: ragSettings?.enabled && currentRagProvider?.isConfigured && currentRagProvider?.isActive
- ? "知识库检索已启用,可帮助长篇写作保持资料和设定连续。"
- : "不配置也可以开始创作;启用后会增强设定、资料和上下文召回。",
+ ? "Knowledge base search is enabled to help long-form writing keep data and settings continuous."
+ : "You can start creating without configuration; enabling enhanced settings, data, and context recall.",
},
{
key: "style",
- title: "写法引擎",
+ title: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
state: !isStyleSettingsLoaded ? "checking" : styleReady ? "ready" : "warning",
description: styleReady
- ? "写法提取等待时间在可用范围内,可用于学习样本文风。"
- : "请确认写法提取等待时间在可用范围内。",
+ ? "The waiting time for writing style extraction is within the available range and can be used to learn sample writing styles."
+ : "Please confirm that the waiting time for writing method extraction is within the available range.",
},
];
}
@@ -125,19 +125,19 @@ export default function SettingsReadinessCard(props: {
const blockingCount = items.filter((item) => item.key !== "rag" && item.state === "warning").length;
const canStart = hasModel && hasHealthyRoutes && blockingCount === 0;
const primaryAction = !hasModel
- ? { label: "配置正文模型", to: "#settings-provider-section" }
+ ? { label: "Configure body model", to: "#settings-provider-section" }
: !hasHealthyRoutes
- ? { label: "检查模型路由", to: "/settings/model-routes" }
- : { label: "开始创建小说", to: "/novels/create" };
+ ? { label: "Check model routing", to: "/settings/model-routes" }
+ : { label: "Start creating a novel", to: "/novels/create" };
return (
- 创作可用性检查
+ Authoring availability check
- 先确认开始写小说必需的模型和路由是否可用;知识库属于增强项,可以稍后再补。
-
+ First confirm whether the necessary models and routes to start writing novels are available; the knowledge base is an enhancement and can be added later.
+
@@ -167,8 +167,8 @@ export default function SettingsReadinessCard(props: {
{canStart
- ? "基础创作链路已经可用,可以开始创建或继续推进小说。"
- : "先处理标记为“需要处理”的项目,完成后再进入自动导演或章节生产会更稳。"}
+ ? "Basic authoring links are available to start creating or continuing to advance a novel."
+ : "It will be more stable to work on items marked as \"needs work\" first and then enter automatic director or chapter production after completion."}
diff --git a/client/src/pages/settings/components/StyleEngineRuntimeSettingsCard.tsx b/client/src/pages/settings/components/StyleEngineRuntimeSettingsCard.tsx
index 83e8a3c82..56aed90c7 100644
--- a/client/src/pages/settings/components/StyleEngineRuntimeSettingsCard.tsx
+++ b/client/src/pages/settings/components/StyleEngineRuntimeSettingsCard.tsx
@@ -57,11 +57,11 @@ export default function StyleEngineRuntimeSettingsCard() {
styleExtractionTimeoutMs: minutes * MS_PER_MINUTE,
}),
onSuccess: async (response) => {
- setFeedback(response.message ?? "写法引擎运行设置保存成功。");
+ setFeedback(response.message ?? "The writing engine running settings are saved successfully.");
await queryClient.invalidateQueries({ queryKey: queryKeys.settings.styleEngineRuntime });
},
onError: (error) => {
- setFeedback(error instanceof Error ? error.message : "写法引擎运行设置保存失败。");
+ setFeedback(error instanceof Error ? error.message : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.");
},
});
@@ -71,19 +71,19 @@ export default function StyleEngineRuntimeSettingsCard() {
&& parsedMinutes <= limits.maxMinutes;
const modeOptions = [
{
- label: "快速检测",
+ label: "Rapid detection",
value: limits.minMinutes,
- description: "适合短文本或快速确认样本文风是否可提取。",
+ description: "Suitable for short texts or to quickly confirm whether the sample writing style can be extracted.",
},
{
- label: "稳定推荐",
+ label: "Stable recommendation",
value: clampMinutes(limits.defaultMinutes, limits.minMinutes, limits.maxMinutes),
- description: "适合大多数写法提取任务,等待时间和异常发现更均衡。",
+ description: "It is suitable for most writing extraction tasks, and the waiting time and exception detection are more balanced.",
},
{
- label: "长文提取",
+ label: "Long text extraction",
value: limits.maxMinutes,
- description: "适合长篇原文或较慢模型,给提取过程更充足时间。",
+ description: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
},
];
@@ -91,12 +91,12 @@ export default function StyleEngineRuntimeSettingsCard() {
- 写法引擎运行设置
+ Writing engine running settings
- 控制写法提取等待模型返回的最长时间。长篇原文提取可以适当调高,短文本保持较短更容易发现异常。
-
+ Controls the maximum time that writing extraction waits for model return. The extraction of long original texts can be appropriately increased, and short texts can be kept shorter to make it easier to detect anomalies.
+
- 生效值 {limits.effectiveMinutes} 分钟
+ Effective value {limits.effectiveMinutes} minute
@@ -117,21 +117,14 @@ export default function StyleEngineRuntimeSettingsCard() {
>
+ Settable range:{limits.minMinutes}-{limits.maxMinutes} minute. After saving, newly submitted and retried writing extraction tasks will use this waiting time.
+
) : null}
diff --git a/client/src/pages/settings/modelRouteLabels.ts b/client/src/pages/settings/modelRouteLabels.ts
index 88f367ec9..9bdb5ebb8 100644
--- a/client/src/pages/settings/modelRouteLabels.ts
+++ b/client/src/pages/settings/modelRouteLabels.ts
@@ -2,47 +2,47 @@ import type { ModelRouteTaskType } from "@ai-novel/shared/types/novel";
export const MODEL_ROUTE_LABELS: Record = {
planner: {
- title: "大纲策划",
- description: "先理解创作目标,再安排这一段创作该怎样推进。",
+ title: "outline planning",
+ description: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
},
writer: {
- title: "主笔写作",
- description: "生成章节正文,把章节内容完整写出来。",
+ title: "Chief writing",
+ description: "Generate chapter text and write out the chapter content completely.",
},
review: {
- title: "通用审校",
- description: "检查剧情、节奏和文风,找出草稿里的质量问题。",
+ title: "General school",
+ description: "Check plot, pacing, and style to identify quality issues in your draft.",
},
light_review: {
- title: "基础快审",
- description: "快速判断章节是否能继续推进,用于正文后的轻量质量检查。",
+ title: "Basic quick review",
+ description: "Quickly determine whether a chapter can be advanced and used for lightweight quality checks after the main text.",
},
critical_review: {
- title: "严格审校",
- description: "处理会影响整本连续性的质量检查,适合高风险审校和复检。",
+ title: "Strict review",
+ description: "Handles quality checks that affect the continuity of the entire book, suitable for high-risk review and rechecking.",
},
repair: {
- title: "章节修复",
- description: "根据审校问题修正文稿,让章节回到可继续推进的状态。",
+ title: "Chapter fixes",
+ description: "Correct the manuscript according to the review issues and return the chapter to a state where it can be moved forward.",
},
replan: {
- title: "窗口重规划",
- description: "当局部修复不能收敛时,重新安排受影响章节的目标和衔接。",
+ title: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ description: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
},
state_resolution: {
- title: "状态解析",
- description: "判断章节状态提案是否可信,帮助自动导演减少人工确认。",
+ title: "State analysis",
+ description: "Determine whether the chapter status proposal is credible and help automatic directors reduce manual confirmation.",
},
summary: {
- title: "剧情摘要",
- description: "把长章节整理成回顾、摘要和关键变化。",
+ title: "Plot summary",
+ description: "Organize long chapters into reviews, summaries, and key changes.",
},
fact_extraction: {
- title: "设定考据",
- description: "整理设定、时间线和关键事实,减少前后矛盾。",
+ title: "Setting testimonials",
+ description: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
},
chat: {
- title: "灵感陪写",
- description: "处理日常对话,并把结果整理成创作时能直接理解的内容。",
+ title: "Inspiration to accompany writing",
+ description: "Take everyday conversations and organize the results into content that can be directly understood at the time of creation.",
},
};
diff --git a/client/src/pages/settings/modelRoutes.utils.ts b/client/src/pages/settings/modelRoutes.utils.ts
index 950f77279..c50a22e12 100644
--- a/client/src/pages/settings/modelRoutes.utils.ts
+++ b/client/src/pages/settings/modelRoutes.utils.ts
@@ -102,24 +102,24 @@ export function isSameRouteDraft(draft: RouteDraft, route: SavedModelRoute | und
export function formatStructuredStatus(status: ModelRouteConnectivityStatus["structured"]): string {
if (!status) {
- return "结构化诊断:未执行";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
if (status.ok) {
- return `结构化正常 · ${status.requestProtocol ?? "auto"} · ${status.strategy ?? "prompt_json"}${status.reasoningForcedOff ? " · 会关闭 thinking" : ""}`;
+ return `结构化正常 · ${status.requestProtocol ?? "auto"} · ${status.strategy ?? "prompt_json"}${status.reasoningForcedOff ? " · Will turn off thinking" : ""}`;
}
- return `结构化异常 · ${status.errorCategory ?? "unknown"} · ${status.error ?? "未知错误"}`;
+ return `结构化异常 · ${status.errorCategory ?? "unknown"} · ${status.error ?? "unknown error"}`;
}
export function formatConnectivityStatus(status?: ModelRouteConnectivityStatus | null): string {
if (!status) {
- return "尚未检测生效路由。";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
const parts: string[] = [];
if (status.plain) {
parts.push(
status.plain.ok
? `普通连通正常${status.plain.latency != null ? ` · ${status.plain.latency}ms` : ""}`
- : `普通连通失败 · ${status.plain.error ?? "未知错误"}`,
+ : `普通连通失败 · ${status.plain.error ?? "unknown error"}`,
);
}
parts.push(formatStructuredStatus(status.structured));
diff --git a/client/src/pages/storyModes/StoryModeManagementPage.tsx b/client/src/pages/storyModes/StoryModeManagementPage.tsx
index 472efc58c..dd93ad6da 100644
--- a/client/src/pages/storyModes/StoryModeManagementPage.tsx
+++ b/client/src/pages/storyModes/StoryModeManagementPage.tsx
@@ -195,7 +195,7 @@ export default function StoryModeManagementPage() {
}),
onSuccess: async () => {
await invalidate();
- toast.success("推进模式已创建。");
+ toast.success("Propulsion pattern created.");
setCreateDialogOpen(false);
},
});
@@ -203,7 +203,7 @@ export default function StoryModeManagementPage() {
const createSelectedChildrenMutation = useMutation({
mutationFn: async () => {
if (!defaultParentId) {
- throw new Error("父级推进模式不存在。");
+ throw new Error("Parent push mode does not exist.");
}
const drafts = selectedGeneratedChildIndexes
@@ -216,7 +216,7 @@ export default function StoryModeManagementPage() {
}));
if (drafts.length === 0) {
- throw new Error("请至少选择一个子类候选。");
+ throw new Error("Please select at least one subcategory candidate.");
}
return createStoryModeChildren({
@@ -235,7 +235,7 @@ export default function StoryModeManagementPage() {
const updateMutation = useMutation({
mutationFn: () => {
if (!editingStoryMode) {
- throw new Error("推进模式不存在。");
+ throw new Error("Advance mode does not exist.");
}
return updateStoryMode(editingStoryMode.id, {
name: editState.name.trim(),
@@ -246,7 +246,7 @@ export default function StoryModeManagementPage() {
},
onSuccess: async () => {
await invalidate();
- toast.success("推进模式已更新。");
+ toast.success("Propulsion mode has been updated.");
setEditingStoryModeId("");
},
});
@@ -255,7 +255,7 @@ export default function StoryModeManagementPage() {
mutationFn: (id: string) => deleteStoryMode(id),
onSuccess: async () => {
await invalidate();
- toast.success("推进模式已删除。");
+ toast.success("Push mode has been removed.");
},
});
@@ -312,7 +312,7 @@ export default function StoryModeManagementPage() {
}
setGeneratedChildCandidates([]);
setCreateDraft(cloneDraft(result.draft));
- toast.success("AI 推进模式树草稿已生成。");
+ toast.success("Draft AI propulsion mode tree generated.");
},
});
@@ -365,9 +365,9 @@ export default function StoryModeManagementPage() {
const selectedParentLabel = useMemo(() => {
if (!defaultParentId) {
- return "作为根推进模式创建";
+ return "Created as root push mode";
}
- return parentOptions.find((item) => item.id === defaultParentId)?.path ?? "作为根推进模式创建";
+ return parentOptions.find((item) => item.id === defaultParentId)?.path ?? "Created as root push mode";
}, [defaultParentId, parentOptions]);
const editParentOptions = useMemo(
@@ -380,43 +380,39 @@ export default function StoryModeManagementPage() {
- {isCreatingChild ? "新增推进模式子类" : "新建推进模式"}
+ {isCreatingChild ? "Add a new propulsion mode subclass" : "Create a new propulsion mode"}
- {isCreatingChild
- ? "当前会在指定父类下新增子类。你可以手动填写,也可以先让 AI 基于父类和现有兄弟节点生成多个子类候选,再多选批量保存。"
- : "先确定挂载位置,再手动填写 profile,或者先让 AI 生成一份两级树草稿。"}
+ {isCreatingChild ? "Currently, a new subclass will be added under the specified parent class. You can fill it in manually, or you can first let the AI generate multiple subclass candidates based on the parent class and existing sibling nodes, and then save them in batches." : "First determine the mounting location, then manually fill in the profile, or first let the AI generate a two-level tree draft."}
- {isCreatingChild
- ? "AI 会基于当前父类和现有兄弟节点输出一个或多个子类节点草稿,不会再生成整棵树。补充方向可以留空。保存前仍然会校验 profile 结构。"
- : "AI 会输出一个可直接编辑的推进模式树草稿,保存前仍然会校验 profile 结构。"}
+ {isCreatingChild ? "AI will output one or more child node drafts based on the current parent and existing sibling nodes, without generating the entire tree. The supplementary direction can be left blank. The profile structure will still be validated before saving." : "AI will output a directly editable advance mode tree draft; the profile structure will still be validated before saving."}
- 推进模式库
+ Advancing Pattern Library
- 这里维护作品的推进模式,例如系统流、无敌流、种田流、治愈日常。它回答的是“这本书靠什么持续推进和兑现”,会作为后续规划和生成的硬约束输入。
-
+ The advancement modes of the work are maintained here, such as system flow, invincible flow, farming flow, and daily healing. It answers "what does this book rely on to continue to advance and be realized?" and will serve as a hard constraint input for subsequent planning and generation.
+
-
当前推进模式数:{totalStoryModes}
+
Current number of advancement modes:{totalStoryModes}
{props.actions.map((action) => (
))}
打开来源页面}
+ title="Open source page"
+ consequence="Only the task source is opened and the task status will not be changed."
+ action={Open source page}
/>
) : (
打开来源页面}
+ title="Open source page"
+ consequence="Only the task source is opened and the task status will not be changed."
+ action={Open source page}
/>
)}
- 检查点:{formatCheckpoint(task.checkpointType, task.executionScopeLabel)} | 建议继续:{task.resumeAction ?? task.nextActionLabel ?? "继续主流程"}
+ Checkpoint:{formatCheckpoint(task.checkpointType, task.executionScopeLabel)} | It is recommended to continue:{task.resumeAction ?? task.nextActionLabel ?? "Continue the main process"}
diff --git a/client/src/pages/tasks/components/TaskCenterRuntimePolicyCard.tsx b/client/src/pages/tasks/components/TaskCenterRuntimePolicyCard.tsx
index ae1d57992..1f6c3bfb8 100644
--- a/client/src/pages/tasks/components/TaskCenterRuntimePolicyCard.tsx
+++ b/client/src/pages/tasks/components/TaskCenterRuntimePolicyCard.tsx
@@ -19,23 +19,23 @@ interface TaskCenterRuntimePolicyCardProps {
const POLICY_OPTIONS: Array<{ value: DirectorPolicyMode; label: string; description: string }> = [
{
value: "suggest_only",
- label: "只给建议",
- description: "只分析和给出建议,不自动写入规划或正文。",
+ label: "Just give suggestions",
+ description: "It only analyzes and gives suggestions, and does not automatically write plans or text.",
},
{
value: "run_next_step",
- label: "推进下一步",
- description: "只执行当前最小步骤,完成后停下来让你检查。",
+ label: "Proceed to the next step",
+ description: "Only take the smallest step you can, pausing for you to check when you're done.",
},
{
value: "run_until_gate",
- label: "推进到检查点",
- description: "连续推进到下一个需要确认的节点。",
+ label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ description: "Continuously advance to the next node that requires confirmation.",
},
{
value: "auto_safe_scope",
- label: "安全范围自动推进",
- description: "仅在系统判断风险较低的范围内继续自动处理。",
+ label: "Safe range automatic advancement",
+ description: "Continue automated processing only to the extent the system judges the risk to be low.",
},
];
@@ -64,10 +64,10 @@ export default function TaskCenterRuntimePolicyCard({
}),
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: queryKeys.tasks.directorRuntime(taskId) });
- toast.success("导演推进方式已更新");
+ toast.success("Director advancement method has been updated");
},
onError: (error) => {
- toast.error(error instanceof Error ? error.message : "更新导演推进方式失败");
+ toast.error(error instanceof Error ? error.message : "Failed to update director advancement method");
},
});
@@ -85,10 +85,10 @@ export default function TaskCenterRuntimePolicyCard({
-
导演推进方式
+
Director's advancement method
- 选择系统接下来怎么推进这个导演任务。
-
+ Choose how the system will advance this director task next.
+
{formatPolicyMode(snapshot.policy.mode)}
@@ -113,10 +113,10 @@ export default function TaskCenterRuntimePolicyCard({
onChange={(event) => setAllowExpensiveReview(event.target.checked)}
/>
- 允许执行更完整的审校
+ Allows a more complete review
- 用于章节质量检查、近期章节复盘等步骤,系统会在执行前记录策略。
-
+ Used for chapter quality inspection, recent chapter review and other steps, the system will record the strategy before execution.
+
@@ -127,10 +127,10 @@ export default function TaskCenterRuntimePolicyCard({
onChange={(event) => setMayOverwriteUserContent(event.target.checked)}
/>
- 允许改写受保护的内容
+ Allow rewriting of protected content
- 仅在你确认要让系统处理已编辑正文或关键设定时开启。
-
+ Only turn it on if you are sure you want the system to process edited text or key settings.
+
@@ -147,7 +147,7 @@ export default function TaskCenterRuntimePolicyCard({
)
}
>
- {mutation.isPending ? "保存中..." : "保存推进方式"}
+ {mutation.isPending ? "Saving..." : "Saving the progress method"}
diff --git a/client/src/pages/tasks/components/TaskCenterSummaryCards.tsx b/client/src/pages/tasks/components/TaskCenterSummaryCards.tsx
index 0e2d4ec82..f9a5d4f0d 100644
--- a/client/src/pages/tasks/components/TaskCenterSummaryCards.tsx
+++ b/client/src/pages/tasks/components/TaskCenterSummaryCards.tsx
@@ -15,10 +15,10 @@ export default function TaskCenterSummaryCards({
}: TaskCenterSummaryCardsProps) {
return (
0 ? "info" : "neutral" },
- { key: "must-handle", label: "必须处理", value: mustHandleCount, detail: "失败、人工恢复或明确重规划", tone: mustHandleCount > 0 ? "danger" : "neutral" },
- { key: "quality", label: "质量提醒", value: qualityReminderCount, detail: "可继续推进并稍后处理", tone: qualityReminderCount > 0 ? "warning" : "neutral" },
+ { key: "active", label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.", value: activeCount, detail: "Running or queued tasks", tone: "info" },
+ { key: "waiting", label: "Waiting for operation", value: waitingActionCount, detail: "Confirm, select or continue with current batch", tone: waitingActionCount > 0 ? "info" : "neutral" },
+ { key: "must-handle", label: "must be dealt with", value: mustHandleCount, detail: "Failure, manual recovery or explicit replanning", tone: mustHandleCount > 0 ? "danger" : "neutral" },
+ { key: "quality", label: "Quality reminder", value: qualityReminderCount, detail: "Can move forward and deal with it later", tone: qualityReminderCount > 0 ? "warning" : "neutral" },
]} />
);
}
diff --git a/client/src/pages/tasks/taskCenterUtils.ts b/client/src/pages/tasks/taskCenterUtils.ts
index ea6e9e516..efb604b6e 100644
--- a/client/src/pages/tasks/taskCenterUtils.ts
+++ b/client/src/pages/tasks/taskCenterUtils.ts
@@ -54,9 +54,9 @@ export function getTaskNoticeSeverity(task: TaskQueuePresentationInput): TaskQue
}
export function getTaskNoticeTitle(task: TaskQueuePresentationInput): string {
- if (isTaskReplanRequired(task)) return "需要重规划";
- if (isTaskQueueQualityReminder(task)) return "质量提醒";
- return "任务提醒";
+ if (isTaskReplanRequired(task)) return "Needs re-planning";
+ if (isTaskQueueQualityReminder(task)) return "Quality reminder";
+ return "Task reminder";
}
export function getTaskListPriority(task: TaskQueuePresentationInput): number {
@@ -98,14 +98,14 @@ export function getTaskQueueTone(task: TaskQueuePresentationInput): WorkspaceTon
export function getTaskQueueLevelLabel(task: TaskQueuePresentationInput): string {
const tone = getTaskQueueTone(task);
- if (isTaskReplanRequired(task)) return "需要重规划";
- if (task.pendingManualRecovery) return "需要恢复";
- if (tone === "danger") return task.status === "failed" ? "任务失败" : "阻塞";
- if (tone === "warning" && isTaskQueueQualityReminder(task)) return "质量提醒";
- if (tone === "warning") return "待操作";
- if (tone === "info") return task.status === "waiting_approval" ? "待操作" : "进行中";
- if (tone === "success") return "已完成";
- return "普通任务";
+ if (isTaskReplanRequired(task)) return "Needs re-planning";
+ if (task.pendingManualRecovery) return "Need to restore";
+ if (tone === "danger") return task.status === "failed" ? "Task failed" : "block";
+ if (tone === "warning" && isTaskQueueQualityReminder(task)) return "Quality reminder";
+ if (tone === "warning") return "To be operated";
+ if (tone === "info") return task.status === "waiting_approval" ? "To be operated" : "in progress";
+ if (tone === "success") return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
+ return "Common tasks";
}
export function getTaskQueueSeverity(task: TaskQueuePresentationInput): TaskQueueSeverity {
@@ -124,11 +124,11 @@ export function getTimestamp(value: string | null | undefined): number {
export function formatDate(value: string | null | undefined): string {
if (!value) {
- return "暂无";
+ return "None yet";
}
const date = new Date(value);
if (Number.isNaN(date.getTime())) {
- return "暂无";
+ return "None yet";
}
return date.toLocaleString();
}
@@ -139,103 +139,103 @@ export function formatTokenCount(value: number | null | undefined): string {
export function formatKind(kind: TaskKind): string {
if (kind === "book_analysis") {
- return "拆书分析";
+ return "Book split analysis";
}
if (kind === "novel_workflow") {
- return "小说创作";
+ return "Novel creation";
}
if (kind === "novel_pipeline") {
- return "小说流水线";
+ return "Novel assembly line";
}
if (kind === "knowledge_document") {
- return "知识库索引";
+ return "Knowledge base index";
}
if (kind === "style_extraction") {
- return "写法提取";
+ return "Writing extraction";
}
if (kind === "agent_run") {
- return "Agent 运行";
+ return "Agent running";
}
- return "图片生成";
+ return "Image generation";
}
export function formatCheckpoint(checkpoint: NovelWorkflowMilestoneType | null | undefined, scopeLabel?: string | null): string {
- const resolvedScopeLabel = scopeLabel?.trim() || "前 10 章";
+ const resolvedScopeLabel = scopeLabel?.trim() || "Previous 10 chapters";
if (checkpoint === "rewrite_snapshot_created") {
- return "重写前备份已创建";
+ return "Backup created before rewriting";
}
if (checkpoint === "candidate_selection_required") {
- return "等待确认书级方向";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
if (checkpoint === "book_contract_ready") {
- return "Book Contract 已就绪";
+ return "Book Contract is ready";
}
if (checkpoint === "character_setup_required") {
- return "角色准备待审核";
+ return "Role preparation pending review";
}
if (checkpoint === "volume_strategy_ready") {
- return "卷战略已就绪";
+ return "Volume strategy is ready";
}
if (checkpoint === "chapter_batch_ready") {
return `${resolvedScopeLabel}自动执行已暂停`;
}
if (checkpoint === "step_review_required") {
- return "当前步骤待检查";
+ return "Current step to be checked";
}
if (checkpoint === "replan_required") {
- return "需要重规划";
+ return "Needs re-planning";
}
if (checkpoint === "workflow_completed") {
- return "主流程完成";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
- return "暂无";
+ return "None yet";
}
export function formatResumeTarget(target: NovelWorkflowResumeTarget | null | undefined): string {
if (!target) {
- return "暂无";
+ return "None yet";
}
if (target.route === "/novels/create") {
- return target.mode === "director" ? "创建页 / AI 自动导演" : "创建页";
+ return target.mode === "director" ? "Create Page/AI Auto Director" : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
if (target.stage === "story_macro") {
- return "小说编辑页 / 故事宏观规划";
+ return "Novel editing page / story macro planning";
}
if (target.stage === "character") {
- return "小说编辑页 / 角色准备";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
if (target.stage === "outline") {
- return "小说编辑页 / 卷战略";
+ return "Novel Editor Page/Volume Strategy";
}
if (target.stage === "structured") {
- return "小说编辑页 / 节奏拆章";
+ return "Novel editing page/rhythm breaking chapter";
}
if (target.stage === "chapter") {
- return "小说编辑页 / 章节执行";
+ return "Novel editing page/chapter execution";
}
if (target.stage === "pipeline") {
- return "小说编辑页 / 质量修复";
+ return "Novel editing page / quality repair";
}
- return "小说编辑页 / 项目设定";
+ return "Novel editing page/project settings";
}
export function formatStatus(status: TaskStatus): string {
if (status === "queued") {
- return "排队中";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
if (status === "running") {
- return "运行中";
+ return "Running";
}
if (status === "waiting_approval") {
- return "等待审批";
+ return "Waiting for approval";
}
if (status === "succeeded") {
- return "已完成";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
if (status === "failed") {
- return "失败";
+ return "fail";
}
- return "已取消";
+ return "Canceled";
}
export function toStatusVariant(status: TaskStatus): "default" | "outline" | "secondary" | "destructive" {
@@ -275,12 +275,12 @@ export function createIdempotencyKey(taskId: string, actionCode: string): string
export function formatFollowUpPriority(priority: "P0" | "P1" | "P2"): string {
if (priority === "P0") {
- return "P0 立即处理";
+ return "P0 Immediate Processing";
}
if (priority === "P1") {
- return "P1 尽快处理";
+ return "P1 should be handled as soon as possible.";
}
- return "P2 可稍后处理";
+ return "P2 can be processed later.";
}
export function followUpActionVariant(action: AutoDirectorAction): "default" | "outline" {
diff --git a/client/src/pages/titles/TitleStudioPage.tsx b/client/src/pages/titles/TitleStudioPage.tsx
index a3e8b8fce..6860f275c 100644
--- a/client/src/pages/titles/TitleStudioPage.tsx
+++ b/client/src/pages/titles/TitleStudioPage.tsx
@@ -28,15 +28,15 @@ export default function TitleStudioPage() {
-
标题工坊
+
Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
- 用项目资料、作品简报或参考标题生成候选;看中的标题可以复制,也可以沉淀到标题库里反复使用。
-
+ Use project materials, work briefs or reference titles to generate candidates; the titles you like can be copied or stored in the title library for repeated use.
+
- 生成候选
- 标题库
+ Generate candidates
+ title library
diff --git a/client/src/pages/titles/components/TitleFactoryPanel.tsx b/client/src/pages/titles/components/TitleFactoryPanel.tsx
index e1e1d2825..19f502dee 100644
--- a/client/src/pages/titles/components/TitleFactoryPanel.tsx
+++ b/client/src/pages/titles/components/TitleFactoryPanel.tsx
@@ -25,16 +25,16 @@ type FactoryMode = "novel" | "brief" | "adapt";
const MODE_COPY: Record = {
novel: {
- title: "按小说生成",
- description: "读取已保存的小说项目资料,直接产出更贴近当前作品的标题候选。",
+ title: "Generate by novel",
+ description: "Read the saved novel project data and directly generate title candidates that are closer to the current work.",
},
brief: {
- title: "自由工坊",
- description: "只写一句题材、主角卖点或核心冲突,快速试一批不同方向的标题。",
+ title: "free workshop",
+ description: "Just write one sentence about the theme, the protagonist’s selling point or the core conflict, and quickly try out a batch of titles in different directions.",
},
adapt: {
- title: "参考改编",
- description: "参考一个标题的节奏和命名结构,再结合你的作品信息重新生成。",
+ title: "Reference adaptation",
+ description: "Refer to the rhythm and naming structure of a title, and then regenerate it based on your work information.",
},
};
@@ -69,7 +69,7 @@ export default function TitleFactoryPanel({ genreTree, novels }: TitleFactoryPan
mutationFn: async () => {
if (mode === "novel") {
if (!selectedNovelId) {
- throw new Error("请先选择一个小说项目。");
+ throw new Error("Please select a fiction project first.");
}
const response = await generateNovelTitles(selectedNovelId, {
provider: llm.provider,
@@ -106,7 +106,7 @@ export default function TitleFactoryPanel({ genreTree, novels }: TitleFactoryPan
mutationFn: (suggestion: TitleFactorySuggestion) => {
const resolvedGenreId = mode === "novel" ? selectedNovel?.genre?.id ?? null : genreId || null;
const description = mode === "novel"
- ? `来源项目:${selectedNovel?.title ?? "未命名项目"}`
+ ? `来源项目:${selectedNovel?.title ?? "Unnamed project"}`
: mode === "adapt"
? `参考标题:${referenceTitle.trim()}`
: brief.trim().slice(0, 400);
@@ -125,14 +125,14 @@ export default function TitleFactoryPanel({ genreTree, novels }: TitleFactoryPan
},
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: queryKeys.titles.all });
- toast.success("标题已加入标题库。");
+ toast.success("The title has been added to the title library.");
},
});
const handleCopy = async (suggestion: TitleFactorySuggestion) => {
await navigator.clipboard.writeText(suggestion.title);
setSelectedTitle(suggestion.title);
- toast.success("标题已复制到剪贴板。");
+ toast.success("Title copied to clipboard.");
};
const handlePrimaryAction = async (suggestion: TitleFactorySuggestion) => {
@@ -152,9 +152,9 @@ export default function TitleFactoryPanel({ genreTree, novels }: TitleFactoryPan
- 按小说生成
- 自由工坊
- 参考改编
+ Generate by novel
+ free workshop
+ Reference adaptation
@@ -162,15 +162,15 @@ export default function TitleFactoryPanel({ genreTree, novels }: TitleFactoryPan
+ Suitable for works that have filled in the introduction and type, the system will generate candidate titles based on the project information.
+
- 创作简报
-
+ Create a brief
+
setBrief(event.target.value)}
- placeholder="描述题材、主角卖点、冲突、文风和读者期待。越具体,标题越有区分度。"
+ placeholder="Describe the subject matter, protagonist’s selling point, conflict, writing style, and reader expectations. The more specific the title, the more distinguishing it will be."
/>
- 类型过滤
-
+ Type filtering
+
setGenreId(event.target.value)}
>
-
- 参考标题
-
+ Reference title
+
setReferenceTitle(event.target.value)}
- placeholder="例如:我在废土捡属性"
+ placeholder="For example: I am picking up attributes in the wasteland"
className={inputClassName}
/>
- 类型过滤
-
+ Type filtering
+
setGenreId(event.target.value)}
>
-
setBrief(event.target.value)}
- placeholder="说明你的作品题材、人物与卖点。系统会参考标题节奏,但不会直接照抄。"
+ placeholder="Explain the subject matter, characters and selling points of your work. The system will refer to the title rhythm, but will not copy it directly."
/>
+ Reusable world samples are kept here. When the novel needs to use the world, import it from the basic information page of the novel as a copy of the world of this book, and then decide whether to synchronize manually.
+
@@ -262,18 +262,18 @@ export default function WorldList() {
{worlds.length === 0 ? (
- 暂无世界样本
- 先生成一个世界样本,后续可以导入到小说中作为本书世界副本。
+ No world samples yet
+ First generate a world sample, which can later be imported into the novel as a copy of the world in this book.
) : (
<>
-
世界样本的使用方式
+
How to use world samples
-
1. 在这里整理通用世界手册,让规则、势力、地点和张力清楚可复用。
-
2. 在小说基础信息页导入为“本书世界”,小说会使用自己的副本。
-
3. 本书副本和世界样本有差异时,由你手动决定推送或拉取。
+
1. Organize the general world manual here to make the rules, forces, locations and tensions clear and reusable.
+
2. Import it as "Book World" on the novel's basic information page, and the novel will use its own copy.
+
3. If there is a difference between the copy of this book and the world sample, it is up to you to manually decide whether to push or pull it.
@@ -306,19 +306,19 @@ export default function WorldList() {
{preview.ruleCount}
-
核心规则
+
core rules
{preview.forceCount}
-
势力
+
Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
{preview.locationCount}
-
地点
+
Place
{preview.relationCount}
-
关系
+
relation
@@ -326,7 +326,7 @@ export default function WorldList() {
- {deleteWorldMutation.isPending ? "删除中..." : "删除世界样本"}
+ {deleteWorldMutation.isPending ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : "Delete world sample"}
@@ -330,12 +330,12 @@ export default function WorldWorkspace() {
className="space-y-4"
>
- 整理世界手册
- 查看手册{featureFlags.worldVisEnabled ? "/可视化" : ""}
- 分层草稿
- 补齐手册
- 手册体检
- 资料与版本
+ Organizing the World Handbook
+ View manual{featureFlags.worldVisEnabled ? "/visualization" : ""}
+ layered draft
+ Complete manual
+ Manual Physical Examination
+ Information and version
@@ -388,14 +388,14 @@ export default function WorldWorkspace() {
<>
- 高级字段维护
+ Advanced field maintenance setAdvancedStructureOpen(false)}>
- 返回整理手册
-
+ Return to organizing manual
+
- 这里用于处理势力关系、地点控制权、结构导入等细节。普通整理优先回到世界手册。
-
+ This is used to handle details such as power relations, location control rights, and structure import. Ordinary sorting will give priority to returning to the world manual.
+
{id ? (
= {
- all: "全部类型",
- state: "政权",
- faction: "阵营",
- race: "种族",
- organization: "组织",
- other: "其他",
+ all: "All types",
+ state: "regime",
+ faction: "camp",
+ race: "Race",
+ organization: "organize",
+ other: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
};
const FACTION_TYPE_COLORS: Record = {
@@ -144,7 +144,7 @@ function getMapLabelMeta(node: GraphNode): string {
return [
node.directionHint ? DIRECTION_LABELS[node.directionHint] : "",
node.terrain ? truncateText(node.terrain, 8) : "",
- node.risk ? "风险" : "",
+ node.risk ? "risk" : "",
].filter(Boolean).join(" / ");
}
@@ -372,14 +372,14 @@ const ROUTE_STYLES: Record = {
const DIRECTION_LABELS: Record = {
north: "北",
- south: "南",
- east: "东",
- west: "西",
- center: "中",
- northeast: "东北",
- northwest: "西北",
- southeast: "东南",
- southwest: "西南",
+ south: "South",
+ east: "East",
+ west: "West",
+ center: "middle",
+ northeast: "northeast",
+ northwest: "northwest",
+ southeast: "southeast",
+ southwest: "southwest",
};
function DraggableGraph(props: {
@@ -431,7 +431,7 @@ function DraggableGraph(props: {
diff --git a/client/src/pages/worlds/components/generator/WorldGeneratorStepOne.tsx b/client/src/pages/worlds/components/generator/WorldGeneratorStepOne.tsx
index d549c7f6e..0d76644b0 100644
--- a/client/src/pages/worlds/components/generator/WorldGeneratorStepOne.tsx
+++ b/client/src/pages/worlds/components/generator/WorldGeneratorStepOne.tsx
@@ -17,18 +17,18 @@ const INSPIRATION_MODE_CARDS: Array<{
}> = [
{
value: "free",
- title: "从一句灵感开始",
- description: "适合已有题材、气质或故事舞台想法的世界样本。",
+ title: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ description: "World samples suitable for existing themes, temperaments, or story stage ideas.",
},
{
value: "reference",
- title: "参考作品改造",
- description: "适合借鉴已有作品的质感,再生成独立的架空世界。",
+ title: "Refinement of reference works",
+ description: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
},
{
value: "random",
- title: "让 AI 给方向",
- description: "适合还没有明确想法,只想先获得一个可用世界雏形。",
+ title: "Let AI give direction",
+ description: "I don’t have a clear idea yet, I just want to get a prototype of a usable world first.",
},
];
@@ -122,12 +122,12 @@ export default function WorldGeneratorStepOne(props: WorldGeneratorStepOneProps)
这个世界先叫什么?
- 名称可以留空,系统会先创建一份可继续整理的世界样本。
-
+ The name can be left blank, and the system will first create a world sample that can be further organized.
+
The theme base library is empty. Creating a world sample requires first preparing an optional theme base.
- 去题材基底库
-
+ Go to the theme base library
+
) : null}
- 先确定题材基底,再生成概念卡、世界属性和后续骨架选择。
-
+ First determine the theme base, then generate concept cards, world attributes and subsequent skeleton selection.
+
-
选择创建方式
+
Choose how to create
{INSPIRATION_MODE_CARDS.map((item) => (
onKnowledgeDocumentIdsChange(next ?? [])}
- title="参考知识库文档"
- description="这里选的是参考源,后续会先提取原作世界锚点,再生成架空改造方向。"
+ title="Reference knowledge base documentation"
+ description="The reference source is selected here. Later, the original world anchor point will be extracted first, and then the overhead transformation direction will be generated."
queryStatus="enabled"
/>
onDetailChange(option.id, event.target.value)}
/>
diff --git a/client/src/pages/worlds/components/generator/WorldReferenceSeedSelector.tsx b/client/src/pages/worlds/components/generator/WorldReferenceSeedSelector.tsx
index 6ea443815..811d8c2a0 100644
--- a/client/src/pages/worlds/components/generator/WorldReferenceSeedSelector.tsx
+++ b/client/src/pages/worlds/components/generator/WorldReferenceSeedSelector.tsx
@@ -15,23 +15,23 @@ const GROUP_META: Record<
}
> = {
rules: {
- title: "原作规则",
- description: "这个世界默认怎么运转,后面自动生成会参考这些底层规则。",
+ title: "original rules",
+ description: "This world operates by default, and subsequent automatic generation will refer to these underlying rules.",
selectionKey: "ruleIds",
},
factions: {
- title: "阵营立场",
- description: "谁站哪边、信什么、想推动什么。适合保留原作里的大方向。",
+ title: "camp position",
+ description: "Who is on which side, what you believe in, what you want to promote. Suitable for retaining the general direction of the original work.",
selectionKey: "factionIds",
},
forces: {
- title: "组织与势力",
- description: "具体公司、部门、帮派、人脉圈这类可直接上场的组织。",
+ title: "Organization and power",
+ description: "Specific companies, departments, gangs, and networks are organizations that can be directly involved.",
selectionKey: "forceIds",
},
locations: {
- title: "地点与场景",
- description: "城市、街区、公司、住处等可以直接拿来用的场景。",
+ title: "Places and Scenes",
+ description: "Scenarios that can be used directly in cities, neighborhoods, companies, residences, etc.",
selectionKey: "locationIds",
},
};
@@ -61,18 +61,18 @@ export default function WorldReferenceSeedSelector(props: {
if (visibleGroups.length === 0) {
return (
- 这次没有从参考作品里稳定提取出可直接沿用的组织、地点或规则,后面会继续按你的改造方向生成。
-
+ This time, no organizations, locations or rules have been stably extracted from the reference works that can be directly used. They will continue to be generated according to your transformation direction later.
+
);
}
return (
-
直接沿用原作设定
+
Directly use the original settings
- 系统会从参考作品里提取一批可沿用设定,并默认勾选。保留它们可以明显减少后续手动填写。
-
+ The system will extract a batch of inheritable settings from the reference works and check them by default. Keeping them can significantly reduce subsequent manual filling.
+
Identified settings from the original that can be directly inherited.
)}
diff --git a/client/src/pages/worlds/components/generator/worldGeneratorShared.ts b/client/src/pages/worlds/components/generator/worldGeneratorShared.ts
index 203bf5c79..68af06460 100644
--- a/client/src/pages/worlds/components/generator/worldGeneratorShared.ts
+++ b/client/src/pages/worlds/components/generator/worldGeneratorShared.ts
@@ -41,18 +41,18 @@ export const REFERENCE_MODE_OPTIONS: Array<{
}> = [
{
value: "adapt_world",
- label: "基于原作做架空改造",
- description: "保留原作世界基底,再决定哪些规则、势力和地点结构可以改造。",
+ label: "An overhead transformation based on the original work",
+ description: "Keep the base of the original world and then decide which rules, forces and location structures can be modified.",
},
{
value: "extract_base",
- label: "提取原作世界基底",
- description: "先稳定抽出原作世界骨架,后续扩写尽量围绕原作事实展开。",
+ label: "Extract the original world base",
+ description: "First, steadily extract the skeleton of the original world, and then try to expand the work around the facts of the original work.",
},
{
value: "tone_rebuild",
label: "只借原作气质与结构重建",
- description: "保留氛围、关系结构与生活手感,但允许较大幅度重建世界事实。",
+ description: "The atmosphere, relationship structure and life feel are retained, but the facts of the world are allowed to be reconstructed to a greater extent.",
},
];
@@ -66,12 +66,12 @@ export const DEFAULT_DIMENSIONS: Record = {
};
const DIMENSION_LABELS: Record = {
- foundation: "基础层",
- power: "力量层",
- society: "社会层",
- culture: "文化层",
- history: "历史层",
- conflict: "冲突层",
+ foundation: "base layer",
+ power: "strength layer",
+ society: "social layer",
+ culture: "cultural layer",
+ history: "historical layer",
+ conflict: "Conflict layer",
};
export const REFERENCE_SEED_SELECTION_KEYS: Record<
@@ -113,7 +113,7 @@ export function parseReferenceControlText(value: string): string[] {
}
export function getReferenceModeLabel(mode: WorldReferenceMode): string {
- return REFERENCE_MODE_OPTIONS.find((item) => item.value === mode)?.label ?? "基于原作做架空改造";
+ return REFERENCE_MODE_OPTIONS.find((item) => item.value === mode)?.label ?? "An overhead transformation based on the original work";
}
export function buildDefaultPropertySelectionState(options: WorldPropertyOption[]) {
diff --git a/client/src/pages/worlds/components/workspace/WorldAssetsTab.tsx b/client/src/pages/worlds/components/workspace/WorldAssetsTab.tsx
index 21192285b..f70ddf59b 100644
--- a/client/src/pages/worlds/components/workspace/WorldAssetsTab.tsx
+++ b/client/src/pages/worlds/components/workspace/WorldAssetsTab.tsx
@@ -64,33 +64,33 @@ type AssetTool = "visualAssets" | "references" | "library" | "snapshots" | "expo
const WORLD_ASSET_PRESETS = [
{
icon: Map,
- title: "世界地图",
- description: "用区域、道路、边境和故事地点解释角色如何移动,冲突会在哪里爆发。",
- readiness: "先补故事舞台、地点风险和势力控制区。",
+ title: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ description: "Use areas, roads, borders, and story locations to explain how characters move and where conflict breaks out.",
+ readiness: "First make up the story stage, location risks and power control areas.",
},
{
icon: Network,
- title: "势力图谱",
- description: "把势力、阵营、盟友、敌对和附庸关系整理成可视化关系网。",
- readiness: "先补主要势力、当前目标和彼此压力。",
+ title: "power map",
+ description: "Organize the relationships of forces, camps, allies, enemies and vassals into a visual relationship network.",
+ readiness: "First make up for the main forces, current goals and mutual pressure.",
},
{
icon: GitFork,
- title: "世界时间线",
- description: "记录重大事件、灾变、王朝更替和局势变化,让世界进展有轨迹。",
- readiness: "先补核心冲突、共同后果和关键历史节点。",
+ title: "world timeline",
+ description: "Record major events, disasters, dynasty changes and situation changes to keep the world's progress on track.",
+ readiness: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
},
{
icon: GitCompareArrows,
- title: "角色关系网",
- description: "把角色与势力、地点、资源和禁忌关系连接起来,减少设定漂移。",
- readiness: "先补角色归属、阵营压力和关键地点。",
+ title: "role network",
+ description: "Connect characters with forces, locations, resources and taboo relationships to reduce setting drift.",
+ readiness: "First make up for character affiliation, camp pressure and key locations.",
},
{
icon: Workflow,
- title: "力量体系树",
- description: "把力量来源、升级路径、代价和禁忌边界整理成层级结构。",
- readiness: "先补核心规则、代价和不可突破的边界。",
+ title: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ description: "Organize sources of power, upgrade paths, costs, and taboo boundaries into a hierarchical structure.",
+ readiness: "First, make up for the core rules, costs and unbreakable boundaries.",
},
];
@@ -164,43 +164,43 @@ export default function WorldAssetsTab(props: WorldAssetsTabProps) {
return (
- 世界资料与版本
+ World data and versions
+ Maps, power maps, timelines, and system trees all extend from the World Manual. First sort out the rules, forces, locations and tensions, and then generate visual assets.
+
Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
+
Organizational power
+
resource
+
Event
+
Props and wonders
- {publishPending ? "保存中..." : "保存素材"}
+ {publishPending ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : "Preserved material"}
setPublishDescription(event.target.value)}
- placeholder="可选描述(留空时默认使用当前分层内容)"
+ placeholder="Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."
/>
+ It can be understood as "the underlying rules of this world that cannot be broken casually." You will refer to this for subsequent automatic generation and manual physical examination.
+
diff --git a/client/src/pages/worlds/components/workspace/WorldConsistencyTab.tsx b/client/src/pages/worlds/components/workspace/WorldConsistencyTab.tsx
index b13e8a88f..d55e9cdc0 100644
--- a/client/src/pages/worlds/components/workspace/WorldConsistencyTab.tsx
+++ b/client/src/pages/worlds/components/workspace/WorldConsistencyTab.tsx
@@ -40,61 +40,61 @@ export default function WorldConsistencyTab(props: WorldConsistencyTabProps) {
return (
- 世界手册体检
+ World Manual Physical Examination
-
世界手册体检
+
World Manual Physical Examination
- 检查核心规则、题材信号、力量体系和冲突支撑是否互相冲突。发现问题后逐条处理即可。
-
+ Check if core rules, genre signals, power structures, and conflict supports conflict with each other. Once problems are discovered, deal with them one by one.
+
- {localizeConsistencyIssueDetail(activeIssue) ?? "可以结合世界手册复核这条风险。"}
+ {localizeConsistencyIssueDetail(activeIssue) ?? "This risk can be reviewed in conjunction with the World Handbook."}
+ No consistency issues have been logged yet, the results will be displayed here after running the check.
+
)}
diff --git a/client/src/pages/worlds/components/workspace/WorldDeepeningTab.tsx b/client/src/pages/worlds/components/workspace/WorldDeepeningTab.tsx
index ae889ce6e..42cf08a9a 100644
--- a/client/src/pages/worlds/components/workspace/WorldDeepeningTab.tsx
+++ b/client/src/pages/worlds/components/workspace/WorldDeepeningTab.tsx
@@ -43,18 +43,18 @@ export default function WorldDeepeningTab(props: WorldDeepeningTabProps) {
return (
- 补齐世界手册
+ Completing the World Manual
+ The system will ask a small number of questions based on this world manual. The answers will be integrated into the world setting to help make rules, forces, locations, and conflicts clearer.
+
- {generatePending ? "生成中..." : "生成补齐问题"}
+ {generatePending ? "Generating..." : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."}
@@ -62,7 +62,7 @@ export default function WorldDeepeningTab(props: WorldDeepeningTabProps) {
+ First, clearly understand the reading impression, core rules, main forces, story stage and conflict tension of this world; then enter the corresponding block when fine-tuning is needed.
+
- 查看手册
-
+ View manual
+
- {savePending ? "保存中..." : "保存手册"}
+ {savePending ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : "save manual"}
@@ -148,7 +148,7 @@ export default function WorldHandbookEditor(props: {
-
+
setDraftStructure((prev) => (prev ? { ...prev, profile: { ...prev.profile, summary: value } } : prev))
}
- placeholder="用一段话让作者知道这个世界长什么样、故事会从哪里开始。"
+ placeholder="Use a paragraph to let the author know what the world looks like and where the story will begin."
/>
-
+
@@ -247,7 +247,7 @@ export default function WorldHandbookEditor(props: {
prev ? { ...prev, profile: { ...prev.profile, coreConflict: value } } : prev,
)
}
- placeholder="例如:星核枯竭让修行者争夺寿命,朝廷想封锁真相,边境异魔趁机入侵。"
+ placeholder="For example: the depletion of the star core makes practitioners compete for life span, the court wants to seal the truth, and alien monsters from the border take the opportunity to invade."
minRows={3}
/>
@@ -259,18 +259,18 @@ export default function WorldHandbookEditor(props: {
-
AI 辅助整理
+
AI-assisted organizing
- 让 AI 根据已有内容补齐一个手册区块;你可以继续改写后再保存。
-
+ Let the AI complete a manual block based on existing content; you can continue to rewrite and then save.
+
{[
- { key: "profile", label: "世界概要" },
- { key: "rules", label: "规则" },
- { key: "factions", label: "势力" },
- { key: "locations", label: "地点" },
- { key: "relations", label: "张力" },
+ { key: "profile", label: "world summary" },
+ { key: "rules", label: "rule" },
+ { key: "factions", label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
+ { key: "locations", label: "Place" },
+ { key: "relations", label: "tension" },
].map((item) => (
- {generatePending ? "补齐中..." : "补齐选中区块"}
+ {generatePending ? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." : "Complete the selected block"}
@@ -293,121 +293,121 @@ export default function WorldHandbookEditor(props: {
setEditingSection("rules")}>
- 整理规则
-
+ Organize rules
+
}
>
[rule.name, rule.summary].filter(Boolean).join(":")),
- "补充 2-3 条必须遵守的核心规则。",
+ "Add 2-3 additional core rules that must be followed.",
)}
- fallback="补充 2-3 条必须遵守的核心规则。"
+ fallback="Add 2-3 additional core rules that must be followed."
/>
setEditingSection("forces")}>
- 整理势力
-
+ Organize forces
+
}
>
[force.name, force.currentObjective].filter(Boolean).join(":")),
- "补充主要势力后,角色身份和阵营冲突会更清楚。",
+ "After the main forces are supplemented, character identities and camp conflicts will become clearer.",
)}
- fallback="补充主要势力后,角色身份和阵营冲突会更清楚。"
+ fallback="After the main forces are supplemented, character identities and camp conflicts will become clearer."
/>
force.pressure),
- "补充势力给主角和世界秩序造成的压力。",
+ "The pressure that supplementary forces put on the protagonist and the world order.",
)}
- fallback="补充势力给主角和世界秩序造成的压力。"
+ fallback="The pressure that supplementary forces put on the protagonist and the world order."
/>
setEditingSection("locations")}>
- 整理地点
-
+ Organize the place
+
}
>
[location.name, location.narrativeFunction || location.terrain].filter(Boolean).join(":"),
),
- "补充开局地点、试炼地点、冲突地点或真相地点。",
+ "Supplementary starting location, trial location, conflict location or truth location.",
)}
- fallback="补充开局地点、试炼地点、冲突地点或真相地点。"
+ fallback="Supplementary starting location, trial location, conflict location or truth location."
/>
location.risk),
- "补充进入地点会遇到的阻力、代价或身份风险。",
+ "Supplement the resistance, costs, or identity risks associated with entering a location.",
)}
- fallback="补充进入地点会遇到的阻力、代价或身份风险。"
+ fallback="Supplement the resistance, costs, or identity risks associated with entering a location."
/>
[relation.relation, relation.tension || relation.detail].filter(Boolean).join(":"),
),
- "补充谁与谁结盟、敌对、竞争或互相利用。",
+ "Complementing who is allied with, hostile to, competing with, or exploiting whom.",
)}
- fallback="补充谁与谁结盟、敌对、竞争或互相利用。"
+ fallback="Complementing who is allied with, hostile to, competing with, or exploiting whom."
/>
@@ -418,11 +418,11 @@ export default function WorldHandbookEditor(props: {
- 正在整理选中区块,保存后会更新上方手册概览。
-
+ The selected blocks are being sorted, and the manual overview above will be updated after saving.
+
) : null}
diff --git a/client/src/pages/worlds/components/workspace/WorldLayersTab.tsx b/client/src/pages/worlds/components/workspace/WorldLayersTab.tsx
index 64f422acc..4222b962d 100644
--- a/client/src/pages/worlds/components/workspace/WorldLayersTab.tsx
+++ b/client/src/pages/worlds/components/workspace/WorldLayersTab.tsx
@@ -91,23 +91,21 @@ export default function WorldLayersTab(props: WorldLayersTabProps) {
return (
- 分层整理世界
+ Organize the world in layers
- {generateAllPending ? "整理中..." : isInitialLayerGeneration ? "AI 整理六层摘要" : "重新整理六层摘要"}
+ {generateAllPending ? "In progress..." : isInitialLayerGeneration ? "AI is reorganizing six layers of summaries" : "Reorganizing six layers of summaries"}
- {isInitialLayerGeneration
- ? "系统会把世界手册整理为基础、力量、社会、文化、历史和冲突六个写作摘要。"
- : "有世界骨架时会按手册内容整理摘要;没有骨架的旧世界才会补写缺失层级。"}
+ {isInitialLayerGeneration ? "The system will organize the world handbook into six writing summaries: Foundation, Power, Society, Culture, History, and Conflict." : "When there is a world skeleton, the summaries will be organized according to the handbook's content; for old worlds without a skeleton, missing layers will be added."}
- {compactText(profile?.summary, summary ?? "补充一句能让创作者快速理解的世界概要。", 180)}
+ {compactText(profile?.summary, summary ?? "Add a summary of the world that creators can quickly understand.", 180)}
- {compactText(profile?.coreConflict, "补充核心冲突后,系统会更容易把世界转化为持续推动剧情的压力。", 160)}
+ {compactText(profile?.coreConflict, "By supplementing the core conflict, the system will more easily transform the world into a constant pressure to drive the plot.", 160)}
-
作为世界样本可提供
+
Available as world sample
-
角色身份边界、势力归属与禁忌组合。
-
开局地点、升级路径与冲突来源。
-
写作时需要持续遵守的规则。
+
Character identity boundaries, power affiliation and taboo combinations.
+
Starting location, upgrade paths and sources of conflict.
+
Rules to keep following while writing.
@@ -206,37 +199,37 @@ export default function WorldOverviewTab(props: WorldOverviewTabProps) {
- 世界手册待成型
+ The world manual is waiting to be formed
- {compactText(summary, "先让 AI 或手册编修整理世界骨架,再把它作为可复用世界样本。", 160)}
+ {compactText(summary, "Let the AI or manual editor organize the world skeleton first, and then use it as a reusable world sample.", 160)}
- 世界手册会把零散设定整理成规则、势力、地点和剧情压力,方便作者理解,也方便本书使用。
-
+ The World Manual will organize scattered settings into rules, forces, locations and plot pressures to facilitate the author's understanding and use of this book.
+
-
建议下一步
+
Suggest next steps
- AI 构建世界
-
+ AI builds the world
+
- 编修手册
-
+ Editing Manual
+
-
-
-
-
+
+
+
+
{sections.length > 0 ? (
-
已有设定片段
+
Already have a set fragment
{sections.map((section) => (
@@ -298,38 +291,38 @@ export default function WorldOverviewTab(props: WorldOverviewTabProps) {
- 世界资产入口
-
+ World Asset Portal
+
- 地图和图谱是世界手册的可视化资产,不参与自动同步覆盖,也不替代世界手册的规则来源。
-
+ Maps and atlases are visual assets of the World Manual and do not participate in automatic synchronization coverage, nor do they replace the source of the rules of the World Manual.
+
- 预留入口
+ Reserve entrance
0 ? "可整理" : "待补地点"}
+ title="Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."
+ description="Carrying area, location connection, story place and conflict heat."
+ status={(structure?.locations.length ?? 0) > 0 ? "Organizable" : "Waiting point"}
/>
0 ? "可整理" : "待补势力"}
+ title="power map"
+ description="Carrying power nodes, allies and enemies, control relationships and power balance."
+ status={(structure?.forces.length ?? 0) + (structure?.factions.length ?? 0) > 0 ? "Organizable" : "Forces to be filled"}
/>
0 ? "可整理" : "待补规则"}
+ title="Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."
+ description="Carrying levels, resources, costs, taboos and breaking boundaries."
+ status={(structure?.rules.axioms.length ?? 0) > 0 ? "Organizable" : "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."}
/>
@@ -132,8 +132,8 @@ export default function WorldHandbookLocationSection(props: {
- 增加故事地点
-
+ Add story location
+
);
}
diff --git a/client/src/pages/worlds/components/workspace/handbook/WorldHandbookRuleSection.tsx b/client/src/pages/worlds/components/workspace/handbook/WorldHandbookRuleSection.tsx
index 0800fdca2..f82086459 100644
--- a/client/src/pages/worlds/components/workspace/handbook/WorldHandbookRuleSection.tsx
+++ b/client/src/pages/worlds/components/workspace/handbook/WorldHandbookRuleSection.tsx
@@ -40,18 +40,18 @@ export default function WorldHandbookRuleSection(props: {
-
+
setDraftStructure((prev) => (prev ? { ...prev, rules: { ...prev.rules, summary: value } } : prev))
}
- placeholder="例如:所有超凡力量都来自星核借贷,越级使用会透支寿命,并被天机阁记录。"
+ placeholder="For example: All extraordinary powers come from star core loans. Use beyond the level will overdraw the lifespan and be recorded by Tianji Pavilion."
minRows={3}
/>
@@ -59,7 +59,7 @@ export default function WorldHandbookRuleSection(props: {
{draftStructure.rules.axioms.map((rule: WorldRule, index) => (
-
规则 {index + 1}
+
rule {index + 1}
- 移除
-
+ Remove
+
-
+
@@ -90,10 +90,10 @@ export default function WorldHandbookRuleSection(props: {
: prev,
)
}
- placeholder="星核借贷、血脉不可逆、禁城不得施法"
+ placeholder="Star core loan, bloodline is irreversible, and spells cannot be cast in the forbidden city."
/>
-
+
@@ -106,11 +106,11 @@ export default function WorldHandbookRuleSection(props: {
: prev,
)
}
- placeholder="这条规则在故事里意味着什么?"
+ placeholder="What does this rule mean in the story?"
minRows={3}
/>
-
+
@@ -126,10 +126,10 @@ export default function WorldHandbookRuleSection(props: {
: prev,
)
}
- placeholder="寿命、记忆、身份、资源、阵营信任..."
+ placeholder="Lifespan, memory, identity, resources, camp trust..."
/>
-
+
@@ -145,7 +145,7 @@ export default function WorldHandbookRuleSection(props: {
: prev,
)
}
- placeholder="谁也不能无代价复活;低阶角色不能越过封印规则。"
+ placeholder="No one can be resurrected without paying a price; low-level characters cannot exceed the sealing rules."
/>
@@ -154,8 +154,8 @@ export default function WorldHandbookRuleSection(props: {
-
+
setDraftStructure((prev) => (prev ? { ...prev, profile: { ...prev.profile, coreConflict: value } } : prev))
}
- placeholder="例如:资源枯竭、秩序崩塌、两套力量体系互相排斥。"
+ placeholder="Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know."
/>
-
+
@@ -39,30 +39,30 @@ export default function WorldHandbookTensionSection(props: {
prev ? { ...prev, rules: { ...prev.rules, sharedConsequences: textToList(value) } } : prev,
)
}
- placeholder="力量越强越接近异化
城市越繁荣越依赖危险资源"
+ placeholder="The stronger the power, the closer to alienation. The more prosperous the city, the more dependent it is on dangerous resources."
/>
-
+
setDraftStructure((prev) => (prev ? { ...prev, rules: { ...prev.rules, taboo: textToList(value) } } : prev))
}
- placeholder="凡人不能无代价操控星核
朝廷密探不能公开加入异魔阵营"
+ placeholder="Mortals cannot control the star core without any cost. Imperial spies cannot openly join the alien camp."
/>
+ When you need to fine-tune power relationships, location control rights, and import structural data, enter advanced field maintenance. Ordinary authors only need to maintain the manual content of this page.
+
Alliance = abstract stance, route, or world alignment; Faction = specific organization, circle, network, or institution.
World-class default rules such as "social pressure mechanisms," "industry operating rules," and "interpersonal network rules" should be written in the "rule center" first, and not stuffed into the faction card.
+ The power card describes a specific organization or circle that can exert pressure, occupy a location, and participate in a network of relationships.
+
{force.factionId ? (
- 所属阵营:{factionNameById.get(force.factionId) || force.factionId}
+ Belong to the camp:{factionNameById.get(force.factionId) || force.factionId}
diff --git a/client/src/pages/worlds/components/workspace/worldWorkspaceShared.ts b/client/src/pages/worlds/components/workspace/worldWorkspaceShared.ts
index c96e2fc40..639d56f72 100644
--- a/client/src/pages/worlds/components/workspace/worldWorkspaceShared.ts
+++ b/client/src/pages/worlds/components/workspace/worldWorkspaceShared.ts
@@ -1,12 +1,12 @@
import type { World } from "@ai-novel/shared/types/world";
export const LAYERS = [
- { key: "foundation", label: "L1 基础层", primaryField: "background" },
- { key: "power", label: "L2 力量层", primaryField: "magicSystem" },
- { key: "society", label: "L3 社会层", primaryField: "politics" },
- { key: "culture", label: "L4 文化层", primaryField: "cultures" },
- { key: "history", label: "L5 历史层", primaryField: "history" },
- { key: "conflict", label: "L6 冲突层", primaryField: "conflicts" },
+ { key: "foundation", label: "L1 base layer", primaryField: "background" },
+ { key: "power", label: "L2 Strength Layer", primaryField: "magicSystem" },
+ { key: "society", label: "L3 Social Level", primaryField: "politics" },
+ { key: "culture", label: "L4 Cultural Layer", primaryField: "cultures" },
+ { key: "history", label: "L5 Historical Layer", primaryField: "history" },
+ { key: "conflict", label: "L6 Conflict Layer", primaryField: "conflicts" },
] as const;
export type LayerKey = (typeof LAYERS)[number]["key"];
@@ -26,14 +26,7 @@ export type LayerField =
| "economy"
| "factions";
-export const LAYER_STATUS_LABELS: Record = {
- pending: "待生成",
- generated: "已生成",
- confirmed: "已确认",
- stale: "待重建",
-};
-
-export const LAYER_FIELDS_BY_KEY: Record = {
+export const LAYER_STATUS_LABELS: Record = { pending: "To be generated", generated: "Generated", confirmed: "Confirmed", stale: "To be rebuilt", }; export const LAYER_FIELDS_BY_KEY: Record = {
foundation: ["background", "geography"],
power: ["magicSystem", "technology"],
society: ["politics", "races", "factions"],
@@ -58,19 +51,19 @@ export type RefineAttribute =
| "factions";
export const REFINE_ATTRIBUTE_OPTIONS: Array<{ value: RefineAttribute; label: string }> = [
- { value: "background", label: "基础背景" },
- { value: "geography", label: "地理环境" },
- { value: "cultures", label: "文化习俗" },
- { value: "magicSystem", label: "力量体系" },
- { value: "politics", label: "政治结构" },
- { value: "races", label: "种族设定" },
- { value: "religions", label: "宗教信仰" },
- { value: "technology", label: "技术体系" },
- { value: "history", label: "历史脉络" },
- { value: "economy", label: "经济系统" },
- { value: "conflicts", label: "核心冲突" },
- { value: "description", label: "世界概述" },
- { value: "factions", label: "势力关系" },
+ { value: "background", label: "basic background" },
+ { value: "geography", label: "geographical environment" },
+ { value: "cultures", label: "cultural customs" },
+ { value: "magicSystem", label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
+ { value: "politics", label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
+ { value: "races", label: "Race settings" },
+ { value: "religions", label: "religious beliefs" },
+ { value: "technology", label: "Technical system" },
+ { value: "history", label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
+ { value: "economy", label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
+ { value: "conflicts", label: "core conflict" },
+ { value: "description", label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
+ { value: "factions", label: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know." },
];
export function normalizeLayerText(raw: unknown): string {
diff --git a/client/src/pages/worlds/worldConsistencyUi.ts b/client/src/pages/worlds/worldConsistencyUi.ts
index 985fed7dd..c11b661fd 100644
--- a/client/src/pages/worlds/worldConsistencyUi.ts
+++ b/client/src/pages/worlds/worldConsistencyUi.ts
@@ -2,50 +2,50 @@ import type { WorldConsistencyIssue, WorldConsistencyReport } from "@ai-novel/sh
const ISSUE_CODE_LABELS: Record = {
THEMATIC_INCOHERENCE: "主题框架不一致",
- REDUNDANT_AXIOM_APPLICATION: "世界公理重复套用",
- AXIOM_VIOLATION: "世界公理冲突",
- GENRE_MISMATCH: "题材信号冲突",
- AXIOM_MAGIC_CONFLICT: "公理与力量体系冲突",
- TECH_ERA_MISMATCH: "技术时代混杂",
- CONFLICT_WEAK: "核心冲突偏弱",
- BASELINE_PASS: "规则检查通过",
+ REDUNDANT_AXIOM_APPLICATION: "Repeated application of world axioms",
+ AXIOM_VIOLATION: "world justice conflict",
+ GENRE_MISMATCH: "Theme Signal Conflict",
+ AXIOM_MAGIC_CONFLICT: "Axiom and power system conflict",
+ TECH_ERA_MISMATCH: "Mixed technological times",
+ CONFLICT_WEAK: "The core conflict is weak",
+ BASELINE_PASS: "Rule check passed",
};
const ISSUE_MESSAGE_LABELS: Record = {
- THEMATIC_INCOHERENCE: "检索补充内容引入了与核心设定不一致的主题框架。",
- REDUNDANT_AXIOM_APPLICATION: "补充内容重复复述了既有公理,没有增加新的有效约束。",
- AXIOM_VIOLATION: "世界名或核心概念与既有公理、背景存在冲突。",
- GENRE_MISMATCH: "题材信号与世界手册约束不一致。",
- AXIOM_MAGIC_CONFLICT: "世界公理与力量体系设定发生冲突。",
- TECH_ERA_MISMATCH: "技术时代感混杂,缺少足够解释。",
- CONFLICT_WEAK: "核心冲突信息过薄,支撑力不足。",
- BASELINE_PASS: "规则层面未发现明显硬冲突。",
+ THEMATIC_INCOHERENCE: "Searching for supplemental content introduces thematic frameworks that are inconsistent with the core setting.",
+ REDUNDANT_AXIOM_APPLICATION: "The supplementary content reiterates existing axioms without adding new valid constraints.",
+ AXIOM_VIOLATION: "The world name or core concept conflicts with existing axioms and background.",
+ GENRE_MISMATCH: "Theme signals are inconsistent with world manual constraints.",
+ AXIOM_MAGIC_CONFLICT: "The world's axioms conflict with the setting of the power system.",
+ TECH_ERA_MISMATCH: "The sense of the technological era is mixed and lacks sufficient explanation.",
+ CONFLICT_WEAK: "The core conflict information is too thin and lacks support.",
+ BASELINE_PASS: "No obvious hard conflicts were found at the rule level.",
};
const ISSUE_DETAIL_LABELS: Record = {
- THEMATIC_INCOHERENCE: "辅助上下文引入了原始设定里没有明确建立的主题表达,容易让世界主轴发生漂移。",
- REDUNDANT_AXIOM_APPLICATION: "补充内容主要在重复已有规则,建议删去冗余复述,只保留真正新增的约束。",
- AXIOM_VIOLATION: "命名、题材承诺或核心概念与既有世界底层规则不一致,需要统一主设定。",
- GENRE_MISMATCH: "命名或关键词传递出了另一种题材预期,和世界手册强调的风格与规则不匹配。",
- AXIOM_MAGIC_CONFLICT: "你在世界公理里限制了超自然/魔法内容,但力量体系或相关文本又重新引入了它。",
- TECH_ERA_MISMATCH: "技术描述同时出现了不同时代层级的元素,但没有交代来源、限制或过渡逻辑。",
- CONFLICT_WEAK: "建议补充冲突双方、触发事件、升级路径和失败代价,让世界主矛盾更清晰。",
+ THEMATIC_INCOHERENCE: "The auxiliary context introduces thematic expressions that are not clearly established in the original setting, which can easily cause the main axis of the world to drift.",
+ REDUNDANT_AXIOM_APPLICATION: "The supplementary content mainly repeats existing rules. It is recommended to delete redundant repetitions and only retain the truly new constraints.",
+ AXIOM_VIOLATION: "The naming, theme commitment or core concepts are inconsistent with the underlying rules of the existing world, and the main setting needs to be unified.",
+ GENRE_MISMATCH: "Naming or keywords convey another theme expectation, which does not match the style and rules emphasized in the World Manual.",
+ AXIOM_MAGIC_CONFLICT: "You limit supernatural/magical content in the world axioms, but the power system or related text reintroduces it.",
+ TECH_ERA_MISMATCH: "The technical description simultaneously presents elements from different era levels, but does not explain the sources, limitations or transition logic.",
+ CONFLICT_WEAK: "It is recommended to add conflicting parties, triggering events, escalation paths and failure costs to make the main conflict in the world clearer.",
};
const FIELD_LABELS: Record = {
- description: "世界概述",
- background: "背景设定",
- geography: "地理环境",
- cultures: "文化习俗",
- magicSystem: "力量体系",
- politics: "政治结构",
- races: "种族设定",
- religions: "宗教信仰",
- technology: "技术体系",
- conflicts: "核心冲突",
- history: "历史脉络",
- economy: "经济系统",
- factions: "势力关系",
+ description: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ background: "background settings",
+ geography: "geographical environment",
+ cultures: "cultural customs",
+ magicSystem: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ politics: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ races: "Race settings",
+ religions: "religious beliefs",
+ technology: "Technical system",
+ conflicts: "core conflict",
+ history: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ economy: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
+ factions: "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.",
};
function hasChinese(text: string): boolean {
@@ -57,7 +57,7 @@ function localizeSummary(summary: string, status: WorldConsistencyReport["status
return summary;
}
if (/Consistency check passed/i.test(summary)) {
- return "世界手册体检通过,未发现明显硬冲突。";
+ return "The World Manual physical examination passed and no obvious hard conflicts were found.";
}
const errorCount = issues.filter((item) => item.severity === "error").length;
const warnCount = issues.filter((item) => item.severity === "warn").length;
@@ -67,7 +67,7 @@ function localizeSummary(summary: string, status: WorldConsistencyReport["status
if (status === "warn") {
return `检测到 ${warnCount} 个警告项,建议继续修正。`;
}
- return "世界手册体检已完成。";
+ return "World Handbook Physical Examination Completed.";
}
export function parseConsistencyReport(raw: string | null | undefined, issues: WorldConsistencyIssue[]): WorldConsistencyReport | null {
@@ -98,11 +98,11 @@ export function parseConsistencyReport(raw: string | null | undefined, issues: W
export function localizeConsistencySeverity(severity: WorldConsistencyIssue["severity"]): string {
switch (severity) {
case "error":
- return "严重冲突";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
case "warn":
- return "警告";
+ return "warn";
case "pass":
- return "通过";
+ return "pass";
default:
return severity;
}
@@ -111,29 +111,29 @@ export function localizeConsistencySeverity(severity: WorldConsistencyIssue["sev
export function localizeConsistencyStatus(status: WorldConsistencyIssue["status"] | WorldConsistencyReport["status"]): string {
switch (status) {
case "open":
- return "待处理";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
case "resolved":
- return "已解决";
+ return "Resolved";
case "ignored":
- return "已忽略";
+ return "Ignored";
case "error":
- return "存在严重冲突";
+ return "There is a serious conflict";
case "warn":
- return "存在警告";
+ return "There is a warning";
case "pass":
- return "检查通过";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
default:
return status;
}
}
export function localizeConsistencySource(source: WorldConsistencyIssue["source"]): string {
- return source === "llm" ? "模型审校" : "规则检查";
+ return source === "llm" ? "Model review" : "Rule checking";
}
export function localizeConsistencyField(targetField?: string | null): string {
if (!targetField) {
- return "未指定";
+ return "not specified";
}
return FIELD_LABELS[targetField] ?? targetField;
}
@@ -147,7 +147,7 @@ export function localizeConsistencyIssueMessage(issue: WorldConsistencyIssue): s
return issue.message;
}
return ISSUE_MESSAGE_LABELS[issue.code]
- ?? `${localizeConsistencyField(issue.targetField)}存在一致性风险。`;
+ ?? `The function ${localizeConsistencyField(issue.targetField)} has a consistency risk.`;
}
export function localizeConsistencyIssueDetail(issue: WorldConsistencyIssue): string | null {
diff --git a/client/src/pages/writingFormula/WritingFormulaPage.tsx b/client/src/pages/writingFormula/WritingFormulaPage.tsx
index 3d15bb924..8e40b1d69 100644
--- a/client/src/pages/writingFormula/WritingFormulaPage.tsx
+++ b/client/src/pages/writingFormula/WritingFormulaPage.tsx
@@ -311,11 +311,11 @@ export default function WritingFormulaPage() {
const reextractFeaturesMutation = useMutation({
mutationFn: async () => {
if (!selectedProfileId || !editor.sourceContent.trim()) {
- throw new Error("请先准备原文样本。");
+ throw new Error("Please prepare the original text sample first.");
}
return extractStyleFeaturesFromText({
- name: editor.name.trim() || selectedProfile?.name || "文本提取写法",
+ name: editor.name.trim() || selectedProfile?.name || "Text extraction writing method",
category: editor.category || undefined,
sourceText: editor.sourceContent,
provider: llm.provider,
@@ -343,7 +343,7 @@ export default function WritingFormulaPage() {
setMessage(
extractedFeatures.length > 0
? `已重新提取 ${extractedFeatures.length} 条特征,请确认后保存。`
- : "这次仍然没有生成可用特征,建议检查原文样本是否足够完整。",
+ : "This time there are still no usable features generated. It is recommended to check whether the original text sample is complete enough.",
);
},
});
@@ -371,7 +371,7 @@ export default function WritingFormulaPage() {
});
},
onSuccess: async () => {
- setMessage("写法资产保存完成。");
+ setMessage("Writing assets are saved.");
await refreshStyleData();
},
});
@@ -379,7 +379,7 @@ export default function WritingFormulaPage() {
const deleteProfileMutation = useMutation({
mutationFn: (id: string) => deleteStyleProfile(id),
onSuccess: async (_response, deletedProfileId) => {
- setMessage("这套写法已删除。");
+ setMessage("This set of writing has been deleted.");
if (deletedProfileId === selectedProfileId) {
setSelectedProfileId("");
setActiveWorkspaceDialog(null);
@@ -409,7 +409,7 @@ export default function WritingFormulaPage() {
});
},
onSuccess: async () => {
- setMessage("这套写法会参与目标对象的生成。");
+ setMessage("Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.");
await refreshStyleData();
},
});
@@ -424,7 +424,7 @@ export default function WritingFormulaPage() {
const testWriteMutation = useMutation({
mutationFn: () => {
if (!selectedProfileId) {
- throw new Error("请先选择写法资产。");
+ throw new Error("Please select the writing asset first.");
}
return testWriteWithStyleProfile(selectedProfileId, {
@@ -443,7 +443,7 @@ export default function WritingFormulaPage() {
const detectionMutation = useMutation({
mutationFn: () => {
if (!selectedProfileId) {
- throw new Error("请先选择写法资产。");
+ throw new Error("Please select the writing asset first.");
}
return detectStyleIssues({
@@ -459,7 +459,7 @@ export default function WritingFormulaPage() {
const rewriteMutation = useMutation({
mutationFn: async () => {
if (!selectedProfileId) {
- throw new Error("请先选择写法资产。");
+ throw new Error("Please select the writing asset first.");
}
const report = detectionMutation.data?.data ?? (await detectStyleIssues({
@@ -489,7 +489,7 @@ export default function WritingFormulaPage() {
},
onSuccess: (response) => {
setRewritePreview(response.data?.content ?? "");
- setMessage("修订稿已经生成,可以继续在去 AI 味里检查和调整。");
+ setMessage("The revised manuscript has been generated and can continue to be checked and adjusted in AI flavor removal.");
},
});
@@ -504,9 +504,9 @@ export default function WritingFormulaPage() {
Style Engine V2
-
写法引擎
+
Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
-
+
{message ?
{message}
: null}
@@ -528,7 +528,7 @@ export default function WritingFormulaPage() {
onUseProfileForClean={(profileId) => openWorkspaceDialog("clean", profileId)}
onDeleteProfile={(profileId) => {
const profile = profiles.find((item) => item.id === profileId);
- const profileName = profile?.name ?? "这套写法";
+ const profileName = profile?.name ?? "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
const confirmed = window.confirm(`确认删除“${profileName}”吗?删除后无法恢复。`);
if (!confirmed) {
return;
@@ -577,10 +577,10 @@ export default function WritingFormulaPage() {
>
- 编辑当前写法
+ Edit current writing
- 这里专门整理写法本身的设定说明。应用测试和去 AI 味已经拆到独立入口,避免混在一个窗口里。
-
+ Here we specifically organize the setting instructions for the writing method itself. Application testing and AI deodorization have been split into separate entrances to avoid being mixed in one window.
+
@@ -626,10 +626,10 @@ export default function WritingFormulaPage() {
>
- 当前写法的应用与测试
+ Application and testing of current writing methods
- 这里专门处理绑定到小说、章节和试写验证,不修改写法字段本身。
-
+ This section specifically deals with binding to novels, chapters and trial writing verification, without modifying the writing field itself.
+
@@ -659,10 +659,10 @@ export default function WritingFormulaPage() {
>
- 去 AI 味
+ Get rid of AI flavor
- 这里专门做正文检测和修正,不进入写法字段编辑,也不混入绑定和试写操作。
-
+ This is dedicated to text detection and correction. It does not enter the writing field editing, nor does it mix in binding and trial writing operations.
+
diff --git a/client/src/pages/writingFormula/components/WritingFormulaAdvancedWorkspace.tsx b/client/src/pages/writingFormula/components/WritingFormulaAdvancedWorkspace.tsx
index f2e049ed5..012699ecb 100644
--- a/client/src/pages/writingFormula/components/WritingFormulaAdvancedWorkspace.tsx
+++ b/client/src/pages/writingFormula/components/WritingFormulaAdvancedWorkspace.tsx
@@ -38,7 +38,7 @@ export default function WritingFormulaAdvancedWorkspace(props: WritingFormulaAdv
{props.selectedProfile
? `当前正在维护「${props.selectedProfile.name}」这套写法的设定说明。应用测试和去 AI 味已经拆到独立入口,这里只负责把写法本身整理清楚。`
- : "当前还没有可编辑的写法。请先回到写法页列表,选中或新建一套写法。"}
+ : "There is currently no editable writing method. Please return to the writing method page list first, select or create a new writing method."}
- 从小说基础信息设置书级默认写法
+ Set the default book-level writing method from the basic information of the novel
+ The writing engine is responsible for creating, testing and organizing writing assets. Which default writing method should be used for the current novel? Please go back to the basic information of the novel to confirm, and then enter the subsequent director and text process.
+
-
现在应该从哪里进入
+
Where should I enter now?
{novelId
? `当前小说${novelTitle ? `《${novelTitle}》` : ""}的“默认写法”已经放到小说基础信息页里。`
- : "请先进入某本小说的基础信息页,在那里确认书级默认写法。"}
+ : "Please first go to the basic information page of a certain novel and confirm the default writing method of the book level there."}
-
两个入口分别负责什么
+
What are the two entrances responsible for?
-
小说页:为当前小说选择默认写法,触发推荐、比较候选,并决定何时带入自动导演。
-
写法引擎:继续负责整理写法资产、试写、去 AI 味和规则管理。
+
Novel page: Select the default writing method for the current novel, trigger recommendations, compare candidates, and decide when to bring in the automatic director.
+
Writing engine: continues to be responsible for organizing writing assets, trial writing, removing AI flavor and rule management.
-
下一步
+
Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
+ First go to the novel page to confirm the default writing method of this book. If there is no suitable writing method in the current asset library, return to the writing method engine to create or organize assets.
+
- 去小说页设置默认写法
+ Go to the novel page to set the default writing method
- 编辑当前写法
-
+ Edit current writing
+
- 新建一套写法
-
+ Create a new writing style
+
diff --git a/client/src/pages/writingFormula/components/WritingFormulaCleanFlow.tsx b/client/src/pages/writingFormula/components/WritingFormulaCleanFlow.tsx
index a977edeff..034f8219f 100644
--- a/client/src/pages/writingFormula/components/WritingFormulaCleanFlow.tsx
+++ b/client/src/pages/writingFormula/components/WritingFormulaCleanFlow.tsx
@@ -43,16 +43,16 @@ export default function WritingFormulaCleanFlow(props: WritingFormulaCleanFlowPr
return (
- 给这段稿子去 AI 味
+ Give this article an AI flavor
+ The test will first help you point out which paragraph is written like a template, and then give you a revised version that can be directly compared. For recurring problems, I will organize them into rule suggestions so that you can take them back to your current writing method and edit them to continue processing.
+
+ Run a test first and it will tell you which passages are most like AI speaking and why they trip up readers.
+
)}
-
修订前后 diff
+
diff before and after revision
- 这里按段落行做轻量对比,方便你快速判断这次修正是在压模板感,还是把原有语气也一起削掉了。
-
+ Here is a light comparison based on paragraph lines, so that you can quickly judge whether this correction is to suppress the template feeling, or whether it also removes the original tone.
+
{rewritePreview ? (
{diffRows.map((row, index) => (
-
原稿 {index + 1}
+
original manuscript {index + 1}
{row.before || " "}
-
修订稿 {index + 1}
+
revised manuscript {index + 1}
{row.after || " "}
@@ -137,22 +137,22 @@ export default function WritingFormulaCleanFlow(props: WritingFormulaCleanFlowPr
) : (
- 一键修订后,这里会直接出现前后对照。
-
+ After one-click revision, a before-and-after comparison will appear directly here.
+
)}
-
可沉淀成规则的重复问题
+
Repetitive questions that can be settled into rules
- 这一版不会强行替你落库,但会先把重复问题整理成建议,方便你带回当前写法编辑决定是否固化。
-
+ This version will not force you to put it into the library, but it will first organize repeated questions into suggestions, so that you can bring back the current writing method and edit it to decide whether to solidify it.
+
- 查看当前写法编辑
-
+ View current writing styleEdit
+
{suggestionDrafts.length > 0 ? (
@@ -164,8 +164,8 @@ export default function WritingFormulaCleanFlow(props: WritingFormulaCleanFlowPr
) : (
- 检测出问题后,这里才会出现可沉淀的规则建议。
-
+ Only after problems are detected will precipitable rule suggestions appear here.
+
)}
diff --git a/client/src/pages/writingFormula/components/WritingFormulaCleanPanel.tsx b/client/src/pages/writingFormula/components/WritingFormulaCleanPanel.tsx
index f98f26694..99d2f0de1 100644
--- a/client/src/pages/writingFormula/components/WritingFormulaCleanPanel.tsx
+++ b/client/src/pages/writingFormula/components/WritingFormulaCleanPanel.tsx
@@ -32,25 +32,25 @@ export default function WritingFormulaCleanPanel(props: WritingFormulaCleanPanel
return (
- 去 AI 味
+ Get rid of AI flavor
{selectedProfile ? (
- 当前按「{selectedProfile.name}」这套写法来做检测和修正。这里只处理正文的 AI 味,不会改写法字段本身。
-
+ Currently pressing "{selectedProfile.name}"This set of writing methods is used for testing and correction. This only handles the AI flavor of the text and does not rewrite the method field itself.
+
) : (
- 先从列表里选中一套写法,再来检测正文。
-
+ First select a writing method from the list, and then check the text.
+
)}
-
当前会优先参考的反 AI 约束
+
Anti-AI constraints that will currently be referenced first
- 如果这套写法绑了反 AI 规则,检测和修正会优先按这些约束去判断问题。
-
+ If this set of writing is tied to anti-AI rules, detection and correction will give priority to judging the problem according to these constraints.
+
{antiAiRuleNames.length > 0 ? (
@@ -62,41 +62,41 @@ export default function WritingFormulaCleanPanel(props: WritingFormulaCleanPanel
) : (
- 这套写法还没有绑定明确的反 AI 规则。当前检测会更依赖通用风险判断,结果可能不够贴合你的预期。
-
+ This set of writing has not yet been bound to clear anti-AI rules. The current test will rely more on general risk judgment, and the results may not be consistent with your expectations.
+
)}
-
检测正文
+
Detect text
- 粘贴你想检查的正文。建议一次给一段完整场景,这样更容易看出叙述腔、对白腔和解释腔的问题。
-
+ Paste the text you want to check. It is recommended to give a complete scene at a time, so that it is easier to see the problems of narrative, dialogue and exposition.
+
@@ -113,13 +113,13 @@ export default function WritingFormulaCleanPanel(props: WritingFormulaCleanPanel
) : (
- 检测结果会在这里显示,重点告诉你哪些句段最像 AI 腔、为什么会被判成风险,以及可以往什么方向改。
-
+ The test results will be displayed here, focusing on telling you which segments are most like AI accents, why they are judged as risks, and what direction they can be changed.
+
)}
{rewritePreview ? (
-
修正结果
+
Correction results
{rewritePreview}
diff --git a/client/src/pages/writingFormula/components/WritingFormulaCreateDialog.tsx b/client/src/pages/writingFormula/components/WritingFormulaCreateDialog.tsx
index 90603c2bc..6d2d6a358 100644
--- a/client/src/pages/writingFormula/components/WritingFormulaCreateDialog.tsx
+++ b/client/src/pages/writingFormula/components/WritingFormulaCreateDialog.tsx
@@ -21,18 +21,18 @@ import type {
const EXTRACTION_PRESET_OPTIONS = [
{
key: "imitate",
- label: "尽量贴近原文",
- summary: "尽量保留强烈指纹和表达习惯,适合短期临摹、风格试写和对照学习。",
+ label: "Try to be as close to the original text as possible",
+ summary: "Try to retain strong fingerprints and expression habits, which is suitable for short-term copying, style trial writing and comparative study.",
},
{
key: "balanced",
- label: "保留主要读感",
- summary: "保留读感和推进节奏,弱化过强指纹,适合大多数项目直接使用。",
+ label: "Keep the main sense of reading",
+ summary: "It retains the sense of reading and advancement rhythm, weakens excessive fingerprints, and is suitable for direct use in most projects.",
},
{
key: "transfer",
- label: "只迁移可复用骨架",
- summary: "更多保留可复用的节奏、结构和对白逻辑,适合长期项目和整书默认写法。",
+ label: "Only migrate reusable skeletons",
+ summary: "It retains more reusable rhythm, structure and dialogue logic, which is suitable for long-term projects and the default writing method of the entire book.",
},
] as const;
@@ -43,18 +43,18 @@ const MATERIAL_SOURCE_OPTIONS: Array<{
}> = [
{
key: "direct_text",
- label: "直接粘贴文本",
- summary: "把样本文本交给后台提取任务,适合手里已有片段或章节。",
+ label: "Paste text directly",
+ summary: "Submit the sample text to the background extraction task, which is suitable for existing fragments or chapters.",
},
{
key: "knowledge_document",
- label: "知识库原文",
- summary: "选择知识库文档活动版本,系统冻结全文快照后用代表性样本学习写法。",
+ label: "Knowledge base original text",
+ summary: "Select the active version of the knowledge base document, and the system freezes the full-text snapshot and uses representative samples to learn how to write.",
},
{
key: "book_analysis",
- label: "拆书结果",
- summary: "直接使用拆书里的文风与技法结果生成写法。",
+ label: "Results of opening the book",
+ summary: "Directly use the writing styles and techniques in the book to generate writing methods.",
},
];
@@ -66,54 +66,54 @@ const KNOWLEDGE_SOURCE_PROCESSING_OPTIONS: Array<{
}> = [
{
key: "representative_sample",
- label: "智能抽样",
- summary: "从开篇、中段、后段和收束抽取代表性样本,适合长篇原文,速度和稳定性更好。",
- badge: "推荐",
+ label: "Smart sampling",
+ summary: "Select representative samples from the beginning, middle, end and conclusion, which is suitable for long original texts and has better speed and stability.",
+ badge: "recommend",
},
{
key: "full_text",
- label: "全文提取",
- summary: "把活动版本全文作为模型输入,适合短文档;长篇可能更慢,也更容易触发模型上下文或超时限制。",
+ label: "Full text extraction",
+ summary: "Using the full text of the active version as model input is suitable for short documents; long documents may be slower and more likely to trigger model context or timeout restrictions.",
},
];
function formatTaskStatus(task: UnifiedTaskDetail | null): string {
if (!task) {
- return "暂无任务";
+ return "No tasks yet";
}
if (task.status === "queued") {
- return "排队中";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
if (task.status === "running") {
- return "处理中";
+ return "Processing";
}
if (task.status === "succeeded") {
- return "已完成";
+ return "Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.";
}
if (task.status === "failed") {
- return "失败";
+ return "fail";
}
if (task.status === "cancelled") {
- return "已取消";
+ return "Canceled";
}
- return "等待审批";
+ return "Waiting for approval";
}
function formatCharCount(value: number | null | undefined): string {
if (!value) {
- return "0 字";
+ return "0 words";
}
return `${value.toLocaleString("zh-CN")} 字`;
}
function formatKnowledgeStatus(status: KnowledgeDocumentSummary["status"]): string {
if (status === "enabled") {
- return "可用";
+ return "Available";
}
if (status === "disabled") {
- return "停用";
+ return "deactivate";
}
- return "归档";
+ return "Archive";
}
interface WritingFormulaCreateDialogProps {
@@ -194,32 +194,32 @@ export default function WritingFormulaCreateDialog(props: WritingFormulaCreateDi
|| (form.materialSource === "knowledge_document" && !knowledgeDocumentReady)
|| (form.materialSource === "book_analysis" && !bookAnalysisReady);
const materialSubmitLabel = form.materialSource === "book_analysis"
- ? "从拆书结果创建写法"
+ ? "Create a writing method from the results of splitting the book"
: form.materialSource === "knowledge_document"
- ? "从知识库原文提取并自动保存"
- : "提交提取任务并自动保存";
+ ? "Extract original text from the knowledge base and automatically save it"
+ : "Submit extraction task and save automatically";
return (
- 创建写法
+ Create writing style
- 先选最省力的起步方式。创建完成后会自动打开当前写法编辑,继续补规则、试写和绑定。
-
+ Choose the least laborious way to start first. After the creation is completed, the current writing method will be automatically opened for editing, and you can continue to fill in the rules, test writing and binding.
+
setActiveTab(value as typeof activeTab)} className="flex min-h-0 flex-1 flex-col space-y-4">
- 模板起步
- 空白 / AI
- 从素材提取
+ Template start
+ Blank/AI
+ Extract from material
- 手里还没有明确规则时,先从模板起一套最省力。后面都可以继续改。
-
+ When you don’t have clear rules in hand, it’s easiest to start with a template. You can continue to change it later.
+
{templates.map((template) => (
@@ -228,7 +228,7 @@ export default function WritingFormulaCreateDialog(props: WritingFormulaCreateDi
) : null}
onCreateFromTemplate(template.id)}
disabled={createFromTemplatePending}
>
- {createFromTemplatePending ? "创建中..." : "基于这套创建"}
+ {createFromTemplatePending ? "Creating..." : "Created based on this set"}
))}
@@ -258,20 +258,20 @@ export default function WritingFormulaCreateDialog(props: WritingFormulaCreateDi
- 你可以手动建一个空白写法,也可以先说一句想要的读感,让 AI 帮你搭第一版骨架。
-
+ You can manually create a blank writing method, or you can first tell the desired reading feeling and let AI help you build the first version of the skeleton.
+
-
手动空白创建
+
Manual blank creation
- 适合你已经知道自己要维护什么类型的写法,只想先建一个空壳。
-
+ It is suitable for you who already know what type of writing you want to maintain and just want to build an empty shell first.
+
+ Create writing styles from materials. The original text and knowledge base text will enter the background extraction task; the book splitting results will be directly analyzed to generate writing style using style and technique analysis.
+
+ No available knowledge base document found. You can upload the original text to the knowledge base first, and then come back here to create the writing method.
+
+ Writing assets will continue to retain the complete source preview; the model only reads representative samples to avoid long full text exceeding the model's carrying capacity at once.
+
) : (
- 全文提取会把活动版本全文交给模型。长篇原文建议先使用智能抽样。
-
+ Full text extraction delivers the active version of the full text to the model. For long original articles, it is recommended to use smart sampling first.
+
)}
{selectedKnowledgeDocumentLoading ? (
- "读取所选文档的活动版本..."
+ "Read the active version of the selected document..."
) : selectedKnowledgeDocument ? (
<>
{selectedKnowledgeDocument.title}
{activeKnowledgeVersion ? (
- 活动版本 v{activeKnowledgeVersion.versionNumber} · {formatCharCount(activeKnowledgeVersion.charCount)}
+ Active version v{activeKnowledgeVersion.versionNumber} · {formatCharCount(activeKnowledgeVersion.charCount)}
) : (
-
所选文档缺少活动版本,不能提交。
+
The selected document lacks an active version and cannot be submitted.
The active version text is empty and cannot be submitted.
) : null}
>
) : (
- "选择一个知识库文档后,系统会读取活动版本全文并在提交任务时冻结快照。"
+ "After selecting a knowledge base document, the system reads the full text of the active version and freezes the snapshot when the task is submitted."
)}
@@ -503,20 +503,20 @@ export default function WritingFormulaCreateDialog(props: WritingFormulaCreateDi
- 来源版本 v{analysis.documentVersionNumber} · {analysis.summary || "拆书结果可用于生成写法"}
+ Source version v{analysis.documentVersionNumber} · {analysis.summary || "The results of book splitting can be used to generate writing methods"}
) : null}
>
) : (
- "选择一个拆书结果后,系统会用文风与技法分析生成写法,不进入后台提取任务。"
+ "After selecting a book-opening result, the system will use writing style and technique analysis to generate a writing method without entering the background extraction task."
)}
>
) : (
<>
-
保留策略
+
Retention policy
- 先决定你要保留多少原始指纹,系统会按这个策略自动生成并保存写法。
-
+ First decide how many original fingerprints you want to keep. The system will automatically generate and save the writing method according to this strategy.
+
+ Will press "{selectedPreset.label}"Submit the background task. After the task is completed, the system will automatically save it. There is no need to manually click to save it as written.
+
+ After submission, the system will complete the extraction, apply the retention policy and automatically save the writing in the background. You can close the pop-up window first, and the results will be opened automatically after completion.
+
)}
>
)}
diff --git a/client/src/pages/writingFormula/components/WritingFormulaEditorPanel.tsx b/client/src/pages/writingFormula/components/WritingFormulaEditorPanel.tsx
index bc1736d6d..688cad436 100644
--- a/client/src/pages/writingFormula/components/WritingFormulaEditorPanel.tsx
+++ b/client/src/pages/writingFormula/components/WritingFormulaEditorPanel.tsx
@@ -62,33 +62,20 @@ function FieldBlock(props: {
const FEATURE_DECISION_META: Record, { label: string; className: string }> = {
keep: {
- label: "保留",
+ label: "reserve",
className: "border-emerald-200 bg-emerald-50 text-emerald-700",
},
weaken: {
- label: "弱化",
+ label: "weaken",
className: "border-amber-200 bg-amber-50 text-amber-700",
},
remove: {
- label: "剥离",
+ label: "peel off",
className: "border-rose-200 bg-rose-50 text-rose-700",
},
};
-const RULE_PATCH_SECTION_LABELS: Record = {
- narrativeRules: "剧情推进",
- characterRules: "人物表达",
- languageRules: "语言质感",
- rhythmRules: "节奏密度",
-};
-
-function formatScorePercent(value: number): string {
- return `${Math.round(value * 100)}%`;
-}
-
-function countPresetDecisions(
- preset: StyleExtractionPreset,
-): Record, number> {
+const RULE_PATCH_SECTION_LABELS: Record = { narrativeRules: "Plot progression", characterRules: "Character expression", languageRules: "Language quality", rhythmRules: "Rhythm density", }; function formatScorePercent(value: number): string { return `${Math.round(value * 100)}%`; } function countPresetDecisions( preset: StyleExtractionPreset, ): Record, number> {
return preset.decisions.reduce, number>>((result, item) => {
result[item.decision] += 1;
return result;
@@ -153,22 +140,22 @@ function RuleFieldCard(props: {
) : (
- 这块规则当前还没有可读字段。你可以先依赖上面的简介和反 AI 规则,确实需要精细兼容时再展开高级 JSON。
-
+ This rule currently has no readable fields. You can rely on the above introduction and anti-AI rules first, and then expand the advanced JSON when you really need fine-grained compatibility.
+
)}
- 查看或编辑高级 JSON
-
+ View or edit advanced JSON
+
+ The original JSON entry is retained here, mainly for compatibility with old assets or fine tuning of parameters. Under normal circumstances, just look at the readable fields above.
+
{parseError ? (
- 当前 JSON 结构无法正常识别。保存时系统会尽量回退为空对象,建议先修正格式再保存。
-
+ The current JSON structure is not recognized properly. When saving, the system will try to return it to an empty object. It is recommended to correct the format before saving.
+
) : null}
+ This is the starting method preset by the system for you. You can directly modify it according to your own project, without making a copy first and then editing it.
+
) : null}
- 如果你不想一上来就面对一堆技术字段,先维护这四块最重要:写法名称、简介、适用题材、反 AI 规则。
- 下面四组高级规则是给系统更精细的控制层,不熟悉时可以先少动。
-
+ If you don’t want to face a bunch of technical fields right off the bat, it’s important to maintain these four areas first: writing method name, introduction, applicable subject matter, and anti-AI rules.
+ The following four sets of advanced rules give the system a more refined control layer. If you are not familiar with it, you can do it less first.
+
-
基础定位
+
Basic positioning
- 先把这套写法到底想写成什么感觉讲清楚,列表页展开时也会优先展示这里的内容。
-
+ First, let’s make it clear what this writing style is like. When the list page is expanded, the content here will be displayed first.
+
+ This part is the "evidence layer" of this writing method. The writing method extracted from the original text or knowledge base will rely on this for subsequent review and re-extraction.
+ Feature descriptions, evidence, scores, preset suggestions and recommendation rules will be displayed here.
+
- 这里会列出原文里抽出来的风格特征。勾选表示继续保留到这套写法里。
- {editor.extractedFeatures.length > 0 ? ` 当前共 ${editor.extractedFeatures.length} 项。` : ""}
+ The stylistic features extracted from the original text will be listed here. Checking the box means continuing to keep this writing method.
+ {editor.extractedFeatures.length > 0 ? ` 当前共 ${editor.extractedFeatures.length} 项。` : ""}
+ Here are three sets of retention options given by the model. The selection currently saved in the writing method will be marked separately to facilitate you to judge whether you need to change the retention intensity.
+
+ These are the rules that are recommended to be bundled together during the extraction phase. Bound ones will be marked directly, and unbound ones will continue to retain their original suggested names.
+
+ This text writing method has not yet generated optional feature entries. You can click "Re-extract features" to regenerate a complete feature pool from the original text sample.
+
)}
@@ -467,14 +453,14 @@ export default function WritingFormulaEditorPanel(props: WritingFormulaEditorPan
+ This is not a copy for readers, but a supplementary explanation for yourself and the system to use when reviewing it. You can write about why this style of writing is retained and what its most important quality is.
+
+ These four blocks are the machine rule layer that will be read when the system is actually executed. When you don’t understand the meaning of a field, you can first read the title and description before deciding whether to change it.
+ If the "Overview" field is currently mainly displayed, it means that this extraction provides more summary-level rules, and there are not many fine-grained compatible fields.
+
- 兼容字段主要用于旧资产兼容和少量实验场景:{compatibilityFields}。需要稳定控制读感时,优先维护表达层摘要和反 AI 规则。
-
+ The compatibility field is mainly used for old asset compatibility and a small number of experimental scenarios:{compatibilityFields}. When stable control of reading sense is required, priority should be given to maintaining expression layer summaries and anti-AI rules.
+
onEditorChange({ narrativeRules: value })}
/>
onEditorChange({ characterRules: value })}
/>
onEditorChange({ languageRules: value })}
/>
onEditorChange({ rhythmRules: value })}
@@ -527,10 +513,10 @@ export default function WritingFormulaEditorPanel(props: WritingFormulaEditorPan
-
绑定反 AI 规则
+
Bind anti-AI rules
- 这里决定系统在检测和修正文稿时优先防什么问题。规则绑得越清楚,“去 AI 味”越有方向感。
-
+ This determines what problems the system will prioritize to prevent when detecting and correcting manuscripts. The clearer the rules are tied, the more sense of direction there will be in "removing the AI flavor".
+
+ After saving, the expansion details, AI flavor removal detection and application testing of this writing method will all read the new settings simultaneously.
+
- 保存当前写法
-
+ Error 500 (Server Error)!!1500.That’s an error.There was an error. Please try again later.That’s all we know.
+
+ You first give me a clear reference text, I split it into executable writing, and then decide whether to imitate closely, retain the skeleton, or migrate only the reusable parts.
+
+ `imitate` is more suitable for copying and trial writing, `balanced` is suitable for most projects, and `transfer` is more suitable for binding the whole book and can actively avoid high fingerprint risks.
+
>
) : (
- 先完成特征提取,这里才会出现三种保留策略和对应的读感说明。
-
+ After feature extraction is completed first, three retention strategies and corresponding reading instructions will appear here.
+
)}
@@ -114,13 +114,13 @@ export default function WritingFormulaImitateFlow(props: WritingFormulaImitateFl
-
3. 预览即将保存的写法骨架
+
3. Preview the writing skeleton to be saved
- 保存后会自动进入当前写法编辑,你可以继续调整规则、绑定到目标,或者直接拿去试写。
-
+ After saving, you will automatically enter the current writing method editor. You can continue to adjust the rules, bind it to the target, or directly use it for trial writing.
+
+ There are no features available for preview yet. Complete feature extraction first, and then decide how to save it.
+
)}
diff --git a/client/src/pages/writingFormula/components/WritingFormulaLanding.tsx b/client/src/pages/writingFormula/components/WritingFormulaLanding.tsx
index 4121f0f84..b3e0abfcb 100644
--- a/client/src/pages/writingFormula/components/WritingFormulaLanding.tsx
+++ b/client/src/pages/writingFormula/components/WritingFormulaLanding.tsx
@@ -118,7 +118,7 @@ export default function WritingFormulaLanding(props: WritingFormulaLandingProps)
- {truncateText(profile.summaryLine, 120) || "暂无写法摘要。"}
+ {truncateText(profile.summaryLine, 120) || "There is no written summary yet."}
- 这套写法在提取阶段额外建议了 {profile.extractionAntiAiRecommendationCount} 条反 AI 规则,适合后续继续精配。
-
+ This writing method is additionally recommended during the extraction stage. {profile.extractionAntiAiRecommendationCount} It violates AI rules and is suitable for further refinement.
+
) : null}
) : (
- 这套写法还没有绑定明确的反 AI 约束,所以“去 AI 味”时可读性会偏弱。
-
+ This writing method has not been bound to clear anti-AI constraints, so the readability will be weak when "removing the AI flavor".
+
Editorial Settings: Maintain instructions, rules, and anti-AI constraints for the writing style itself.
+
Application and testing: Bind it to a novel or chapter, and do a trial writing verification.
+
Remove the AI flavor: only process text detection and correction, without rewriting method fields.
@@ -316,37 +316,37 @@ export default function WritingFormulaLanding(props: WritingFormulaLandingProps)
- 我的写法资产
-
+ My writing assets
+
- 先选一套写法,再决定要编辑、应用还是去 AI 味。
-
+ First choose a writing method, and then decide whether to edit, apply or remove the AI flavor.
+
- 首页负责看清你已有的写法资产。展开后会直接展示这套写法的读感定位、规则摘要、反 AI 约束和当前成熟度。
-
+ The home page is responsible for seeing your existing writing assets. After expansion, the reading positioning, rule summary, anti-AI constraints and current maturity of this writing method will be directly displayed.
+
- 新建一套写法
-
+ Create a new writing style
+
- 书级默认写法请从小说基础信息进入,由小说来选择要使用的写法资产,再带入后续导演和正文流程。
-
+ Please enter the book-level default writing method from the basic information of the novel, and let the novel select the writing method assets to be used, and then bring them into the subsequent director and text process.
+
{profileItems.length === 0 ? (
-
当前还没有写法资产
+
There are currently no writing assets
- 先创建第一套写法,后面再回来慢慢补规则、做试写和绑定目标。
-
+ Create the first set of writing methods first, and then come back later to slowly add rules, test writing, and bind goals.
+
- 去创建第一套写法
-
+ To create the first set of writing methods
+
+ These are the reusable assets you have accumulated and should be picked here first.
+
- {customProfiles.length} 套
-
+ {customProfiles.length} set
+
{customProfiles.map(renderProfileCard)}
@@ -374,14 +374,14 @@ export default function WritingFormulaLanding(props: WritingFormulaLandingProps)
-
可直接改的起步写法
+
Start writing method that can be changed directly
- 这些预置资产适合先借一套骨架,再按当前项目改成自己的写法。
-
+ These preset assets are suitable for borrowing a set of skeletons first, and then changing them into your own writing style according to the current project.
+
- {starterProfiles.length} 套
-
+ {starterProfiles.length} set
+
{starterProfiles.map(renderProfileCard)}
diff --git a/client/src/pages/writingFormula/components/WritingFormulaRulesPanel.tsx b/client/src/pages/writingFormula/components/WritingFormulaRulesPanel.tsx
index 38679133b..8544623f9 100644
--- a/client/src/pages/writingFormula/components/WritingFormulaRulesPanel.tsx
+++ b/client/src/pages/writingFormula/components/WritingFormulaRulesPanel.tsx
@@ -23,18 +23,18 @@ export default function WritingFormulaRulesPanel(props: WritingFormulaRulesPanel
- 反 AI 规则
-
+ Anti-AI rules
+
+ View, create and adjust anti-AI rules in the Rule Center; the writing editing area continues to be responsible for selecting which rules are bound to the current writing.
+
- 进入规则中心
+ Enter the rule center
diff --git a/client/src/pages/writingFormula/components/WritingFormulaWorkbenchPanel.tsx b/client/src/pages/writingFormula/components/WritingFormulaWorkbenchPanel.tsx
index c3aabc04a..364ac13a8 100644
--- a/client/src/pages/writingFormula/components/WritingFormulaWorkbenchPanel.tsx
+++ b/client/src/pages/writingFormula/components/WritingFormulaWorkbenchPanel.tsx
@@ -57,38 +57,38 @@ export default function WritingFormulaWorkbenchPanel(props: WritingFormulaWorkbe
return (
- 当前写法的应用与测试
+ Application and testing of current writing methods
- 这里只处理两件事:把这套写法绑定到小说/章节/任务,以及先试写一段看看效果。
- “去 AI 味”已经拆成独立入口,不再和这里混在一起。
-
+ There are only two things to deal with here: binding this writing method to the novel/chapter/task, and trying to write a paragraph to see the effect.
+ "Remove AI flavor" has been separated into a separate entrance and is no longer mixed with this place.
+
+ After binding, this writing method will be generated in the corresponding novel, chapter or task. The higher the priority, the higher the influence; the higher the weight, the stronger the degree of participation.
+
-
绑定层级
+
Binding level
onBindingFormChange({ targetType: event.target.value as StyleBinding["targetType"] })}
>
-
+ When you are not sure whether this writing method has been successfully implemented, creating or rewriting a paragraph is the most intuitive way to verify it.
+
-
试写方式
+
Trial writing method
onTestWriteFormChange({ mode: event.target.value as "generate" | "rewrite" })}
>
-