narraeon 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +11 -0
  2. package/README.zh-CN.md +8 -0
  3. package/dist/node/protocol/v1.js +0 -2
  4. package/dist/node/runtime/FileNativeExclusiveLink.js +39 -0
  5. package/dist/node/runtime/V1Runtime.js +1 -5
  6. package/dist/node/runtime/archive/WorldArchives.js +14 -46
  7. package/dist/node/runtime/artifact/ArtifactStoreFiles.js +330 -0
  8. package/dist/node/runtime/artifact/FileNativeArtifactStore.js +495 -1535
  9. package/dist/node/runtime/artifact/LegacyArtifactStoreMigration.js +260 -0
  10. package/dist/node/runtime/config/AppPreferencesStore.js +0 -1
  11. package/dist/node/runtime/config/ProtagonistTemplateStore.js +0 -1
  12. package/dist/node/runtime/config/dataRootLock.js +108 -0
  13. package/dist/node/runtime/content/ContentWorkspace.js +0 -10
  14. package/dist/node/runtime/content/CurrentTreeContentLibrary.js +7 -44
  15. package/dist/node/runtime/extension/PackageScriptPermissions.js +0 -8
  16. package/dist/node/runtime/extension/WorldExtensionControls.js +0 -8
  17. package/dist/node/runtime/model/ModelConnectionStore.js +0 -1
  18. package/dist/node/runtime/play/FileNativeContinuityCorrection.js +3 -14
  19. package/dist/node/runtime/play/FileNativePlayAdvanceStore.js +32 -207
  20. package/dist/node/runtime/play/FileNativePlayTimelineStore.js +29 -239
  21. package/dist/node/runtime/play/PlayCallChain.js +338 -366
  22. package/dist/node/runtime/play/PlayDocumentTools.js +8 -125
  23. package/dist/node/runtime/play/PlayFollowupRequests.js +2 -2
  24. package/dist/node/runtime/play/PlayTraceRetention.js +8 -36
  25. package/dist/node/runtime/setting/FileNativeSettingImprovementStore.js +3 -15
  26. package/dist/node/runtime/world/FileNativeWorldStorageMigrator.js +213 -1033
  27. package/dist/node/runtime/world/FileNativeWorldStore.js +230 -781
  28. package/dist/node/runtime/world/{FileNativeAuthorityV3.js → LegacyAuthorityV3Reader.js} +13 -404
  29. package/dist/node/runtime/world/WorldActivity.js +14 -22
  30. package/dist/node/runtime/world/WorldDocumentMaintenance.js +2 -2
  31. package/dist/node/runtime/world/WorldLedgerAuthority.js +340 -0
  32. package/dist/node/runtime/world/WorldOperationCoordinator.js +59 -401
  33. package/dist/node/runtime/world/WorldQueue.js +46 -0
  34. package/dist/node/runtime/world/WorldTurnLedger.js +353 -0
  35. package/dist/node/runtime/world-revision/FileNativeWorldRevisionStore.js +0 -12
  36. package/dist/node/server/mobile.js +20 -0
  37. package/dist/node/server/startWebServer.js +15 -8
  38. package/dist/web/assets/{index-BgBL8G-A.js → index-7GzLSUpU.js} +2 -2
  39. package/dist/web/index.html +1 -1
  40. package/package.json +2 -1
  41. package/dist/node/runtime/FileNativePhysicalClone.js +0 -86
@@ -15,7 +15,7 @@ Error generating stack: `+e.message+`
15
15
 
16
16
  Protagonist template:
17
17
 
18
- {body}`,导出世界备份:`Export world backup`,导出备份:`Export backup`,"正在导出世界备份…":`Exporting world backup…`,"正在导出…":`Exporting…`,"世界备份已开始下载。":`Your world backup download has started.`,"备份包含完整故事、世界文档和模型调用记录,不包含 API Key 和模型配置。":`Backups include the complete story, world documents, and model call records. API keys and model configuration are excluded.`,"世界备份超过容量限制。":`The world backup exceeds the size limits.`,"正在上传世界备份:{progress}%":`Uploading world backup: {progress}%`,"正在校验并导入世界备份…":`Validating and importing world backup…`,"世界备份已导入,新世界已加入列表。":`World backup imported. The new world is now in your list.`,"世界备份 ZIP 文件":`World backup ZIP file`,"此上下文来自备份;修改后须以本机选择开启全新上下文。":`This context came from a backup. After editing, start a fresh context with your local choices.`,导入世界备份:`Import world backup`,"世界备份上传失败,请重试。":`World backup upload failed. Please try again.`,"这不是有效的世界备份,请选择世界备份 ZIP。":`This is not a valid world backup. Select a world backup ZIP.`,"此世界备份格式不受支持,请升级应用后重试。":`This world backup format is unsupported. Update the app and try again.`,"世界备份包含不安全或冲突的文件路径。":`The world backup contains unsafe or conflicting file paths.`,"世界备份完整性校验失败,未创建世界。":`World backup integrity verification failed. No world was created.`,"请先等待生成结束,并应用或放弃世界修订,再导出备份。":`Wait for generation to finish and apply or discard any world revision before exporting a backup.`,"请先修复世界,再导出备份。":`Repair the world before exporting a backup.`,"世界从备份导入;下一次行动会从新上下文开始":`World imported from backup; your next action will start a fresh context`,保存说明:`About saving`,重命名文件:`Rename file`,"生成预览后,在这里查看实际发送的消息、材料与工具。":`Generate a preview to inspect the messages, materials, and tools sent to the model.`,关闭提示:`Dismiss notification`,"找不到该页面。":`Page not found.`,"找不到该世界。":`World not found.`,"找不到该内容包。":`Content package not found.`,"找不到该预设。":`Preset not found.`,"已保留当前页面的未保存修改,请保存或放弃后再切换。":`Your unsaved changes have been kept. Save or discard them before switching pages.`,"这份内容包正在完善,请在回复完成后创建世界。":`This content package is being developed. Wait for the reply to finish before creating a world.`,放弃控制修改:`Discard control changes`,选择编辑文件:`Choose a file to edit`,当前文件:`Current file`,工作区导航:`Workspace navigation`,切换页面:`Switch page`,内容包读取失败:`Could not load content package`,"选择一份内容包,从它的开场开始。":`Choose a content package and begin with its opening.`,"先新建或导入一份内容包。":`Create or import a content package first.`,创建世界的内容包:`Content package for the new world`,"内容包需要修复后才能创建世界。":`Repair this content package before creating a world.`,配置模型连接:`Configure model connection`,"正在创建世界…":`Creating world…`,"新世界独立保存,之后的游玩不会修改源内容包。":`The new world is saved independently. Playing will not change the source package.`,开场白预览:`Opening preview`,"这份内容包还没有开场白。":`This content package has no opening yet.`,"正在读取开场白…":`Loading opening…`,"选择一个世界继续故事,或从内容包开始创作。":`Continue a story in an existing world, or start creating from a content package.`,"世界操作:{title}":`World actions: {title}`,刚刚:`Just now`,活动时间未记录:`Activity time not recorded`,"1 回合":`1 turn`,"{count} 回合":`{count} turns`,"先新建或导入一份内容包,再完善开场与世界设定。":`Create or import a content package, then develop its opening and world setting.`,"导入内容包 ZIP":`Import content package ZIP`,"世界控制 · 高级编辑":`World control · Advanced editor`,当前配置与编译边界:`Current configuration and compilation boundaries`,参数说明:`Parameter details`,创建世界:`Create world`,完善这份内容包:`Develop this content package`,返回游玩:`Return to play`,预设管理:`Manage presets`,预设检查通过:`Preset validated`,"预设已导出。":`Preset exported.`,"JavaScript 已关闭;文本、HTML 和样式仍可预览。":`JavaScript disabled. Text, HTML and styles can still be previewed.`,"已复制为独立预设。":`Copied as an independent preset.`,"已复制推荐{name},可独立编辑。":`Copied the recommended {name} for independent editing.`,预设编辑器:`Preset editor`,"已加入修复草稿,保存时将检查预设格式。":`Added to the repair draft. The preset format will be checked when saving.`,"跨世界使用的主持规则;可在“游玩”左侧目录调整启用状态和顺序。":`Narrator rules shared across worlds. Change their order and enabled state in the Play directory.`,"主叙事或后置请求使用的提示词;在“游玩”左侧选择对应条目即可编辑。":`Prompts for the main narration or follow-up requests. Select the corresponding item in the Play directory to edit it.`,"预设已保存。":`Preset saved.`,"已应用此预设;游玩修改将在下一次正常发送时生效。":`Preset applied. Play changes take effect on the next normal send.`,导入预设:`Import preset`,选择要导入的预设:`Choose a preset to import`,"无法识别此预设。请选择通过“导出预设”生成的文件。":`This preset could not be recognized. Choose a file created with Export preset.`,导出预设:`Export preset`,"预设已导入为独立副本,JavaScript 默认关闭;信任其内容后可在预设操作中启用。":`Preset imported as an independent copy with JavaScript disabled. Enable it in preset actions after reviewing and trusting its contents.`,"发送给 AI 的内容预览":`Preview content sent to AI`,"请先保存当前修改;这里预览的是已保存且校验通过的预设,不包含未保存修改。":`Save your changes first. This preview uses the saved, validated preset and excludes unsaved changes.`,当前使用的预设:`Current preset`,"在同一处编辑设定完善方法、主持规则、后置请求与界面展示。":`Edit authoring methods, narrator rules, follow-up requests and displays in one place.`,"预设原文与表单均有修改,请先保留一种编辑结果并保存,再预览。":`Both the preset source and forms have changed. Keep one set of edits and save before previewing.`,"预设原文与表单均有修改,请先保留一种编辑结果再保存,以免覆盖当前修改。":`Both the preset source and forms have changed. Keep one set of edits before saving to avoid overwriting your changes.`,"这里显示此产物的数据要求。需要修改时,请在当前内容包或世界修订的文件编辑中修改 control/followups.yaml 的对应产物声明。":`These are the data requirements for this output. To change them, edit the corresponding output declaration in control/followups.yaml in the current content package or world revision file editor.`,模板版本标记:`Template version marker`,新增修复文件:`Add a repair file`,已关闭:`Disabled`,系统示例:`System example`,世界扩展:`World extensions`,"界面显示(不调用模型)":`Interface display (no model requests)`,"选择仅用于当前世界。后置请求重新开启后,从下一次正常发送生效;界面显示直接读取当前字段。":`Choices apply only to this world. Re-enabled requests take effect on the next normal send; interface displays read current fields immediately.`,随组暂停:`Paused by group`,"后置声明无法编辑,请在文件编辑中修复后重试。":`The followup declaration cannot be edited. Repair it in the file editor and try again.`,"主剧情完成后按此顺序生成额外内容。关闭保留提示和资源;创建世界后独立保存,后续修改需通过世界修订应用。":`Generate extra content in this order after the main story. Disabling keeps prompts and resources. Created worlds keep independent copies; later changes require applying a world revision.`,"根据已完成的剧情,通过 artifact_emit 输出 output_1。":`Use artifact_emit to emit output_1 based on the settled story.`,故事正文:`Story content`,侧栏:`Sidebar`,删除请求:`Delete request`,新增包后置请求:`Add package followup`,包请求名称:`Package request name`,包请求提示词:`Package request prompt`,包请求显示位置:`Package request display location`,稳定请求身份:`Stable request identity`,产物字节上限:`Artifact byte limit`,内容包脚本权限:`Package script permissions`,"仅授权此处已保存的资源代码。导入不会授权,资源修改后需要重新授权;关闭立即停止此来源脚本,HTML 和样式仍可显示。":`Authorize only the saved resource code here. Imports do not grant permission, and changed resources require authorization again. Revoking permission stops scripts from this source; HTML and styles remain visible.`,允许运行内容包脚本:`Allow package scripts`,撤销全部包脚本授权:`Revoke all package script permissions`,"请先保存修改,再授权资源代码。":`Save your changes before authorizing resource code.`,"JavaScript 仅在 app 模式且已允许脚本时运行。":`JavaScript runs only in app mode with scripts enabled.`,"HTML 模板":`HTML template`,编辑渲染资源:`Edit rendering resources`,"在此位置展开当前世界的内容包后置请求。无声明时为空组,不调用模型;正文由内容包管理。":`The current world’s package followups expand here. An undeclared group is empty and makes no model calls; the package owns its content.`,克隆后置请求:`Clone followup`,"界面产物:场景回顾,Markdown,剧情内容区;保存到提交,替换上一份,显式清除。":`Artifact: scene recap, Markdown, story area; saved with the commit, replacing the previous result until explicitly cleared.`,系统后置提示词:`System followup prompt`,"系统示例只读;新运行使用应用最新版,克隆后独立保存。":`This system example is read-only. New runs use the current application example; clones are saved independently.`,内容包后置请求:`Content-package followups`,最近一次请求材料:`Latest request materials`,"下一次发送将采用当前预设和世界材料;原生对话保留。":`The next send uses the current preset and world materials while preserving the native conversation.`,"每次正常发送重新编译,工具往返使用本轮快照":`Each normal send recompiles; tool exchanges use the same run snapshot.`,下一次发送候选:`Next send candidate`,"分别展示最近一次请求、累计当时读取和下一次候选;旧读取不代表当前字段值。":`Inspect the latest request, accumulated historical reads, and next candidate separately. Past reads do not prove current field values.`,本次请求快照的真实覆盖:`Actual coverage of this request snapshot`,"查看下一次发送候选的完整 Prompt Preview":`Inspect the complete next-send Prompt Preview`,"游玩在下一次正常发送生效,原样重试保留旧请求":`Play changes take effect on the next normal send; exact retries retain the saved request.`,"· 游玩修改在下一次正常发送生效":`· Play changes take effect on the next normal send`,"保留原生对话,重新编译当前提示和材料":`Preserve the native conversation and recompile current prompts and materials`,"累计实际读取(当时内容)":`Accumulated reads (historical content)`,"累计读取的当时内容,不代表当前字段值":`Historical content read at the time, not current field values`,查看各次请求保存的提示材料:`Inspect saved prompt materials for each request`,"原样重试使用已保存请求,不采用新预设。":`Exact retry uses the saved request and does not apply a new preset.`,"正常发送和空输入续写使用最新预设;本轮工具执行期间保持不变。":`Normal sends and empty continuations use the latest preset; it stays fixed during this tool loop.`,"玩家视图格式无效,请检查文本处理规则。":`Invalid player-view data. Check the text processing rules.`,"玩家视图、布局、资源与无模型预览":`Player views, layout, resources and model-free preview`,"直接用已保存的玩家视图配置界面,不需要后置请求。模型产物的位置设置独立保留。":`Configure displays directly from saved player views. Model-generated outputs have separate placement settings.`,"当前没有模型产物;玩家视图面板仍可独立使用。":`There are no model-generated outputs. Player-view panels work independently.`,"同一玩家视图由自定义面板接管,其他视图仍显示默认卡片。":`Custom panels replace the default card for their player view. Other views keep their default cards.`,纯界面预览:`Interface preview`,"使用当前编辑内容与所选世界的已保存玩家视图,不调用模型,也不保存或修改世界。修改后请重新预览。":`Preview the current edits with saved player views from the selected world. This makes no model calls and does not save or change the world. Preview again after editing.`,预览世界:`Preview world`,选择已有世界:`Select an existing world`,预览界面:`Preview interface`,"正在读取玩家视图…":`Loading player views…`,"请先解决高级文件与结构化编辑冲突,再预览。":`Resolve conflicts between file and structured edits before previewing.`,渲染资源:`Rendering resources`,"编辑资源 {name}":`Edit resource {name}`,"资源内容 {name}":`Resource content {name}`,"对话连接已断开,正在重新连接…":`Conversation disconnected. Reconnecting…`,"对话同步失败,请重新打开此页面。":`Conversation synchronization failed. Reopen this page to reconnect.`,"正在读取工作区…":`Loading workspace…`,工作区读取失败:`Failed to load the workspace`,"内容包当前树已整批保存。":`The content package tree has been saved.`,"内容包已重命名。":`The content package has been renamed.`,"内容包 ZIP 自身大小超过安全上限。":`The content package ZIP exceeds the safety limit.`,"ZIP 内容包已导入为新的本地身份。":`The ZIP content package was imported with a new local identity.`,"请先保存手动编辑,再开始 AI 设定完善。":`Save manual edits before starting setting improvement.`,"请先保存并启用一份模型配置。":`Save and enable a model connection first.`,"世界已重命名为“{name}”。":`World renamed to “{name}”.`,"删除世界“{title}”?它的全部提交、历史和存档都会从本机移除,且无法撤销。":`Delete world “{title}”? All of its commits, history, and saved data will be removed from this device. This cannot be undone.`,"世界已从本机删除。":`The world has been deleted from this device.`,操作失败:`Operation failed`,未命名世界:`Untitled world`,模型连接:`Model connection`,完成原因:`Provider completion reason`,原生续传载荷:`Provider-native continuation payload`,不可用:`Unavailable`,尚未配置:`Not configured`,内容包:`Content package`,内容包编辑方式:`Content package editing mode`,"已放弃未保存修改;内容包当前树未改变。":`Unsaved edits were discarded; the current content package tree is unchanged.`,"Narraeon · 本地优先":`Narraeon · Local first`,世界工作区:`World workspace`,"创作内容包,连接 AI 主持,让每个世界独立演化。":`Create content packages, connect a model host, and let every world evolve independently.`,返回工作区:`Back to workspace`,手动编辑:`Edit manually`,"AI 完善":`Setting improvement`,新建世界:`Create world`,从当前内容包创建:`Create from current content package`,界面语言:`Interface language`,"界面语言已保存。":`Interface language saved.`,简体中文:`Simplified Chinese`,"扩展 iframe 加载失败":`Extension iframe failed to load`,"下一次真实 Prompt Preview":`Next real Prompt Preview`,"每个后置请求在主调用链完成后单独派发一次,共用同一段冻结前缀,彼此看不见对方,也不会进入之后的模型上下文。请求提示、产物格式和显示位置都可在当前页面编辑。":`Each follow-up is dispatched once after the main call chain. They share the same frozen prefix, cannot see one another, and never enter later model context. Edit each request prompt, artifact format, and display location here.`,"上次模型请求没有完整返回。保持输入框为空并点击“追加上下文”,就会原样发送已保存的请求;不会追加玩家指令,中断片段也不会进入模型上下文。":`The previous model request did not return completely. Leave the input empty and append context to resend the saved request unchanged. No player instruction is appended, and the interrupted fragment does not enter model context.`,产物调试器:`Artifact debugger`,扩展状态:`Extension status`,核心已提交:`Core committed`,核心未提交:`Core not committed`,"已完成请求:":`Completed requests:`,无:`None`,玩家视图面板诊断:`Player-view panel diagnostics`,玩家视图面板:`Player-view panel`,"已提交玩家视图投影 · head:":`Committed player-view projection · head:`,"bridge 事件(":`Bridge events (`,来源:`Source`,世界端点:`World endpoint`,未提交:`Uncommitted`,"保存/投影":`Retention / projection`,"renderer 状态":`Renderer status`,最终内容:`Final content`,"regex steps(structured_payload 以规范化 JSON 文本为输入)":`Regex steps (structured_payload uses normalized JSON text as input)`,"artifact 内容超过宿主渲染上限":`Artifact content exceeds the host rendering limit`,正则规则数量超过宿主上限:`The number of regex rules exceeds the host limit`,"markdown_html 规则只作用于 Markdown 转换后的 HTML":`markdown_html rules apply only to HTML produced from Markdown`,正则转换次数超过宿主上限:`The number of regex transformations exceeds the host limit`,正则输出超过宿主字节上限:`Regex output exceeds the host byte limit`,"Markdown HTML DOM 大小超过宿主上限":`The Markdown HTML DOM exceeds the host size limit`,正则执行失败:`Regex execution failed`,"正则规则失败,实例显示 raw artifact(可恢复)":`A regex rule failed; the instance is showing the raw artifact and can recover`,"正则规则失败,实例进入错误状态并显示 raw artifact":`A regex rule failed; the instance entered an error state and is showing the raw artifact`,"document renderer 必须包含唯一 narraeon:content marker":`A document renderer must contain exactly one narraeon:content marker`,"旧产物没有可证明的回复归属,仅保留诊断,无法自动迁移为正文附件。":`Legacy artifacts have no proven reply attachment. Diagnostics remain available; automatic migration to story attachments is unavailable.`,生成失败:`Generation failed`,已更新:`Updated`,更新中:`Updating`,生成中:`Generating`,生成未完成:`Generation incomplete`,"ArtifactExtensionMount 必须位于 ArtifactExtensionHost 内":`ArtifactExtensionMount must be inside ArtifactExtensionHost`,"文档 renderer 无效":`Invalid document renderer`,扩展报告了未知诊断:`The extension reported an unknown diagnostic`,"当前调用链正在提交,暂不能修改草稿":`The current call chain is committing; its draft cannot be changed yet`,"composer.set_draft.text 必须是字符串":`composer.set_draft.text must be a string`,"扩展资源不可用,显示 raw artifact。":`Extension resources are unavailable; showing the raw artifact.`,恢复此扩展:`Restore this extension`,停用此扩展:`Disable this extension`,本地可信代码:`Trusted local code`,扩展展帧加载失败:`Extension iframe failed to load`,产物诊断:`Artifact diagnostics`,内容包当前树:`Current content-package tree`,"逐份编辑 YAML/Markdown;整批保存时才原子替换已保存版本。":`Edit YAML and Markdown files individually; the saved version is replaced atomically only when the whole draft is saved.`,内容包编辑状态:`Content-package editing status`,"已保存版本:":`Saved version:`,可用:`Usable`,需要修复:`Needs repair`,有未保存修改:`Unsaved changes`,草稿与已保存版本一致:`Draft matches the saved version`,内容包文件统计:`Content-package file counts`,全部文件:`All files`,开场白:`Opening text`,世界内容:`World content`,控制文件:`Control files`,内容包文件:`Content-package files`,当前草稿:`Current draft`,文件:`Files`,筛选文件:`Filter files`,"人物、地点或文件名":`Character, location, or file name`,内容包文件树:`Content-package file tree`,"没有匹配的文件。":`No files match.`,新建文件:`Create file`,新文件路径:`New file path`,"新文件先加入本地草稿;Runtime 会在整批保存时检查路径与内容。":`A new file is added to the local draft first. Runtime validates its path and content when the whole draft is saved.`,加入草稿:`Add to draft`,文件编辑器:`File editor`,当前草稿没有文件:`The current draft has no files`,"从左侧新建第一份文件,或返回工作区导入已有内容包。":`Create the first file from the left, or return to the workspace and import an existing content package.`,从草稿移除:`Remove from draft`,文件路径:`File path`,应用新路径:`Apply new path`,二进制资源不在文本编辑器展开:`Binary resources are not expanded in the text editor`,"当前 Base64 内容会原样保留;如需替换资源,请重新导入内容包。":`The current Base64 content is preserved exactly. Reimport the content package to replace the resource.`,文件内容:`File content`,"Ctrl / ⌘ + S 整批保存":`Ctrl / ⌘ + S saves the whole draft`,行:`Lines`,字符:`Characters`,已保存版本诊断:`Saved-version diagnostics`,已保存版本有:`The saved version has`,"已保存版本有 {count} 项需要修复":`The saved version has {count} issue(s) to repair`,项需要修复:`issue(s) to repair`,"诊断对应上一次保存;整批保存后会按新草稿重新检查。":`Diagnostics describe the last save. Saving the whole draft checks the new draft again.`,草稿尚未保存:`Draft not saved`,当前树已保存:`Current tree saved`,"保存会把整棵草稿作为一个候选原子替换,不会逐文件提交。":`Saving atomically replaces the entire tree with one candidate; files are not committed one at a time.`,整批保存:`Save all changes`,放弃未保存修改:`Discard unsaved changes`,内容包操作:`Content-package actions`,"请先整批保存或放弃当前草稿,再操作已保存内容包。":`Save or discard the current draft before acting on the saved content package.`,内容包标题:`Content-package title`,导入的内容包:`Imported content package`,重命名:`Rename`,复制为新本地身份:`Copy as a new local identity`,"导出 ZIP":`Export ZIP`,删除内容包:`Delete content package`,"请输入文件路径。":`Enter a file path.`,"路径必须是使用 / 的内容包相对路径。":`The path must be relative to the content package and use / separators.`,"路径不能包含空目录、. 或 ..。":`The path cannot contain empty directories, . or ...`,"当前文件原生 V1 不接受 manifest 或 JSON 内容文件。":`File-native V1 does not accept a manifest or JSON content files.`,"当前草稿中已经存在同一路径。":`The current draft already contains that path.`,二进制资源:`Binary resource`,玩家首次行动前看到的开场白:`Opening text shown before the player's first action`,会复制为世界状态的设定文档:`Setting document copied into world state`,会复制为世界控制的作者资产:`Author asset copied into world control`,内容包文本资源:`Content-package text resource`,"写下玩家首次行动前立即看到的局面……":`Describe the situation visible immediately before the player's first action…`,"$document:\n id: character.example\n ref: example\n title: 示例人物\n summary: 一句话稳定简介。\n aliases: []\n":`$document:
18
+ {body}`,导出世界备份:`Export world backup`,导出备份:`Export backup`,"正在导出世界备份…":`Exporting world backup…`,"正在导出…":`Exporting…`,"世界备份已开始下载。":`Your world backup download has started.`,"备份包含完整故事、世界文档和模型调用记录,不包含 API Key 和模型配置。":`Backups include the complete story, world documents, and model call records. API keys and model configuration are excluded.`,"世界备份超过容量限制。":`The world backup exceeds the size limits.`,"正在上传世界备份:{progress}%":`Uploading world backup: {progress}%`,"正在校验并导入世界备份…":`Validating and importing world backup…`,"世界备份已导入,新世界已加入列表。":`World backup imported. The new world is now in your list.`,"世界备份 ZIP 文件":`World backup ZIP file`,"此上下文来自备份;修改后须以本机选择开启全新上下文。":`This context came from a backup. After editing, start a fresh context with your local choices.`,导入世界备份:`Import world backup`,"世界备份上传失败,请重试。":`World backup upload failed. Please try again.`,"这不是有效的世界备份,请选择世界备份 ZIP。":`This is not a valid world backup. Select a world backup ZIP.`,"此世界备份格式不受支持,请升级应用后重试。":`This world backup format is unsupported. Update the app and try again.`,"世界备份包含不安全或冲突的文件路径。":`The world backup contains unsafe or conflicting file paths.`,"世界备份完整性校验失败,未创建世界。":`World backup integrity verification failed. No world was created.`,"请先等待生成结束,并应用或放弃世界修订,再导出备份。":`Wait for generation to finish and apply or discard any world revision before exporting a backup.`,"请先修复世界,再导出备份。":`Repair the world before exporting a backup.`,"世界从备份导入;下一次行动会从新上下文开始":`World imported from backup; your next action will start a fresh context`,保存说明:`About saving`,重命名文件:`Rename file`,"生成预览后,在这里查看实际发送的消息、材料与工具。":`Generate a preview to inspect the messages, materials, and tools sent to the model.`,关闭提示:`Dismiss notification`,"找不到该页面。":`Page not found.`,"找不到该世界。":`World not found.`,"找不到该内容包。":`Content package not found.`,"找不到该预设。":`Preset not found.`,"已保留当前页面的未保存修改,请保存或放弃后再切换。":`Your unsaved changes have been kept. Save or discard them before switching pages.`,"这份内容包正在完善,请在回复完成后创建世界。":`This content package is being developed. Wait for the reply to finish before creating a world.`,放弃控制修改:`Discard control changes`,选择编辑文件:`Choose a file to edit`,当前文件:`Current file`,工作区导航:`Workspace navigation`,切换页面:`Switch page`,内容包读取失败:`Could not load content package`,"选择一份内容包,从它的开场开始。":`Choose a content package and begin with its opening.`,"先新建或导入一份内容包。":`Create or import a content package first.`,创建世界的内容包:`Content package for the new world`,"内容包需要修复后才能创建世界。":`Repair this content package before creating a world.`,配置模型连接:`Configure model connection`,"正在创建世界…":`Creating world…`,"新世界独立保存,之后的游玩不会修改源内容包。":`The new world is saved independently. Playing will not change the source package.`,开场白预览:`Opening preview`,"这份内容包还没有开场白。":`This content package has no opening yet.`,"正在读取开场白…":`Loading opening…`,"选择一个世界继续故事,或从内容包开始创作。":`Continue a story in an existing world, or start creating from a content package.`,"世界操作:{title}":`World actions: {title}`,刚刚:`Just now`,活动时间未记录:`Activity time not recorded`,待升级:`Upgrade pending`,"1 回合":`1 turn`,"{count} 回合":`{count} turns`,"先新建或导入一份内容包,再完善开场与世界设定。":`Create or import a content package, then develop its opening and world setting.`,"导入内容包 ZIP":`Import content package ZIP`,"世界控制 · 高级编辑":`World control · Advanced editor`,当前配置与编译边界:`Current configuration and compilation boundaries`,参数说明:`Parameter details`,创建世界:`Create world`,完善这份内容包:`Develop this content package`,返回游玩:`Return to play`,预设管理:`Manage presets`,预设检查通过:`Preset validated`,"预设已导出。":`Preset exported.`,"JavaScript 已关闭;文本、HTML 和样式仍可预览。":`JavaScript disabled. Text, HTML and styles can still be previewed.`,"已复制为独立预设。":`Copied as an independent preset.`,"已复制推荐{name},可独立编辑。":`Copied the recommended {name} for independent editing.`,预设编辑器:`Preset editor`,"已加入修复草稿,保存时将检查预设格式。":`Added to the repair draft. The preset format will be checked when saving.`,"跨世界使用的主持规则;可在“游玩”左侧目录调整启用状态和顺序。":`Narrator rules shared across worlds. Change their order and enabled state in the Play directory.`,"主叙事或后置请求使用的提示词;在“游玩”左侧选择对应条目即可编辑。":`Prompts for the main narration or follow-up requests. Select the corresponding item in the Play directory to edit it.`,"预设已保存。":`Preset saved.`,"已应用此预设;游玩修改将在下一次正常发送时生效。":`Preset applied. Play changes take effect on the next normal send.`,导入预设:`Import preset`,选择要导入的预设:`Choose a preset to import`,"无法识别此预设。请选择通过“导出预设”生成的文件。":`This preset could not be recognized. Choose a file created with Export preset.`,导出预设:`Export preset`,"预设已导入为独立副本,JavaScript 默认关闭;信任其内容后可在预设操作中启用。":`Preset imported as an independent copy with JavaScript disabled. Enable it in preset actions after reviewing and trusting its contents.`,"发送给 AI 的内容预览":`Preview content sent to AI`,"请先保存当前修改;这里预览的是已保存且校验通过的预设,不包含未保存修改。":`Save your changes first. This preview uses the saved, validated preset and excludes unsaved changes.`,当前使用的预设:`Current preset`,"在同一处编辑设定完善方法、主持规则、后置请求与界面展示。":`Edit authoring methods, narrator rules, follow-up requests and displays in one place.`,"预设原文与表单均有修改,请先保留一种编辑结果并保存,再预览。":`Both the preset source and forms have changed. Keep one set of edits and save before previewing.`,"预设原文与表单均有修改,请先保留一种编辑结果再保存,以免覆盖当前修改。":`Both the preset source and forms have changed. Keep one set of edits before saving to avoid overwriting your changes.`,"这里显示此产物的数据要求。需要修改时,请在当前内容包或世界修订的文件编辑中修改 control/followups.yaml 的对应产物声明。":`These are the data requirements for this output. To change them, edit the corresponding output declaration in control/followups.yaml in the current content package or world revision file editor.`,模板版本标记:`Template version marker`,新增修复文件:`Add a repair file`,已关闭:`Disabled`,系统示例:`System example`,世界扩展:`World extensions`,"界面显示(不调用模型)":`Interface display (no model requests)`,"选择仅用于当前世界。后置请求重新开启后,从下一次正常发送生效;界面显示直接读取当前字段。":`Choices apply only to this world. Re-enabled requests take effect on the next normal send; interface displays read current fields immediately.`,随组暂停:`Paused by group`,"后置声明无法编辑,请在文件编辑中修复后重试。":`The followup declaration cannot be edited. Repair it in the file editor and try again.`,"主剧情完成后按此顺序生成额外内容。关闭保留提示和资源;创建世界后独立保存,后续修改需通过世界修订应用。":`Generate extra content in this order after the main story. Disabling keeps prompts and resources. Created worlds keep independent copies; later changes require applying a world revision.`,"根据已完成的剧情,通过 artifact_emit 输出 output_1。":`Use artifact_emit to emit output_1 based on the settled story.`,故事正文:`Story content`,侧栏:`Sidebar`,删除请求:`Delete request`,新增包后置请求:`Add package followup`,包请求名称:`Package request name`,包请求提示词:`Package request prompt`,包请求显示位置:`Package request display location`,稳定请求身份:`Stable request identity`,产物字节上限:`Artifact byte limit`,内容包脚本权限:`Package script permissions`,"仅授权此处已保存的资源代码。导入不会授权,资源修改后需要重新授权;关闭立即停止此来源脚本,HTML 和样式仍可显示。":`Authorize only the saved resource code here. Imports do not grant permission, and changed resources require authorization again. Revoking permission stops scripts from this source; HTML and styles remain visible.`,允许运行内容包脚本:`Allow package scripts`,撤销全部包脚本授权:`Revoke all package script permissions`,"请先保存修改,再授权资源代码。":`Save your changes before authorizing resource code.`,"JavaScript 仅在 app 模式且已允许脚本时运行。":`JavaScript runs only in app mode with scripts enabled.`,"HTML 模板":`HTML template`,编辑渲染资源:`Edit rendering resources`,"在此位置展开当前世界的内容包后置请求。无声明时为空组,不调用模型;正文由内容包管理。":`The current world’s package followups expand here. An undeclared group is empty and makes no model calls; the package owns its content.`,克隆后置请求:`Clone followup`,"界面产物:场景回顾,Markdown,剧情内容区;保存到提交,替换上一份,显式清除。":`Artifact: scene recap, Markdown, story area; saved with the commit, replacing the previous result until explicitly cleared.`,系统后置提示词:`System followup prompt`,"系统示例只读;新运行使用应用最新版,克隆后独立保存。":`This system example is read-only. New runs use the current application example; clones are saved independently.`,内容包后置请求:`Content-package followups`,最近一次请求材料:`Latest request materials`,"下一次发送将采用当前预设和世界材料;原生对话保留。":`The next send uses the current preset and world materials while preserving the native conversation.`,"每次正常发送重新编译,工具往返使用本轮快照":`Each normal send recompiles; tool exchanges use the same run snapshot.`,下一次发送候选:`Next send candidate`,"分别展示最近一次请求、累计当时读取和下一次候选;旧读取不代表当前字段值。":`Inspect the latest request, accumulated historical reads, and next candidate separately. Past reads do not prove current field values.`,本次请求快照的真实覆盖:`Actual coverage of this request snapshot`,"查看下一次发送候选的完整 Prompt Preview":`Inspect the complete next-send Prompt Preview`,"游玩在下一次正常发送生效,原样重试保留旧请求":`Play changes take effect on the next normal send; exact retries retain the saved request.`,"· 游玩修改在下一次正常发送生效":`· Play changes take effect on the next normal send`,"保留原生对话,重新编译当前提示和材料":`Preserve the native conversation and recompile current prompts and materials`,"累计实际读取(当时内容)":`Accumulated reads (historical content)`,"累计读取的当时内容,不代表当前字段值":`Historical content read at the time, not current field values`,查看各次请求保存的提示材料:`Inspect saved prompt materials for each request`,"原样重试使用已保存请求,不采用新预设。":`Exact retry uses the saved request and does not apply a new preset.`,"正常发送和空输入续写使用最新预设;本轮工具执行期间保持不变。":`Normal sends and empty continuations use the latest preset; it stays fixed during this tool loop.`,"玩家视图格式无效,请检查文本处理规则。":`Invalid player-view data. Check the text processing rules.`,"玩家视图、布局、资源与无模型预览":`Player views, layout, resources and model-free preview`,"直接用已保存的玩家视图配置界面,不需要后置请求。模型产物的位置设置独立保留。":`Configure displays directly from saved player views. Model-generated outputs have separate placement settings.`,"当前没有模型产物;玩家视图面板仍可独立使用。":`There are no model-generated outputs. Player-view panels work independently.`,"同一玩家视图由自定义面板接管,其他视图仍显示默认卡片。":`Custom panels replace the default card for their player view. Other views keep their default cards.`,纯界面预览:`Interface preview`,"使用当前编辑内容与所选世界的已保存玩家视图,不调用模型,也不保存或修改世界。修改后请重新预览。":`Preview the current edits with saved player views from the selected world. This makes no model calls and does not save or change the world. Preview again after editing.`,预览世界:`Preview world`,选择已有世界:`Select an existing world`,预览界面:`Preview interface`,"正在读取玩家视图…":`Loading player views…`,"请先解决高级文件与结构化编辑冲突,再预览。":`Resolve conflicts between file and structured edits before previewing.`,渲染资源:`Rendering resources`,"编辑资源 {name}":`Edit resource {name}`,"资源内容 {name}":`Resource content {name}`,"对话连接已断开,正在重新连接…":`Conversation disconnected. Reconnecting…`,"对话同步失败,请重新打开此页面。":`Conversation synchronization failed. Reopen this page to reconnect.`,"正在读取工作区…":`Loading workspace…`,工作区读取失败:`Failed to load the workspace`,"内容包当前树已整批保存。":`The content package tree has been saved.`,"内容包已重命名。":`The content package has been renamed.`,"内容包 ZIP 自身大小超过安全上限。":`The content package ZIP exceeds the safety limit.`,"ZIP 内容包已导入为新的本地身份。":`The ZIP content package was imported with a new local identity.`,"请先保存手动编辑,再开始 AI 设定完善。":`Save manual edits before starting setting improvement.`,"请先保存并启用一份模型配置。":`Save and enable a model connection first.`,"世界已重命名为“{name}”。":`World renamed to “{name}”.`,"删除世界“{title}”?它的全部提交、历史和存档都会从本机移除,且无法撤销。":`Delete world “{title}”? All of its commits, history, and saved data will be removed from this device. This cannot be undone.`,"世界已从本机删除。":`The world has been deleted from this device.`,操作失败:`Operation failed`,未命名世界:`Untitled world`,模型连接:`Model connection`,完成原因:`Provider completion reason`,原生续传载荷:`Provider-native continuation payload`,不可用:`Unavailable`,尚未配置:`Not configured`,内容包:`Content package`,内容包编辑方式:`Content package editing mode`,"已放弃未保存修改;内容包当前树未改变。":`Unsaved edits were discarded; the current content package tree is unchanged.`,"Narraeon · 本地优先":`Narraeon · Local first`,世界工作区:`World workspace`,"创作内容包,连接 AI 主持,让每个世界独立演化。":`Create content packages, connect a model host, and let every world evolve independently.`,返回工作区:`Back to workspace`,手动编辑:`Edit manually`,"AI 完善":`Setting improvement`,新建世界:`Create world`,从当前内容包创建:`Create from current content package`,界面语言:`Interface language`,"界面语言已保存。":`Interface language saved.`,简体中文:`Simplified Chinese`,"扩展 iframe 加载失败":`Extension iframe failed to load`,"下一次真实 Prompt Preview":`Next real Prompt Preview`,"每个后置请求在主调用链完成后单独派发一次,共用同一段冻结前缀,彼此看不见对方,也不会进入之后的模型上下文。请求提示、产物格式和显示位置都可在当前页面编辑。":`Each follow-up is dispatched once after the main call chain. They share the same frozen prefix, cannot see one another, and never enter later model context. Edit each request prompt, artifact format, and display location here.`,"上次模型请求没有完整返回。保持输入框为空并点击“追加上下文”,就会原样发送已保存的请求;不会追加玩家指令,中断片段也不会进入模型上下文。":`The previous model request did not return completely. Leave the input empty and append context to resend the saved request unchanged. No player instruction is appended, and the interrupted fragment does not enter model context.`,产物调试器:`Artifact debugger`,扩展状态:`Extension status`,核心已提交:`Core committed`,核心未提交:`Core not committed`,"已完成请求:":`Completed requests:`,无:`None`,玩家视图面板诊断:`Player-view panel diagnostics`,玩家视图面板:`Player-view panel`,"已提交玩家视图投影 · head:":`Committed player-view projection · head:`,"bridge 事件(":`Bridge events (`,来源:`Source`,世界端点:`World endpoint`,未提交:`Uncommitted`,"保存/投影":`Retention / projection`,"renderer 状态":`Renderer status`,最终内容:`Final content`,"regex steps(structured_payload 以规范化 JSON 文本为输入)":`Regex steps (structured_payload uses normalized JSON text as input)`,"artifact 内容超过宿主渲染上限":`Artifact content exceeds the host rendering limit`,正则规则数量超过宿主上限:`The number of regex rules exceeds the host limit`,"markdown_html 规则只作用于 Markdown 转换后的 HTML":`markdown_html rules apply only to HTML produced from Markdown`,正则转换次数超过宿主上限:`The number of regex transformations exceeds the host limit`,正则输出超过宿主字节上限:`Regex output exceeds the host byte limit`,"Markdown HTML DOM 大小超过宿主上限":`The Markdown HTML DOM exceeds the host size limit`,正则执行失败:`Regex execution failed`,"正则规则失败,实例显示 raw artifact(可恢复)":`A regex rule failed; the instance is showing the raw artifact and can recover`,"正则规则失败,实例进入错误状态并显示 raw artifact":`A regex rule failed; the instance entered an error state and is showing the raw artifact`,"document renderer 必须包含唯一 narraeon:content marker":`A document renderer must contain exactly one narraeon:content marker`,"旧产物没有可证明的回复归属,仅保留诊断,无法自动迁移为正文附件。":`Legacy artifacts have no proven reply attachment. Diagnostics remain available; automatic migration to story attachments is unavailable.`,生成失败:`Generation failed`,已更新:`Updated`,更新中:`Updating`,生成中:`Generating`,生成未完成:`Generation incomplete`,"ArtifactExtensionMount 必须位于 ArtifactExtensionHost 内":`ArtifactExtensionMount must be inside ArtifactExtensionHost`,"文档 renderer 无效":`Invalid document renderer`,扩展报告了未知诊断:`The extension reported an unknown diagnostic`,"当前调用链正在提交,暂不能修改草稿":`The current call chain is committing; its draft cannot be changed yet`,"composer.set_draft.text 必须是字符串":`composer.set_draft.text must be a string`,"扩展资源不可用,显示 raw artifact。":`Extension resources are unavailable; showing the raw artifact.`,恢复此扩展:`Restore this extension`,停用此扩展:`Disable this extension`,本地可信代码:`Trusted local code`,扩展展帧加载失败:`Extension iframe failed to load`,产物诊断:`Artifact diagnostics`,内容包当前树:`Current content-package tree`,"逐份编辑 YAML/Markdown;整批保存时才原子替换已保存版本。":`Edit YAML and Markdown files individually; the saved version is replaced atomically only when the whole draft is saved.`,内容包编辑状态:`Content-package editing status`,"已保存版本:":`Saved version:`,可用:`Usable`,需要修复:`Needs repair`,有未保存修改:`Unsaved changes`,草稿与已保存版本一致:`Draft matches the saved version`,内容包文件统计:`Content-package file counts`,全部文件:`All files`,开场白:`Opening text`,世界内容:`World content`,控制文件:`Control files`,内容包文件:`Content-package files`,当前草稿:`Current draft`,文件:`Files`,筛选文件:`Filter files`,"人物、地点或文件名":`Character, location, or file name`,内容包文件树:`Content-package file tree`,"没有匹配的文件。":`No files match.`,新建文件:`Create file`,新文件路径:`New file path`,"新文件先加入本地草稿;Runtime 会在整批保存时检查路径与内容。":`A new file is added to the local draft first. Runtime validates its path and content when the whole draft is saved.`,加入草稿:`Add to draft`,文件编辑器:`File editor`,当前草稿没有文件:`The current draft has no files`,"从左侧新建第一份文件,或返回工作区导入已有内容包。":`Create the first file from the left, or return to the workspace and import an existing content package.`,从草稿移除:`Remove from draft`,文件路径:`File path`,应用新路径:`Apply new path`,二进制资源不在文本编辑器展开:`Binary resources are not expanded in the text editor`,"当前 Base64 内容会原样保留;如需替换资源,请重新导入内容包。":`The current Base64 content is preserved exactly. Reimport the content package to replace the resource.`,文件内容:`File content`,"Ctrl / ⌘ + S 整批保存":`Ctrl / ⌘ + S saves the whole draft`,行:`Lines`,字符:`Characters`,已保存版本诊断:`Saved-version diagnostics`,已保存版本有:`The saved version has`,"已保存版本有 {count} 项需要修复":`The saved version has {count} issue(s) to repair`,项需要修复:`issue(s) to repair`,"诊断对应上一次保存;整批保存后会按新草稿重新检查。":`Diagnostics describe the last save. Saving the whole draft checks the new draft again.`,草稿尚未保存:`Draft not saved`,当前树已保存:`Current tree saved`,"保存会把整棵草稿作为一个候选原子替换,不会逐文件提交。":`Saving atomically replaces the entire tree with one candidate; files are not committed one at a time.`,整批保存:`Save all changes`,放弃未保存修改:`Discard unsaved changes`,内容包操作:`Content-package actions`,"请先整批保存或放弃当前草稿,再操作已保存内容包。":`Save or discard the current draft before acting on the saved content package.`,内容包标题:`Content-package title`,导入的内容包:`Imported content package`,重命名:`Rename`,复制为新本地身份:`Copy as a new local identity`,"导出 ZIP":`Export ZIP`,删除内容包:`Delete content package`,"请输入文件路径。":`Enter a file path.`,"路径必须是使用 / 的内容包相对路径。":`The path must be relative to the content package and use / separators.`,"路径不能包含空目录、. 或 ..。":`The path cannot contain empty directories, . or ...`,"当前文件原生 V1 不接受 manifest 或 JSON 内容文件。":`File-native V1 does not accept a manifest or JSON content files.`,"当前草稿中已经存在同一路径。":`The current draft already contains that path.`,二进制资源:`Binary resource`,玩家首次行动前看到的开场白:`Opening text shown before the player's first action`,会复制为世界状态的设定文档:`Setting document copied into world state`,会复制为世界控制的作者资产:`Author asset copied into world control`,内容包文本资源:`Content-package text resource`,"写下玩家首次行动前立即看到的局面……":`Describe the situation visible immediately before the player's first action…`,"$document:\n id: character.example\n ref: example\n title: 示例人物\n summary: 一句话稳定简介。\n aliases: []\n":`$document:
19
19
  id: character.example
20
20
  ref: example
21
21
  title: Alex Morgan
@@ -1044,7 +1044,7 @@ followups: []
1044
1044
  #player-view-panel-root dd { margin: 0; }
1045
1045
  #player-view-panel-root ul, #player-view-panel-root dl dl { margin: .25rem 0 0 1rem; padding-left: 1rem; }
1046
1046
  .player-view-empty { color: #6c6259; }
1047
- `}}V(`en`);function ge(e){return[{id:`action-choices`,label:e===`zh-CN`?`行动选项`:`Action choices`,name:e===`zh-CN`?`下一步建议(可编辑副本)`:`Next-step ideas (editable copy)`,files:me(e)},{id:`status-panel`,label:e===`zh-CN`?`状态栏`:`Status panel`,name:e===`zh-CN`?`状态栏(可编辑副本)`:`Status panel (editable copy)`,files:V(e)},{id:`generic-panels`,label:e===`zh-CN`?`Markdown / HTML 面板`:`Markdown / HTML panels`,name:e===`zh-CN`?`通用面板(可编辑副本)`:`Generic panels (editable copy)`,files:he(e)},{id:`artifact-debugger`,label:e===`zh-CN`?`产物调试器`:`Artifact debugger`,name:e===`zh-CN`?`产物调试器(可编辑副本)`:`Artifact debugger (editable copy)`,files:he(e)}]}ge(`en`);var _e=[[`home`,`工作区`],[`content`,`内容编辑`],[`plays`,`预设`],[`create`,`新建世界`],[`preview`,`提示词预览`]];function ve({screen:e,locale:t,localeSaving:n,navigationLocked:r,activeModelName:i,onNavigate:a,onLocaleChange:o}){let[s,c]=(0,l.useState)(!1);function u(e){r||(c(!1),a(e))}return(0,F.jsxs)(`header`,{className:`workspace-header`,children:[(0,F.jsx)(`button`,{type:`button`,className:`workspace-brand`,"aria-label":D(`返回工作区`),disabled:r,onClick:()=>u(`home`),children:`Narraeon`}),(0,F.jsx)(`nav`,{id:`workspace-navigation`,className:s?`workspace-navigation is-open`:`workspace-navigation`,"aria-label":D(`工作区导航`),onKeyDown:e=>{e.key===`Escape`&&(c(!1),document.getElementById(`workspace-menu-toggle`)?.focus())},children:_e.map(([t,n])=>(0,F.jsx)(`button`,{type:`button`,"aria-current":e===t?`page`:void 0,disabled:r,onClick:()=>u(t),children:D(n)},t))}),(0,F.jsxs)(`div`,{className:`workspace-header-actions`,children:[(0,F.jsxs)(`label`,{className:`workspace-locale-picker`,children:[(0,F.jsx)(`span`,{children:D(`界面语言`)}),(0,F.jsxs)(`select`,{"aria-label":D(`界面语言`),value:t,disabled:n||r,onChange:e=>o(e.currentTarget.value),children:[(0,F.jsx)(`option`,{value:`en`,children:`English`}),(0,F.jsx)(`option`,{value:`zh-CN`,children:D(`简体中文`)})]})]}),(0,F.jsxs)(`button`,{type:`button`,className:`workspace-model-button`,"aria-label":D(`模型连接`),"aria-current":e===`model`?`page`:void 0,title:i??D(`尚未配置`),disabled:r,onClick:()=>u(`model`),children:[(0,F.jsx)(`span`,{className:`workspace-model-dot ${i===null?`needs-attention`:`is-ready`}`,"aria-hidden":`true`}),D(`模型连接`)]}),(0,F.jsx)(`button`,{id:`workspace-menu-toggle`,type:`button`,className:`workspace-menu-toggle`,"aria-label":D(`切换页面`),"aria-expanded":s,"aria-controls":`workspace-navigation`,disabled:r,onClick:()=>c(!s),children:`☰`})]})]})}function ye(){return[{scheme:`d100`,label:`d100`,description:D(`默认方案。掷一颗个位骰,可加奖励骰或惩罚骰;结果不高于目标值即成功。`)},{scheme:`d20`,label:`d20`,description:D(`掷一颗 d20 加修正,总值不低于难度即成功;可掷优势或劣势。`)},{scheme:`bag`,label:D(`混乱袋`),description:D(`从混乱袋抽一枚标记;数值标记加修正后与难度比较,特殊标记直接决定大成功或大失败。`)}]}function be({pending:e,onCancel:t,onCreate:n}){let r=(0,l.useRef)(null),i=(0,l.useId)(),[a,o]=(0,l.useState)(`interactive-fiction`),[s,c]=(0,l.useState)(`d100`),[u,d]=(0,l.useState)(``);(0,l.useEffect)(()=>{let e=r.current;return e!==null&&!e.open&&e.showModal(),()=>{e?.open===!0&&e.close()}},[]);async function f(){if(!e){d(``);try{await n(a===`tabletop`?{mode:`tabletop`,scheme:s}:{mode:`interactive-fiction`})}catch(e){d(e instanceof Error?e.message:D(`内容包创建失败`))}}}return(0,F.jsx)(`dialog`,{ref:r,className:`create-package-dialog`,"aria-labelledby":`${i}-title`,onCancel:n=>{n.preventDefault(),e||t()},children:(0,F.jsxs)(`form`,{className:`create-package-form`,onSubmit:e=>{e.preventDefault(),f()},children:[(0,F.jsxs)(`header`,{children:[(0,F.jsx)(`h2`,{id:`${i}-title`,children:D(`新建内容包`)}),(0,F.jsx)(`p`,{children:D(`内容包的玩法在创建时确定,之后不能切换。`)})]}),(0,F.jsxs)(`fieldset`,{className:`create-package-choices`,disabled:e,children:[(0,F.jsx)(`legend`,{children:D(`玩法`)}),(0,F.jsxs)(`label`,{className:`create-package-choice`,children:[(0,F.jsx)(`input`,{type:`radio`,name:`${i}-mode`,value:`interactive-fiction`,checked:a===`interactive-fiction`,onChange:()=>o(`interactive-fiction`)}),(0,F.jsx)(`span`,{children:D(`互动式小说`)})]}),(0,F.jsxs)(`label`,{className:`create-package-choice`,children:[(0,F.jsx)(`input`,{type:`radio`,name:`${i}-mode`,value:`tabletop`,checked:a===`tabletop`,onChange:()=>o(`tabletop`)}),(0,F.jsx)(`span`,{children:D(`跑团`)})]})]}),a===`tabletop`?(0,F.jsxs)(`fieldset`,{className:`create-package-choices`,disabled:e,children:[(0,F.jsx)(`legend`,{children:D(`判定方案`)}),ye().map(e=>(0,F.jsxs)(`div`,{className:`create-package-choice-block`,children:[(0,F.jsxs)(`label`,{className:`create-package-choice`,children:[(0,F.jsx)(`input`,{type:`radio`,name:`${i}-scheme`,value:e.scheme,checked:s===e.scheme,"aria-describedby":`${i}-${e.scheme}-description`,onChange:()=>c(e.scheme)}),(0,F.jsx)(`span`,{children:e.label})]}),(0,F.jsx)(`p`,{className:`field-note`,id:`${i}-${e.scheme}-description`,children:e.description})]},e.scheme))]}):null,u===``?null:(0,F.jsx)(`p`,{className:`create-package-failure`,role:`alert`,children:u}),e?(0,F.jsx)(`p`,{role:`status`,children:D(`正在创建内容包…`)}):null,(0,F.jsxs)(`footer`,{className:`button-row`,children:[(0,F.jsx)(`button`,{type:`button`,className:`secondary-button`,disabled:e,onClick:t,children:D(`取消`)}),(0,F.jsx)(`button`,{type:`submit`,disabled:e,children:D(`创建`)})]})]})})}function xe(e,t){return typeof e==`string`&&e.trim().length>0&&[...e].length<=80&&!/[\r\n\u0085\u2028\u2029]/u.test(e)&&typeof t==`string`&&t.trim().length>0&&new TextEncoder().encode(t).byteLength<=16384}function Se({client:e,templates:t,onChange:n,onClose:r,onNavigationLockChange:i}){let[a,o]=(0,l.useState)(null),[s,c]=(0,l.useState)(``),[u,d]=(0,l.useState)(``),[f,p]=(0,l.useState)(!1),m=(0,l.useRef)(!1),[h,g]=(0,l.useState)(``),_=s!==(a?.name??``)||u!==(a?.body??``);(0,l.useEffect)(()=>(i?.(_||f),()=>i?.(!1)),[_,f,i]);function v(){return!m.current&&(!_||globalThis.confirm(D(`放弃未保存的主角模板修改?`)))}function y(e){o(e),c(e?.name??``),d(e?.body??``),g(``)}async function b(t){if(!m.current){m.current=!0,p(!0),g(``);try{let r=await e.request(t);n(r),y(t.type===`protagonist-templates.delete`?null:t.type===`protagonist-templates.save`&&t.id!==void 0?r.templates.find(({id:e})=>e===t.id)??null:r.templates.at(-1)??null)}catch(e){g(e instanceof Error?e.message:D(`主角模板保存失败`))}finally{m.current=!1,p(!1)}}}return(0,F.jsx)(`div`,{className:`protagonist-template-scrim`,children:(0,F.jsxs)(`section`,{className:`protagonist-template-manager`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`protagonist-templates-title`,children:[(0,F.jsxs)(`header`,{children:[(0,F.jsx)(`h2`,{id:`protagonist-templates-title`,children:D(`管理主角模板`)}),(0,F.jsx)(`button`,{type:`button`,disabled:f,onClick:()=>{v()&&r()},children:D(`关闭`)})]}),(0,F.jsxs)(`div`,{className:`protagonist-template-columns`,children:[(0,F.jsxs)(`nav`,{"aria-label":D(`主角模板列表`),children:[(0,F.jsx)(`button`,{type:`button`,disabled:f,onClick:()=>{v()&&y(null)},children:D(`新建主角模板`)}),t.map(e=>(0,F.jsx)(`button`,{type:`button`,disabled:f,"aria-pressed":a?.id===e.id,onClick:()=>{v()&&y(e)},children:e.name},e.id))]}),(0,F.jsxs)(`form`,{onSubmit:e=>{e.preventDefault(),xe(s,u)&&b({type:`protagonist-templates.save`,...a===null?{}:{id:a.id},name:s,body:u})},children:[(0,F.jsxs)(`label`,{children:[D(`模板名称`),(0,F.jsx)(`input`,{"aria-label":D(`模板名称`),value:s,disabled:f,onChange:e=>c(e.currentTarget.value)})]}),(0,F.jsxs)(`label`,{children:[D(`模板正文`),(0,F.jsx)(`textarea`,{"aria-label":D(`模板正文`),value:u,disabled:f,onChange:e=>d(e.currentTarget.value)})]}),(0,F.jsx)(`small`,{children:D(`名称为 1–80 个字符;正文为自由 Markdown,最多 16 KiB。`)}),h&&(0,F.jsx)(`p`,{role:`alert`,children:h}),(0,F.jsxs)(`div`,{className:`button-row`,children:[(0,F.jsx)(`button`,{className:`primary-button`,type:`submit`,disabled:f||!_||!xe(s,u),children:D(`保存`)}),(0,F.jsx)(`button`,{type:`button`,disabled:f||a===null,onClick:()=>{a!==null&&v()&&b({type:`protagonist-templates.copy`,id:a.id})},children:D(`复制`)}),(0,F.jsx)(`button`,{type:`button`,className:`danger-button`,disabled:f||a===null,onClick:()=>{a!==null&&!m.current&&globalThis.confirm(D(`删除这个主角模板?`))&&b({type:`protagonist-templates.delete`,id:a.id})},children:D(`删除`)})]})]})]})]})})}function Ce({client:e,packages:t,selectedId:n,pending:r,modelConfigured:i,onSelect:a,onCreate:o,onEdit:s,onConfigureModel:c,onNavigationLockChange:u}){let[d,f]=(0,l.useState)([]),[p,m]=(0,l.useState)(``),[g,_]=(0,l.useState)(!0),[v,y]=(0,l.useState)(``),[b,x]=(0,l.useState)(0),[S,C]=(0,l.useState)(!1);(0,l.useEffect)(()=>{let t=!0;return e.request({type:`protagonist-templates.read`}).then(e=>{t&&(f(e.templates),y(``))}).catch(e=>{t&&y(e instanceof Error?e.message:D(`主角模板读取失败`))}).finally(()=>{t&&_(!1)}),()=>{t=!1}},[e,b]);let w=t.find(({localId:e})=>e===n),[T,O]=(0,l.useState)(null);return(0,l.useEffect)(()=>{let t=!0;return n!==``&&e.request({type:`content.read`,packageId:n}).then(({files:e})=>{t&&O({packageId:n,failure:``,opening:e.find(({path:e})=>e===`opening.md`)?.contents??``})}).catch(e=>{t&&O({packageId:n,opening:``,failure:e instanceof Error?e.message:D(`内容包读取失败`)})}),()=>{t=!1}},[e,n]),(0,F.jsxs)(`main`,{className:`create-world-screen`,children:[(0,F.jsxs)(`aside`,{children:[(0,F.jsx)(`h1`,{children:D(`新建世界`)}),(0,F.jsx)(`p`,{children:D(`选择一份内容包,从它的开场开始。`)}),t.length===0?(0,F.jsx)(`p`,{children:D(`先新建或导入一份内容包。`)}):(0,F.jsxs)(`label`,{children:[D(`内容包`),(0,F.jsx)(`select`,{"aria-label":D(`创建世界的内容包`),value:n,disabled:r,onChange:e=>a(e.currentTarget.value),children:t.map(e=>(0,F.jsxs)(`option`,{value:e.localId,children:[`${e.title} · ${h(e.gameplay,E())}`,e.status===`needs_repair`?D(` · 需要修复`):``]},e.localId))})]}),(0,F.jsxs)(`label`,{children:[D(`主角模板`),(0,F.jsxs)(`select`,{"aria-label":D(`主角模板`),value:p,disabled:r||g,onChange:e=>m(e.currentTarget.value),children:[(0,F.jsx)(`option`,{value:``,children:D(`不使用`)}),d.map(e=>(0,F.jsx)(`option`,{value:e.id,children:e.name},e.id))]})]}),(0,F.jsx)(`button`,{type:`button`,disabled:r||g||v!==``,onClick:()=>C(!0),children:D(`管理主角模板`)}),v&&(0,F.jsxs)(`p`,{role:`alert`,children:[v,(0,F.jsx)(`button`,{type:`button`,disabled:r,onClick:()=>{_(!0),x(e=>e+1)},children:D(`重新读取主角模板`)})]}),w?.status===`needs_repair`&&(0,F.jsx)(`p`,{role:`status`,children:D(`内容包需要修复后才能创建世界。`)}),!i&&(0,F.jsx)(`button`,{type:`button`,disabled:r,onClick:c,children:D(`配置模型连接`)}),(0,F.jsx)(`button`,{type:`button`,className:`primary-button`,disabled:r||!i||w?.status!==`usable`,onClick:()=>o(d.find(({id:e})=>e===p)),children:D(r?`正在创建世界…`:`从当前内容包创建`)}),(0,F.jsx)(`button`,{type:`button`,disabled:r,onClick:s,children:D(w===void 0?`新建内容包`:`完善这份内容包`)}),(0,F.jsx)(`small`,{children:D(`新世界独立保存,之后的游玩不会修改源内容包。`)})]}),(0,F.jsxs)(`article`,{"aria-label":D(`开场白预览`),children:[(0,F.jsx)(`span`,{children:D(`开场白`)}),(0,F.jsx)(`h2`,{children:w?.title}),w===void 0?null:(0,F.jsx)(`p`,{className:`create-world-gameplay`,children:h(w.gameplay,E())}),T?.packageId===n&&T.failure?(0,F.jsx)(`p`,{role:`alert`,children:T.failure}):T?.packageId===n?(0,F.jsx)(`p`,{className:`create-world-opening`,children:T.opening||D(`这份内容包还没有开场白。`)}):w===void 0?null:(0,F.jsx)(`p`,{role:`status`,children:D(`正在读取开场白…`)})]}),S&&(0,F.jsx)(Se,{client:e,templates:d,onChange:e=>{f(e.templates),e.templates.some(({id:e})=>e===p)||m(``)},onClose:()=>C(!1),...u===void 0?{}:{onNavigationLockChange:u}})]})}function we({text:e,onDismiss:t,className:n=``,role:r=`status`}){return(0,F.jsxs)(`div`,{className:`dismissible-notice ${n}`,role:r,children:[(0,F.jsx)(`span`,{children:e}),(0,F.jsx)(`button`,{type:`button`,"aria-label":D(`关闭提示`),title:D(`关闭提示`),onClick:t,children:`×`})]})}function Te(e,t=Date.now()){let n=Math.max(0,Math.floor((t-e)/1e3));if(n<60)return D(`刚刚`);let r=new Intl.RelativeTimeFormat(E(),{numeric:`always`});for(let[e,t]of[[`year`,31536e3],[`month`,2592e3],[`day`,86400],[`hour`,3600],[`minute`,60]])if(n>=t)return r.format(-Math.floor(n/t),e);return D(`刚刚`)}function Ee({contentPackages:e,worlds:t,worldBackupImport:n,renderWorldBackupExport:r,selectedPackageId:i,modelConfigured:a,activeModelName:o,currentPresetName:s,importArchive:c,importPending:u,samplePending:d=!1,onImportArchiveChange:f,onOpenPlayPresets:p=()=>void 0,onCreateWorld:m,onCreatePackage:g,onCreateSamplePackage:_,onConfigureModel:v,onImportPackage:y,onOpenPackage:b,onOpenWorld:x,onRenamePackage:S,onDeletePackage:C,onRenameWorld:w,onDeleteWorld:T}){let[O,k]=(0,l.useState)(null),[A,ee]=(0,l.useState)(``),[j,te]=(0,l.useState)(null),[ne,M]=(0,l.useState)(``),N=u||d,P=e.filter(({status:e})=>e===`usable`).length,re=e.length-P;return(0,F.jsxs)(`main`,{className:`home-dashboard`,children:[(0,F.jsxs)(`header`,{className:`home-page-heading`,children:[(0,F.jsx)(`h1`,{children:D(`世界工作区`)}),(0,F.jsx)(`p`,{children:D(`选择一个世界继续故事,或从内容包开始创作。`)})]}),(0,F.jsxs)(`div`,{className:`home-library-grid`,children:[(0,F.jsxs)(`section`,{className:`home-surface home-world-library`,"aria-labelledby":`home-worlds-title`,children:[(0,F.jsxs)(`header`,{className:`home-section-heading`,children:[(0,F.jsx)(`div`,{children:(0,F.jsx)(`h2`,{id:`home-worlds-title`,children:D(`继续游玩`)})}),(0,F.jsx)(`span`,{className:`home-count`,"aria-label":D(`{count} 个世界`,{count:t.length}),children:t.length.toLocaleString(E())}),(0,F.jsx)(`button`,{type:`button`,disabled:N,onClick:m,children:D(`新建世界`)}),n]}),t.length>0?(0,F.jsx)(`div`,{className:`home-world-grid`,children:t.map(e=>(0,F.jsxs)(`div`,{className:`home-world-card-shell`,children:[(0,F.jsxs)(`button`,{className:`home-world-card`,type:`button`,"aria-label":D(`打开世界:{title}`,{title:e.title}),disabled:N,onClick:()=>x(e.worldId),children:[(0,F.jsxs)(`span`,{className:`home-world-card-story`,children:[(0,F.jsx)(`strong`,{children:e.title}),(0,F.jsx)(`small`,{className:`home-world-card-gameplay`,children:h(e.gameplay,E())}),(0,F.jsxs)(`span`,{className:`home-world-card-activity`,children:[e.lastActivityAt==null?(0,F.jsx)(`span`,{children:D(`活动时间未记录`)}):(0,F.jsx)(`time`,{dateTime:new Date(e.lastActivityAt).toISOString(),title:new Date(e.lastActivityAt).toLocaleString(E()),children:Te(e.lastActivityAt)}),(0,F.jsx)(`span`,{children:D(e.playerTurns===1?`1 回合`:`{count} 回合`,{count:(e.playerTurns??0).toLocaleString(E())})})]}),e.excerpt==null?null:(0,F.jsx)(`span`,{className:`home-world-card-excerpt`,title:e.excerpt,children:e.excerpt})]}),(0,F.jsxs)(`span`,{className:`home-world-card-action`,children:[D(`进入世界`),(0,F.jsx)(`span`,{"aria-hidden":`true`,children:`→`})]})]}),(0,F.jsxs)(`details`,{className:`home-world-card-actions`,children:[(0,F.jsx)(`summary`,{"aria-label":D(`世界操作:{title}`,{title:e.title}),children:`•••`}),(0,F.jsxs)(`div`,{children:[r?.(e),(0,F.jsx)(`button`,{className:`home-world-rename`,type:`button`,"aria-label":D(`重命名世界:{title}`,{title:e.title}),disabled:N,onClick:()=>{k(e.worldId),ee(e.title)},children:D(`重命名`)}),(0,F.jsx)(`button`,{className:`home-world-delete`,type:`button`,"aria-label":D(`删除世界:{title}`,{title:e.title}),disabled:N,onClick:()=>T(e),children:D(`删除`)})]})]}),O===e.worldId?(0,F.jsxs)(`form`,{className:`home-world-rename-form`,onSubmit:t=>{t.preventDefault();let n=A.trim();n!==``&&n!==e.title&&(w(e,n),k(null))},children:[(0,F.jsxs)(`label`,{children:[(0,F.jsx)(`span`,{children:D(`世界名称`)}),(0,F.jsx)(`input`,{"aria-label":D(`世界名称`),autoFocus:!0,maxLength:160,value:A,onChange:e=>ee(e.currentTarget.value)})]}),(0,F.jsxs)(`div`,{className:`button-row`,children:[(0,F.jsx)(`button`,{type:`submit`,"aria-label":D(`保存世界名称`),disabled:A.trim()===``||A.trim()===e.title,children:D(`保存`)}),(0,F.jsx)(`button`,{type:`button`,className:`secondary-button`,onClick:()=>k(null),children:D(`取消`)})]})]}):null]},e.worldId))}):(0,F.jsx)(`div`,{className:`home-empty-world`,children:(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`h3`,{children:D(`还没有正在游玩的世界`)}),(0,F.jsx)(`p`,{children:D(`先新建内容包,或上传一份 ZIP,再整理开场与世界文档并创建独立世界。`)})]})})]}),(0,F.jsxs)(`section`,{className:`home-surface home-content-library`,"aria-labelledby":`home-content-title`,children:[(0,F.jsxs)(`header`,{className:`home-section-heading`,children:[(0,F.jsx)(`div`,{children:(0,F.jsx)(`h2`,{id:`home-content-title`,children:D(`内容包`)})}),(0,F.jsx)(`button`,{type:`button`,disabled:N,onClick:g,children:D(`新建内容包`)}),(0,F.jsx)(`button`,{type:`button`,disabled:N,onClick:_,children:D(`从示例新建`)})]}),!a&&(0,F.jsx)(`button`,{type:`button`,disabled:N,onClick:v,children:D(`配置模型连接`)}),(0,F.jsxs)(`div`,{className:`home-content-layout`,children:[(0,F.jsx)(`div`,{children:e.length>0?(0,F.jsx)(`div`,{className:`home-package-list`,children:e.map(e=>(0,F.jsxs)(`div`,{className:`home-world-card-shell`,children:[(0,F.jsxs)(`button`,{className:`home-package-card`,type:`button`,"aria-label":D(`打开内容包:{name}`,{name:e.title}),"aria-pressed":e.localId===i,disabled:N,onClick:()=>b(e.localId),children:[(0,F.jsxs)(`span`,{className:`home-package-card-heading`,children:[(0,F.jsx)(`strong`,{children:e.title}),(0,F.jsx)(`span`,{className:`home-package-state ${e.status}`,children:e.status===`usable`?D(`可用`):D(`待修复`)})]}),(0,F.jsx)(`span`,{className:`home-package-card-gameplay`,children:h(e.gameplay,E())}),(0,F.jsx)(`span`,{className:`home-package-card-copy`,children:e.status===`usable`?D(`可编辑、预览并创建新世界`):D(`打开并修复当前树中的问题`)})]}),(0,F.jsxs)(`details`,{className:`home-world-card-actions`,children:[(0,F.jsx)(`summary`,{"aria-label":D(`内容包操作:{title}`,{title:e.title}),children:`•••`}),(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`button`,{className:`home-world-rename`,type:`button`,"aria-label":D(`重命名内容包:{title}`,{title:e.title}),disabled:N,onClick:()=>{te(e.localId),M(e.title)},children:D(`重命名`)}),(0,F.jsx)(`button`,{className:`home-world-delete`,type:`button`,"aria-label":D(`删除内容包:{title}`,{title:e.title}),disabled:N,onClick:()=>C(e),children:D(`删除`)})]})]}),j===e.localId?(0,F.jsxs)(`form`,{className:`home-world-rename-form`,onSubmit:t=>{t.preventDefault();let n=ne.trim();n!==``&&n!==e.title&&(S(e,n),te(null))},children:[(0,F.jsxs)(`label`,{children:[(0,F.jsx)(`span`,{children:D(`内容包标题`)}),(0,F.jsx)(`input`,{"aria-label":D(`内容包标题`),autoFocus:!0,maxLength:160,value:ne,onChange:e=>M(e.currentTarget.value)})]}),(0,F.jsxs)(`div`,{className:`button-row`,children:[(0,F.jsx)(`button`,{type:`submit`,"aria-label":D(`保存内容包标题`),disabled:ne.trim()===``||ne.trim()===e.title,children:D(`保存`)}),(0,F.jsx)(`button`,{type:`button`,className:`secondary-button`,onClick:()=>te(null),children:D(`取消`)})]})]}):null]},e.localId))}):(0,F.jsxs)(`div`,{className:`home-empty-packages`,children:[(0,F.jsx)(`h3`,{children:D(`从一组人类可读文件开始`)}),(0,F.jsx)(`p`,{children:D(`从雨夜旅店的示例故事开始,也可以新建或导入自己的内容包。`)}),(0,F.jsx)(`button`,{type:`button`,className:`primary-button`,disabled:N,onClick:_,children:D(d?`正在创建示例…`:`从示例开始`)})]})}),(0,F.jsxs)(`details`,{className:`home-import-disclosure`,children:[(0,F.jsx)(`summary`,{children:D(`导入内容包 ZIP`)}),(0,F.jsxs)(`form`,{className:`home-import-card`,onSubmit:e=>{e.preventDefault(),y()},children:[(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`h3`,{children:D(`导入 ZIP`)}),(0,F.jsx)(`p`,{children:D(`选择本应用导出或遵循相同结构的内容包 ZIP;导入会创建新的本地身份,不覆盖同名内容包。`)})]}),(0,F.jsxs)(`label`,{className:`home-zip-picker`,children:[(0,F.jsx)(`span`,{children:D(`内容包 ZIP`)}),(0,F.jsx)(`input`,{className:`home-zip-picker-input`,type:`file`,accept:`.zip,application/zip,application/x-zip-compressed`,"aria-label":D(`内容包 ZIP 文件`),disabled:N,onChange:e=>f(e.target.files?.[0]??null)},c===null?`empty`:`${c.name}:${c.size}:${c.lastModified}`),(0,F.jsx)(`span`,{className:`home-zip-picker-control`,children:(0,F.jsx)(`span`,{"aria-live":`polite`,children:c===null?D(`尚未选择`):`${c.name} · ${De(c.size)}`})})]}),(0,F.jsx)(`button`,{type:`submit`,disabled:c===null||N,children:D(u?`正在导入…`:`导入 ZIP`)})]})]})]})]})]}),(0,F.jsxs)(`footer`,{className:`home-status-line`,"aria-label":D(`工作区状态`),children:[(0,F.jsxs)(`span`,{children:[D(`模型连接`),` ·`,` `,a?o:D(`尚未配置`)]}),(0,F.jsxs)(`button`,{type:`button`,disabled:N,onClick:p,children:[D(`当前预设`),` · `,s??D(`尚未选择`)]}),(0,F.jsxs)(`span`,{children:[D(`可用内容包`),` · `,P,re>0&&D(` · {count} 份待修复`,{count:re})]})]})]})}function De(e){return e<1024?`${e} B`:e<1048576?`${(e/1024).toFixed(1)} KiB`:`${(e/1048576).toFixed(1)} MiB`}function Oe(e){return/\((?:-1|none|auto|minimal|low|medium|high|xhigh|max|\d+)\)$/iu.test(e)}function ke(){return{connectionId:null,name:D(`默认模型`),presetId:`custom`,provider:`chat_completions`,dialect:`standard`,baseUrl:``,apiKey:``,modelId:``,reasoningEffort:`provider_default`,reasoningSummary:`provider_default`,thinkingMode:`provider_default`,thinkingBudgetTokens:``,contextWindowTokens:`128000`,maxOutputTokens:`16000`}}function Ae({client:e,library:t,onLibraryChange:n,onNotice:r,onDirtyChange:i}){let a=t.connections.find(({id:e})=>e===t.activeConnectionId),[o,s]=(0,l.useState)(()=>a===void 0?ke():Me(a)),[c,u]=(0,l.useState)([]),[d,f]=(0,l.useState)(null),[p,m]=(0,l.useState)(!1),h=t.connections.find(({id:e})=>e===t.activeConnectionId),g=(0,l.useMemo)(()=>t.presets.find(({id:e})=>e===o.presetId),[o.presetId,t.presets]),_=o.connectionId===null;(0,l.useEffect)(()=>i(p),[p,i]),(0,l.useEffect)(()=>()=>{i(!1)},[i]);function v(e,t=!1){s(t=>({...t,...e})),m(!0),t&&u([])}function y(e){v({modelId:e,...o.provider===`anthropic_messages`&&o.thinkingMode===`provider_default`&&!Ie({...o,modelId:e})?{reasoningSummary:`provider_default`}:{}})}function b(e){let n=t.presets.find(({id:t})=>t===e);n!==void 0&&v(n.id===`custom`?{presetId:e}:{presetId:e,provider:n.provider,dialect:`standard`,...Fe(o,n.provider,`standard`),baseUrl:n.baseUrl,name:o.connectionId===null||o.name===D(`默认模型`)?n.name:o.name},!0)}function x(e){s(Me(e)),u([]),m(!1),r(``)}function S(){s(ke()),u([]),m(!1),r(``)}async function C(t){if(t.preventDefault(),d===null){f(`save`);try{let t=await e.request({type:`model.save`,connection:{...o.connectionId===null?{}:{connectionId:o.connectionId},name:o.name,presetId:o.presetId,provider:o.provider,dialect:o.dialect,baseUrl:o.baseUrl,...o.apiKey.length===0?{}:{apiKey:o.apiKey},modelId:o.modelId,reasoningEffort:o.reasoningEffort,reasoningSummary:o.reasoningSummary,thinkingMode:o.thinkingMode,thinkingBudgetTokens:o.thinkingMode===`enabled`?Number(o.thinkingBudgetTokens):null,contextWindowTokens:Number(o.contextWindowTokens),maxOutputTokens:Number(o.maxOutputTokens)}});n(t),m(!1);let i=t.connections.find(({id:e})=>e===t.activeConnectionId);i!==void 0&&x(i),r(D(`模型连接已保存并启用;后续请求只使用当前配置。`))}catch(e){r(Ne(e))}finally{f(null)}}}async function w(t){if(d===null){f(t);try{n(await e.request({type:`model.select`,connectionId:t})),r(D(`已切换当前模型配置;Runtime 不会自动故障转移。`))}catch(e){r(Ne(e))}finally{f(null)}}}async function T(i){if(d===null){f(`copy:${i.id}`);try{let a=Pe(i.name,t.connections),o=await e.request({type:`model.copy`,connectionId:i.id,name:a}),s=o.library.connections.find(({id:e})=>e===o.copiedConnectionId);if(s===void 0)throw Error(D(`Runtime 没有返回已克隆的模型配置`));n(o.library),x(s),r(D(`已克隆为「{name}」。副本保留本机凭据,但不会切换当前配置。`,{name:s.name}))}catch(e){r(Ne(e))}finally{f(null)}}}async function E(t){if(d===null){f(`delete:${t}`);try{n(await e.request({type:`model.delete`,connectionId:t})),o.connectionId===t&&S(),r(D(`模型配置已从本机删除。`))}catch(e){r(Ne(e))}finally{f(null)}}}async function O(){if(d===null){f(`models`);try{let t=await e.request({type:`model.models`,...o.connectionId===null?{}:{connectionId:o.connectionId},provider:o.provider,baseUrl:o.baseUrl,...o.apiKey.length===0?{}:{apiKey:o.apiKey}});u(t.models),r(D(`已从当前端点拉取 {count} 个模型 ID。`,{count:t.models.length}))}catch(e){r(Ne(e))}finally{f(null)}}}return(0,F.jsxs)(`section`,{className:`model-connection-screen`,"aria-labelledby":`model-title`,children:[(0,F.jsxs)(`header`,{className:`config-header`,children:[(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`p`,{className:`eyebrow`,children:`WORKSPACE SETTING`}),(0,F.jsx)(`h2`,{id:`model-title`,children:D(`模型连接`)}),(0,F.jsx)(`p`,{className:`field-note`,children:D(`保存多份本机配置并明确切换。API Key 不会返回浏览器;切换失败时 Runtime 不会悄悄改用另一份配置。`)})]}),(0,F.jsxs)(`div`,{className:`model-active-summary`,children:[(0,F.jsx)(`span`,{children:D(`当前配置`)}),(0,F.jsx)(`strong`,{children:h?.name??D(`尚未启用`)}),(0,F.jsx)(`small`,{children:h?.modelId??D(`先保存一份可用连接`)})]})]}),(0,F.jsxs)(`div`,{className:`config-layout`,children:[(0,F.jsxs)(`form`,{className:`config-form panel-card`,onSubmit:e=>void C(e),children:[(0,F.jsxs)(`div`,{className:`section-heading-row`,children:[(0,F.jsx)(`h3`,{children:o.connectionId===null?D(`新建配置`):D(`编辑配置`)}),o.connectionId!==null&&(0,F.jsx)(`button`,{className:`secondary-button`,disabled:p,onClick:S,type:`button`,children:D(`新建另一份`)})]}),(0,F.jsx)(`label`,{htmlFor:`model-preset`,children:D(`提供商`)}),(0,F.jsx)(`select`,{id:`model-preset`,value:o.presetId,onChange:e=>b(e.target.value),children:t.presets.map(e=>(0,F.jsx)(`option`,{value:e.id,children:e.name},e.id))}),(0,F.jsx)(`label`,{htmlFor:`model-name`,children:D(`配置名称`)}),(0,F.jsx)(`input`,{id:`model-name`,maxLength:160,required:!0,value:o.name,onChange:e=>v({name:e.target.value})}),(0,F.jsx)(`label`,{htmlFor:`model-provider`,children:D(`协议适配器`)}),(0,F.jsxs)(`select`,{id:`model-provider`,value:o.provider,onChange:e=>v({presetId:`custom`,provider:e.target.value,...Fe(o,e.target.value,o.dialect)},!0),children:[(0,F.jsx)(`option`,{value:`chat_completions`,children:`OpenAI-compatible Chat Completions`}),(0,F.jsx)(`option`,{value:`openai_responses`,children:`OpenAI Responses API`}),(0,F.jsx)(`option`,{value:`anthropic_messages`,children:`Anthropic Messages`})]}),(0,F.jsx)(`label`,{htmlFor:`model-dialect`,children:D(`端点方言`)}),(0,F.jsxs)(`select`,{id:`model-dialect`,value:o.dialect,onChange:e=>{let t=e.target.value;v({dialect:t,...Fe(o,o.provider,t)})},children:[(0,F.jsx)(`option`,{value:`standard`,children:D(`协议标准`)}),(0,F.jsx)(`option`,{value:`cliproxyapi`,children:`CLIProxyAPI`})]}),(0,F.jsxs)(`details`,{className:`parameter-help`,children:[(0,F.jsx)(`summary`,{children:D(`参数说明`)}),(0,F.jsx)(`p`,{className:`field-note`,children:D(`CLIProxyAPI 方言只启用代理明确支持的兼容参数;响应仍按所选协议解析,不从可见文本猜测思考块。Claude 的签名 thinking 请选 Responses 或 Anthropic Messages;Chat Completions 通常只有 reasoning_content,不能承诺无损续传签名。模型名的 (high) 等 thinking 后缀会覆盖请求参数,因此 Effort 与 Thinking 都必须保留“Provider 默认”。`)})]}),(0,F.jsxs)(`div`,{className:`model-reasoning-fields`,children:[(0,F.jsxs)(`label`,{htmlFor:`model-reasoning-effort`,children:[D(`Effort(推理强度)`),(0,F.jsxs)(`select`,{id:`model-reasoning-effort`,value:o.reasoningEffort,onChange:e=>v({reasoningEffort:e.target.value}),children:[(0,F.jsx)(`option`,{value:`provider_default`,children:D(`Provider 默认`)}),o.provider===`anthropic_messages`?null:(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(`option`,{value:`none`,children:`none`}),(0,F.jsx)(`option`,{value:`minimal`,children:`minimal`})]}),(0,F.jsx)(`option`,{value:`low`,children:`low`}),(0,F.jsx)(`option`,{value:`medium`,children:`medium`}),(0,F.jsx)(`option`,{value:`high`,children:`high`}),(0,F.jsx)(`option`,{value:`xhigh`,children:`xhigh`}),(0,F.jsx)(`option`,{value:`max`,children:`max`})]})]}),(0,F.jsxs)(`label`,{htmlFor:`model-thinking-mode`,children:[D(`Thinking(思考模式)`),(0,F.jsxs)(`select`,{id:`model-thinking-mode`,disabled:o.provider!==`anthropic_messages`,value:o.thinkingMode,onChange:e=>{let t=e.target.value;v({thinkingMode:t,thinkingBudgetTokens:t===`enabled`?o.thinkingBudgetTokens||Le(o.maxOutputTokens):``,reasoningSummary:t===`provider_default`||t===`disabled`?t===`provider_default`&&Ie({...o,thinkingMode:t})?o.reasoningSummary:`provider_default`:o.reasoningSummary})},children:[(0,F.jsx)(`option`,{value:`provider_default`,children:D(`Provider 默认`)}),(0,F.jsx)(`option`,{value:`adaptive`,children:`adaptive`}),(0,F.jsx)(`option`,{value:`enabled`,children:D(`手动 token 预算`)}),(0,F.jsx)(`option`,{value:`disabled`,children:`disabled`})]})]}),(0,F.jsxs)(`label`,{htmlFor:`model-reasoning-summary`,children:[D(`Thinking 返回内容`),(0,F.jsxs)(`select`,{id:`model-reasoning-summary`,disabled:o.provider===`chat_completions`&&o.dialect===`standard`||o.provider===`anthropic_messages`&&(o.thinkingMode===`provider_default`||o.thinkingMode===`disabled`)&&!Ie(o),value:o.reasoningSummary,onChange:e=>v({reasoningSummary:e.target.value}),children:[(0,F.jsx)(`option`,{value:`provider_default`,children:D(`Provider 默认`)}),(0,F.jsx)(`option`,{value:`auto`,children:`auto`}),o.provider===`openai_responses`?(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(`option`,{value:`concise`,children:`concise`}),(0,F.jsx)(`option`,{value:`detailed`,children:`detailed`})]}):null,(0,F.jsx)(`option`,{value:`none`,children:`none`})]})]})]}),o.provider===`anthropic_messages`&&o.thinkingMode===`enabled`?(0,F.jsxs)(`label`,{htmlFor:`model-thinking-budget`,children:[D(`Thinking budget tokens`),(0,F.jsx)(`input`,{id:`model-thinking-budget`,min:1024,max:Math.max(1024,Number(o.maxOutputTokens)-1),required:!0,step:1,type:`number`,value:o.thinkingBudgetTokens,onChange:e=>v({thinkingBudgetTokens:e.target.value})})]}):null,(0,F.jsxs)(`details`,{className:`parameter-help`,children:[(0,F.jsx)(`summary`,{children:D(`参数说明`)}),(0,F.jsx)(`p`,{className:`field-note`,children:D(`Effort 控制整份响应投入;Anthropic Thinking 独立控制思考块,手动预算必须小于最大输出。Thinking 返回内容只进入模型诊断;原生续传块仍会原样保存。`)})]}),(0,F.jsx)(`label`,{htmlFor:`model-base-url`,children:`Base URL`}),(0,F.jsx)(`input`,{id:`model-base-url`,"aria-label":`Base URL`,required:!0,type:`url`,value:o.baseUrl,onChange:e=>v({presetId:`custom`,baseUrl:e.target.value},!0)}),g?.id!==`custom`&&(0,F.jsxs)(`p`,{className:`field-note`,children:[D(`已填入`),g?.name,` `,D(`的内置端点;手动修改后按自定义端点保存。`)]}),(0,F.jsx)(`label`,{htmlFor:`model-api-key`,children:`API Key`}),(0,F.jsx)(`input`,{autoComplete:`off`,id:`model-api-key`,"aria-label":`API Key`,required:_,type:`password`,value:o.apiKey,placeholder:o.connectionId===null?D(`新配置必填`):D(`留空以沿用本机保存的现有凭据`),onChange:e=>v({apiKey:e.target.value},!0)}),(0,F.jsx)(`p`,{className:`field-note`,children:D(`编辑已有配置时,API Key 留空会把本机保存的现有凭据用于当前协议和端点;填写新值会覆盖它。`)}),(0,F.jsxs)(`div`,{className:`model-picker-row`,children:[(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`label`,{htmlFor:`model-id`,children:D(`模型 ID`)}),(0,F.jsx)(`input`,{id:`model-id`,"aria-label":D(`模型 ID`),maxLength:512,required:!0,value:o.modelId,onChange:e=>y(e.target.value)})]}),(0,F.jsx)(`button`,{className:`secondary-button`,disabled:d!==null||o.baseUrl.length===0||_&&o.apiKey.length===0,onClick:()=>void O(),type:`button`,children:D(d===`models`?`正在拉取…`:`从端点拉取模型`)})]}),c.length>0&&(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`label`,{htmlFor:`fetched-model-id`,children:D(`已拉取的模型`)}),(0,F.jsxs)(`select`,{id:`fetched-model-id`,value:c.includes(o.modelId)?o.modelId:``,disabled:d!==null,onChange:e=>y(e.target.value),children:[(0,F.jsx)(`option`,{value:``,disabled:!0,children:D(`选择模型`)}),c.map(e=>(0,F.jsx)(`option`,{value:e,children:e},e))]}),(0,F.jsx)(`p`,{className:`field-note`,children:D(`选择后会填入模型 ID,也可以手动输入。`)})]}),(0,F.jsxs)(`div`,{className:`two-column-fields`,children:[(0,F.jsxs)(`label`,{children:[D(`真实 context window`),(0,F.jsx)(`input`,{min:4096,max:16777216,required:!0,step:1,type:`number`,value:o.contextWindowTokens,onChange:e=>v({contextWindowTokens:e.target.value})})]}),(0,F.jsxs)(`label`,{children:[D(`最大输出 tokens`),(0,F.jsx)(`input`,{min:256,max:16777215,required:!0,step:1,type:`number`,value:o.maxOutputTokens,onChange:e=>v({maxOutputTokens:e.target.value})})]})]}),(0,F.jsxs)(`div`,{className:`form-actions`,children:[(0,F.jsx)(`button`,{disabled:d!==null,type:`submit`,children:D(d===`save`?`正在保存…`:`保存模型连接并启用`)}),p&&o.connectionId!==null&&(0,F.jsx)(`button`,{className:`secondary-button`,disabled:d!==null,onClick:()=>{let e=t.connections.find(({id:e})=>e===o.connectionId);e!==void 0&&x(e)},type:`button`,children:D(`放弃修改`)}),p&&o.connectionId===null&&(0,F.jsx)(`button`,{className:`secondary-button`,disabled:d!==null,onClick:S,type:`button`,children:D(`重置新配置`)})]})]}),(0,F.jsxs)(`section`,{className:`config-list`,"aria-label":D(`已保存模型配置`),children:[(0,F.jsxs)(`div`,{className:`section-heading-row`,children:[(0,F.jsx)(`h3`,{children:D(`已保存配置`)}),(0,F.jsxs)(`span`,{children:[t.connections.length,` `,D(`份`)]})]}),t.connections.length===0&&(0,F.jsx)(`p`,{className:`panel-card`,children:D(`尚未保存模型配置。`)}),t.connections.map(e=>{let n=e.id===t.activeConnectionId;return(0,F.jsxs)(`article`,{className:`config-card panel-card${n?` active-model-config`:``}`,children:[(0,F.jsxs)(`div`,{className:`section-heading-row`,children:[(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`p`,{className:`eyebrow`,children:je(e.provider)}),(0,F.jsx)(`h3`,{children:e.name})]}),n&&(0,F.jsx)(`span`,{className:`active-config`,children:D(`当前使用`)})]}),(0,F.jsxs)(`details`,{className:`model-connection-details`,children:[(0,F.jsx)(`summary`,{children:e.modelId}),(0,F.jsxs)(`dl`,{children:[(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`dt`,{children:D(`模型`)}),(0,F.jsx)(`dd`,{children:e.modelId})]}),(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`dt`,{children:D(`端点`)}),(0,F.jsx)(`dd`,{children:e.baseUrl})]}),(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`dt`,{children:D(`窗口 / 输出`)}),(0,F.jsxs)(`dd`,{children:[e.contextWindowTokens.toLocaleString(),` /`,` `,e.maxOutputTokens.toLocaleString()]})]}),(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`dt`,{children:D(`方言 / Effort / Thinking`)}),(0,F.jsxs)(`dd`,{children:[e.dialect,` / `,e.reasoningEffort,` /`,` `,e.thinkingMode,e.thinkingBudgetTokens===null?``:` (${e.thinkingBudgetTokens.toLocaleString()} tokens)`,` `,`/ `,e.reasoningSummary]})]}),(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`dt`,{children:D(`凭据`)}),(0,F.jsx)(`dd`,{children:e.hasApiKey?D(`已保存在本机`):D(`未配置`)})]})]})]}),(0,F.jsxs)(`div`,{className:`form-actions`,children:[!n&&(0,F.jsx)(`button`,{disabled:d!==null,onClick:()=>void w(e.id),type:`button`,children:d===e.id?D(`正在切换…`):D(`切换到此配置`)}),(0,F.jsx)(`button`,{className:`secondary-button`,disabled:d!==null||p,onClick:()=>void T(e),type:`button`,children:d===`copy:${e.id}`?D(`正在克隆…`):D(`克隆配置`)}),(0,F.jsx)(`button`,{className:`secondary-button`,disabled:d!==null||p||o.connectionId===e.id,onClick:()=>x(e),type:`button`,children:o.connectionId===e.id?D(`正在编辑`):D(`编辑`)}),(0,F.jsx)(`button`,{className:`danger-button`,disabled:d!==null||p||n&&t.connections.length>1,onClick:()=>void E(e.id),type:`button`,children:d===`delete:${e.id}`?D(`正在删除…`):D(`删除`)})]}),n&&t.connections.length>1&&(0,F.jsx)(`p`,{className:`field-note`,children:D(`先切换到另一份配置,才能删除当前配置。`)})]},e.id)})]})]})]})}function je(e){switch(e){case`chat_completions`:return`Chat Completions`;case`openai_responses`:return`OpenAI Responses`;case`anthropic_messages`:return`Anthropic Messages`}}function Me(e){return{connectionId:e.id,name:e.name,presetId:e.presetId,provider:e.provider,dialect:e.dialect,baseUrl:e.baseUrl,apiKey:``,modelId:e.modelId,reasoningEffort:e.reasoningEffort,reasoningSummary:e.reasoningSummary,thinkingMode:e.thinkingMode,thinkingBudgetTokens:e.thinkingBudgetTokens===null?``:String(e.thinkingBudgetTokens),contextWindowTokens:String(e.contextWindowTokens),maxOutputTokens:String(e.maxOutputTokens)}}function Ne(e){return e instanceof Error?e.message:D(`模型配置操作失败`)}function Pe(e,t){let n=new Set(t.map(({name:e})=>e));for(let r=1;r<=t.length+1;r+=1){let t=r===1?D(`(副本)`):D(`(副本 {number})`,{number:r}),i=`${e.slice(0,Math.max(0,160-t.length))}${t}`;if(!n.has(i))return i}throw Error(D(`无法生成不重复的副本名称`))}function Fe(e,t,n){let r=t===`anthropic_messages`&&(e.reasoningEffort===`none`||e.reasoningEffort===`minimal`)?`provider_default`:e.reasoningEffort,i=t===`anthropic_messages`&&e.provider===`anthropic_messages`?e.thinkingMode:`provider_default`,a=Ie({...e,provider:t,dialect:n,thinkingMode:i});return{reasoningEffort:r,reasoningSummary:t!==`openai_responses`&&(e.reasoningSummary===`concise`||e.reasoningSummary===`detailed`)||t===`chat_completions`&&n===`standard`||t===`anthropic_messages`&&(i===`provider_default`||i===`disabled`)&&!a?`provider_default`:e.reasoningSummary,thinkingMode:i,thinkingBudgetTokens:i===`enabled`?e.thinkingBudgetTokens:``}}function Ie(e){return e.provider===`anthropic_messages`&&e.dialect===`cliproxyapi`&&e.thinkingMode===`provider_default`&&Oe(e.modelId)}function Le(e){let t=Number(e);return!Number.isSafeInteger(t)||t<=1024?`1024`:String(Math.min(8192,t-1))}function Re(){if(typeof crypto.randomUUID==`function`)return crypto.randomUUID();let e=crypto.getRandomValues(new Uint8Array(16));e[6]=e[6]&15|64,e[8]=e[8]&63|128;let t=Array.from(e,e=>e.toString(16).padStart(2,`0`));return[t.slice(0,4).join(``),t.slice(4,6).join(``),t.slice(6,8).join(``),t.slice(8,10).join(``),t.slice(10).join(``)].join(`-`)}function ze(e){return[...e.map(({id:e})=>({id:e,kind:`user`,enabled:!0})),{id:`builtin:summary`,kind:`builtin`,enabled:!1},{id:`package:followups`,kind:`content-package`,enabled:!0}]}function Be(e){let t=e===`zh-CN`;return{body:t?`主叙事已完成。仅根据已向玩家呈现的叙事与明确可见事实整理简短场景回顾,通过 artifact_emit 输出 recap。不要从工具材料、未表露认知或场外状态补全幕后原因,不补写剧情或引入新事实。回顾只是显示产物,不替代状态维护、检查点或历史恢复。`:`The main narrative has settled. Emit a brief scene recap as recap using artifact_emit, using only narrative already shown to the player and explicitly visible facts. Do not fill in backstage causes from tool material, unexpressed knowledge or offstage state. Do not continue the story or introduce new facts. This display artifact does not replace state maintenance, checkpoints or history recovery.`,definition:{id:`builtin:summary`,displayName:t?`场景回顾(系统示例)`:`Scene recap (system example)`,prompt:{role:`author_instruction`,path:`builtin:followup-summary`},artifacts:[{name:`recap`,channel:`builtin:summary`,strategy:`replace`,contentType:`text/markdown`,rendererMode:`document`,save:`commit`,invalidation:`explicit_clear`,required:!0,maxEmits:1}],maxArtifactBytes:32768}}}function Ve(e,t){if(t.type===`delete-resource`){let n=Ue(e).get(t.path)??{declared:!1,sources:[]};if(n.declared||n.sources.length)throw Error(`Resource is referenced: ${t.path}${n.sources.length?` (${n.sources.join(`, `)})`:``}`);let r=structuredClone(e);return delete r.files[t.path],r.structure.extensionRefs=r.structure.extensionRefs.filter(e=>e!==t.path),r}if((t.type===`create-followup`||t.type===`clone-followup`)&&(e.structure.followups.some(e=>e.id===t.id)||e.structure.followupItems?.some(e=>e.id===t.id)||e.files[`prompts/${t.id}.md`]!==void 0))throw Error(`Request or prompt already exists: ${t.id}`);let n=structuredClone(e);if(t.type===`create-panel`)return n.structure.playerViewPanels.push({id:t.id,source:{kind:`player_view`,view:`status`},channel:`player.view.${t.id}`,key:`current`,mount:`sidebar`,rendererMode:`document`,config:{title:t.title,layout:`stack`,theme:`default`,empty:`message`,emptyMessage:t.emptyMessage,groups:[]}}),n;if(t.type===`remove-panel`)return n.structure.playerViewPanels=n.structure.playerViewPanels.filter(e=>e.id!==t.id),n;if(t.type===`remove-followup`||t.type===`remove-artifact`){let e=t.type===`remove-followup`?t.id:t.requestId,r=n.structure.followups.find(t=>t.id===e);if(!r)throw Error(`Request does not exist: ${e}`);let i=t.type===`remove-followup`?r.artifacts:r.artifacts.filter(e=>e.name===t.output),a=new Set(i.map(e=>e.channel));t.type===`remove-followup`?(n.structure.followupItems=(n.structure.followupItems??ze(n.structure.followups)).filter(t=>t.id!==e),n.structure.followups=n.structure.followups.filter(t=>t.id!==e)):r.artifacts=r.artifacts.filter(e=>e.name!==t.output);let o=new Set([...n.structure.followups.flatMap(e=>e.artifacts.map(e=>e.channel)),...n.structure.playerViewPanels.map(e=>e.channel)]);return n.structure.mounts=n.structure.mounts.filter(e=>!a.has(e.channel)||o.has(e.channel)),n}if(t.type===`create-followup`){let e=`prompts/${t.id}.md`,r=qe(`${t.id}.output_1`,`output_1`);return n.files[e]=t.body,n.structure.followupItems=[...n.structure.followupItems??ze(n.structure.followups),{id:t.id,kind:`user`,enabled:!0}],n.structure.followups.push({id:t.id,displayName:t.displayName,prompt:{role:`author_instruction`,path:e},artifacts:[r],maxArtifactBytes:32768}),n.structure.mounts.push({channel:r.channel,mount:`story`}),n}if(t.type===`add-artifact`){let e=n.structure.followups.find(e=>e.id===t.requestId);if(!e)throw Error(`Request does not exist: ${t.requestId}`);for(let[e,r]of Object.entries(t.files??{})){if(n.files[e]!==void 0)throw Error(`Resource already exists: ${e}`);n.files[e]=r}let r=We(t.artifact);for(let e of r)if(n.files[e]===void 0)throw Error(`Resource does not exist: ${e}`);return e.artifacts.push(structuredClone(t.artifact)),n.structure.mounts.some(e=>e.channel===t.artifact.channel)||n.structure.mounts.push({channel:t.artifact.channel,mount:t.mount??`story`}),n.structure.extensionRefs=[...new Set([...n.structure.extensionRefs,...r,...Object.keys(t.files??{})])],n}if(t.type===`clone-followup`){let r=t.source===`builtin:summary`?Be(E()):void 0,i=r?.definition??n.structure.followups.find(e=>e.id===t.source);if(!i)throw Error(`Clone source does not exist`);let a=structuredClone(i);a.id=t.id,a.displayName+=E()===`zh-CN`?` 副本`:` copy`;let o=new Map,s=r?.body??e.files[i.prompt.path];if(s===void 0)throw Error(`Resource does not exist: ${i.prompt.path}`);let c=[...i.artifacts.flatMap(We),...Ge(s)];for(let t of c){if(o.has(t))continue;if(e.files[t]===void 0)throw Error(`Resource does not exist: ${t}`);let n=t.lastIndexOf(`/`);o.set(t,`${t.slice(0,n)}/${Re()}-${t.slice(n+1)}`),c.push(...Ge(e.files[t]))}let l=e=>e.replace(Ke(),e=>o.get(e)??e);for(let[t,r]of o)n.files[r]=l(e.files[t]);a.prompt.path=`prompts/${t.id}.md`,n.files[a.prompt.path]=l(s);let u=new Map;return a.artifacts=a.artifacts.map(e=>{let n={...e},r=u.get(e.channel);return r||(r=`${t.id}.output_${u.size+1}`,u.set(e.channel,r)),n.channel=r,e.renderer&&(n.renderer=o.get(e.renderer)),e.regex&&(n.regex=o.get(e.regex)),e.scripts&&(n.scripts=e.scripts.map(e=>o.get(e))),e.assets&&(n.assets=e.assets.map(e=>o.get(e))),n}),n.structure.followupItems=[...n.structure.followupItems??ze(n.structure.followups),{id:a.id,kind:`user`,enabled:!0}],n.structure.followups.push(a),n.structure.mounts.push(...(r?i.artifacts.map(e=>({channel:e.channel,mount:`story`})):e.structure.mounts).filter(e=>u.has(e.channel)).map(e=>({...e,channel:u.get(e.channel)}))),n.structure.extensionRefs=[...new Set([...n.structure.extensionRefs,...o.values()])],n}let r=t.target,i=r.kind===`panel`?n.structure.playerViewPanels.find(e=>e.id===r.id):n.structure.followups.find(e=>e.id===r.requestId)?.artifacts.find(e=>e.name===r.output);if(!i)throw Error(`Display declaration does not exist`);let a=t.edit;if(a.type===`mode`)return i.rendererMode=a.mode,n;let{kind:o}=a;if(a.type===`unbind`)return o===`assets`||o===`scripts`?i[o]=(i[o]??[]).filter(e=>e!==a.path):i[o]===a.path&&(delete i[o],o===`renderer`&&(delete i.rendererRevision,i.rendererMode=`document`)),n;let s=a.type===`create`?He(a):a.path;if(a.type===`create`&&(n.files[s]=a.body),n.files[s]===void 0)throw Error(`Resource does not exist: ${s}`);return o===`assets`||o===`scripts`?i[o]=[...new Set([...i[o]??[],s])]:(i[o]=s,o===`renderer`&&(i.rendererRevision=Re())),n.structure.extensionRefs=[...new Set([...n.structure.extensionRefs,s])],n}function He(e){return`${{renderer:`renderers`,regex:`regex`,scripts:`scripts`,assets:`assets`}[e.kind]}/${e.name.trim()?`named-`+Array.from(e.name.trim().slice(0,24)).map(e=>/[A-Za-z0-9.-]/u.test(e)?e:`_u${e.codePointAt(0).toString(16)}_`).join(``):`resource`}.${Re()}.${e.suffix}`}function Ue(e){let t=new Map;function n(e){let n=t.get(e);return n||(n={declared:!1,sources:[]},t.set(e,n)),n}let r=e.structure,i=[r.settingImprovementPrompt?.path,...r.narrativePrompts.map(e=>e.path),...r.followups.flatMap(e=>[e.prompt.path,...e.artifacts.flatMap(We)]),...r.playerViewPanels.flatMap(We)];for(let e of i)e!==void 0&&(n(e).declared=!0);for(let[t,i]of Object.entries(e.files))if(t!==`preset.yaml`&&t!==r.callChainPath)for(let e of new Set(Ge(i)))e!==t&&n(e).sources.push(t);return t}function We(e){return[e.renderer,e.regex,...e.scripts??[],...e.assets??[]].filter(e=>e!==void 0)}function Ge(e){return[...e.matchAll(Ke())].map(e=>e[0])}function Ke(){return/(?:(?<![A-Za-z0-9_./:@%?=#\\-])|(?<=\s[A-Za-z_:][A-Za-z0-9_:.-]*[\t\n\f\r ]*=))(?:renderers\/[A-Za-z0-9][A-Za-z0-9._-]*\.html|regex\/[A-Za-z0-9][A-Za-z0-9._-]*\.yaml|scripts\/[A-Za-z0-9][A-Za-z0-9._-]*\.js|assets\/[A-Za-z0-9][A-Za-z0-9._/-]*)(?![A-Za-z0-9_./$?%#=\\-])/gu}function qe(e,t){return{name:t,displayName:E()===`zh-CN`?`新产物`:`New output`,channel:e,strategy:`replace`,contentType:`text/markdown`,rendererMode:`document`,save:`commit`,invalidation:`explicit_clear`,required:!1,maxEmits:1}}function Je(e){let t=e.split(`/`).at(-1)??e,n=t.replace(/^(?:[a-f0-9]{8}-[a-f0-9-]{27}-)+/u,``).replace(/\.[a-f0-9]{8}-[a-f0-9-]{27}(?=\.)/gu,``);return n!==t&&n.startsWith(`named-`)?n.slice(6).replace(/_u([a-f0-9]+)_/gu,(e,t)=>Number.parseInt(t,16)<=1114111?String.fromCodePoint(Number.parseInt(t,16)):`_u${t}_`):n}function Ye(e,t){let n=`example_${Re().replaceAll(`-`,``)}`,r=E()===`zh-CN`,i=`renderers/${n}.html`,a=`scripts/${n}.js`,o=`assets/${n}.css`,s=e===`actions`?[{label:r?`询问船夫`:`Ask the ferryman`,prompt:r?`我问船夫:这封信是谁交给你的?`:`I ask the ferryman who gave him the letter.`}]:{title:r?`雾港`:`Mist harbour`,body:r?`灯塔在暮色中亮起。`:`The lighthouse shines at dusk.`};return{artifact:{name:n,displayName:e===`actions`?r?`行动建议按钮`:`Action buttons`:r?`结构化场景卡片`:`Scene card`,purpose:e===`actions`?r?`提供下一步行动草稿,不自动发送。`:`Offer draft actions without sending them.`:r?`回顾已发生的场景。`:`Recap the established scene.`,channel:`${t}.${n}`,strategy:`replace`,contentType:`application/json`,renderer:i,rendererRevision:`v1`,rendererMode:`app`,scripts:[a],assets:[o],save:`commit`,invalidation:`new_operation`,required:!1,maxEmits:1,payloadContract:e===`actions`?{type:`array`,minItems:1,maxItems:6,items:{type:`object`,additionalProperties:!1,required:[`label`,`prompt`],properties:{label:{type:`string`,minLength:1,maxLength:80},prompt:{type:`string`,minLength:1,maxLength:1e3}}}}:{type:`object`,additionalProperties:!1,required:[`title`,`body`],properties:{title:{type:`string`,minLength:1,maxLength:80},body:{type:`string`,maxLength:4e3}}}},files:{[i]:`<!doctype html><html><body><main id="content"></main><p id="feedback" role="status"></p></body></html>`,[o]:`body { margin: 0; padding: 16px; color: #27333c; background: #f7f4eb; font: 14px/1.7 system-ui; } #content { display: grid; gap: 8px; } button { padding: 12px; border: 1px solid #b9ad95; background: white; text-align: left; cursor: pointer; } button:disabled { opacity: .5; cursor: default; }`,[a]:`// request and fromHost are helpers defined by this example, not a built-in SDK.
1047
+ `}}V(`en`);function ge(e){return[{id:`action-choices`,label:e===`zh-CN`?`行动选项`:`Action choices`,name:e===`zh-CN`?`下一步建议(可编辑副本)`:`Next-step ideas (editable copy)`,files:me(e)},{id:`status-panel`,label:e===`zh-CN`?`状态栏`:`Status panel`,name:e===`zh-CN`?`状态栏(可编辑副本)`:`Status panel (editable copy)`,files:V(e)},{id:`generic-panels`,label:e===`zh-CN`?`Markdown / HTML 面板`:`Markdown / HTML panels`,name:e===`zh-CN`?`通用面板(可编辑副本)`:`Generic panels (editable copy)`,files:he(e)},{id:`artifact-debugger`,label:e===`zh-CN`?`产物调试器`:`Artifact debugger`,name:e===`zh-CN`?`产物调试器(可编辑副本)`:`Artifact debugger (editable copy)`,files:he(e)}]}ge(`en`);var _e=[[`home`,`工作区`],[`content`,`内容编辑`],[`plays`,`预设`],[`create`,`新建世界`],[`preview`,`提示词预览`]];function ve({screen:e,locale:t,localeSaving:n,navigationLocked:r,activeModelName:i,onNavigate:a,onLocaleChange:o}){let[s,c]=(0,l.useState)(!1);function u(e){r||(c(!1),a(e))}return(0,F.jsxs)(`header`,{className:`workspace-header`,children:[(0,F.jsx)(`button`,{type:`button`,className:`workspace-brand`,"aria-label":D(`返回工作区`),disabled:r,onClick:()=>u(`home`),children:`Narraeon`}),(0,F.jsx)(`nav`,{id:`workspace-navigation`,className:s?`workspace-navigation is-open`:`workspace-navigation`,"aria-label":D(`工作区导航`),onKeyDown:e=>{e.key===`Escape`&&(c(!1),document.getElementById(`workspace-menu-toggle`)?.focus())},children:_e.map(([t,n])=>(0,F.jsx)(`button`,{type:`button`,"aria-current":e===t?`page`:void 0,disabled:r,onClick:()=>u(t),children:D(n)},t))}),(0,F.jsxs)(`div`,{className:`workspace-header-actions`,children:[(0,F.jsxs)(`label`,{className:`workspace-locale-picker`,children:[(0,F.jsx)(`span`,{children:D(`界面语言`)}),(0,F.jsxs)(`select`,{"aria-label":D(`界面语言`),value:t,disabled:n||r,onChange:e=>o(e.currentTarget.value),children:[(0,F.jsx)(`option`,{value:`en`,children:`English`}),(0,F.jsx)(`option`,{value:`zh-CN`,children:D(`简体中文`)})]})]}),(0,F.jsxs)(`button`,{type:`button`,className:`workspace-model-button`,"aria-label":D(`模型连接`),"aria-current":e===`model`?`page`:void 0,title:i??D(`尚未配置`),disabled:r,onClick:()=>u(`model`),children:[(0,F.jsx)(`span`,{className:`workspace-model-dot ${i===null?`needs-attention`:`is-ready`}`,"aria-hidden":`true`}),D(`模型连接`)]}),(0,F.jsx)(`button`,{id:`workspace-menu-toggle`,type:`button`,className:`workspace-menu-toggle`,"aria-label":D(`切换页面`),"aria-expanded":s,"aria-controls":`workspace-navigation`,disabled:r,onClick:()=>c(!s),children:`☰`})]})]})}function ye(){return[{scheme:`d100`,label:`d100`,description:D(`默认方案。掷一颗个位骰,可加奖励骰或惩罚骰;结果不高于目标值即成功。`)},{scheme:`d20`,label:`d20`,description:D(`掷一颗 d20 加修正,总值不低于难度即成功;可掷优势或劣势。`)},{scheme:`bag`,label:D(`混乱袋`),description:D(`从混乱袋抽一枚标记;数值标记加修正后与难度比较,特殊标记直接决定大成功或大失败。`)}]}function be({pending:e,onCancel:t,onCreate:n}){let r=(0,l.useRef)(null),i=(0,l.useId)(),[a,o]=(0,l.useState)(`interactive-fiction`),[s,c]=(0,l.useState)(`d100`),[u,d]=(0,l.useState)(``);(0,l.useEffect)(()=>{let e=r.current;return e!==null&&!e.open&&e.showModal(),()=>{e?.open===!0&&e.close()}},[]);async function f(){if(!e){d(``);try{await n(a===`tabletop`?{mode:`tabletop`,scheme:s}:{mode:`interactive-fiction`})}catch(e){d(e instanceof Error?e.message:D(`内容包创建失败`))}}}return(0,F.jsx)(`dialog`,{ref:r,className:`create-package-dialog`,"aria-labelledby":`${i}-title`,onCancel:n=>{n.preventDefault(),e||t()},children:(0,F.jsxs)(`form`,{className:`create-package-form`,onSubmit:e=>{e.preventDefault(),f()},children:[(0,F.jsxs)(`header`,{children:[(0,F.jsx)(`h2`,{id:`${i}-title`,children:D(`新建内容包`)}),(0,F.jsx)(`p`,{children:D(`内容包的玩法在创建时确定,之后不能切换。`)})]}),(0,F.jsxs)(`fieldset`,{className:`create-package-choices`,disabled:e,children:[(0,F.jsx)(`legend`,{children:D(`玩法`)}),(0,F.jsxs)(`label`,{className:`create-package-choice`,children:[(0,F.jsx)(`input`,{type:`radio`,name:`${i}-mode`,value:`interactive-fiction`,checked:a===`interactive-fiction`,onChange:()=>o(`interactive-fiction`)}),(0,F.jsx)(`span`,{children:D(`互动式小说`)})]}),(0,F.jsxs)(`label`,{className:`create-package-choice`,children:[(0,F.jsx)(`input`,{type:`radio`,name:`${i}-mode`,value:`tabletop`,checked:a===`tabletop`,onChange:()=>o(`tabletop`)}),(0,F.jsx)(`span`,{children:D(`跑团`)})]})]}),a===`tabletop`?(0,F.jsxs)(`fieldset`,{className:`create-package-choices`,disabled:e,children:[(0,F.jsx)(`legend`,{children:D(`判定方案`)}),ye().map(e=>(0,F.jsxs)(`div`,{className:`create-package-choice-block`,children:[(0,F.jsxs)(`label`,{className:`create-package-choice`,children:[(0,F.jsx)(`input`,{type:`radio`,name:`${i}-scheme`,value:e.scheme,checked:s===e.scheme,"aria-describedby":`${i}-${e.scheme}-description`,onChange:()=>c(e.scheme)}),(0,F.jsx)(`span`,{children:e.label})]}),(0,F.jsx)(`p`,{className:`field-note`,id:`${i}-${e.scheme}-description`,children:e.description})]},e.scheme))]}):null,u===``?null:(0,F.jsx)(`p`,{className:`create-package-failure`,role:`alert`,children:u}),e?(0,F.jsx)(`p`,{role:`status`,children:D(`正在创建内容包…`)}):null,(0,F.jsxs)(`footer`,{className:`button-row`,children:[(0,F.jsx)(`button`,{type:`button`,className:`secondary-button`,disabled:e,onClick:t,children:D(`取消`)}),(0,F.jsx)(`button`,{type:`submit`,disabled:e,children:D(`创建`)})]})]})})}function xe(e,t){return typeof e==`string`&&e.trim().length>0&&[...e].length<=80&&!/[\r\n\u0085\u2028\u2029]/u.test(e)&&typeof t==`string`&&t.trim().length>0&&new TextEncoder().encode(t).byteLength<=16384}function Se({client:e,templates:t,onChange:n,onClose:r,onNavigationLockChange:i}){let[a,o]=(0,l.useState)(null),[s,c]=(0,l.useState)(``),[u,d]=(0,l.useState)(``),[f,p]=(0,l.useState)(!1),m=(0,l.useRef)(!1),[h,g]=(0,l.useState)(``),_=s!==(a?.name??``)||u!==(a?.body??``);(0,l.useEffect)(()=>(i?.(_||f),()=>i?.(!1)),[_,f,i]);function v(){return!m.current&&(!_||globalThis.confirm(D(`放弃未保存的主角模板修改?`)))}function y(e){o(e),c(e?.name??``),d(e?.body??``),g(``)}async function b(t){if(!m.current){m.current=!0,p(!0),g(``);try{let r=await e.request(t);n(r),y(t.type===`protagonist-templates.delete`?null:t.type===`protagonist-templates.save`&&t.id!==void 0?r.templates.find(({id:e})=>e===t.id)??null:r.templates.at(-1)??null)}catch(e){g(e instanceof Error?e.message:D(`主角模板保存失败`))}finally{m.current=!1,p(!1)}}}return(0,F.jsx)(`div`,{className:`protagonist-template-scrim`,children:(0,F.jsxs)(`section`,{className:`protagonist-template-manager`,role:`dialog`,"aria-modal":`true`,"aria-labelledby":`protagonist-templates-title`,children:[(0,F.jsxs)(`header`,{children:[(0,F.jsx)(`h2`,{id:`protagonist-templates-title`,children:D(`管理主角模板`)}),(0,F.jsx)(`button`,{type:`button`,disabled:f,onClick:()=>{v()&&r()},children:D(`关闭`)})]}),(0,F.jsxs)(`div`,{className:`protagonist-template-columns`,children:[(0,F.jsxs)(`nav`,{"aria-label":D(`主角模板列表`),children:[(0,F.jsx)(`button`,{type:`button`,disabled:f,onClick:()=>{v()&&y(null)},children:D(`新建主角模板`)}),t.map(e=>(0,F.jsx)(`button`,{type:`button`,disabled:f,"aria-pressed":a?.id===e.id,onClick:()=>{v()&&y(e)},children:e.name},e.id))]}),(0,F.jsxs)(`form`,{onSubmit:e=>{e.preventDefault(),xe(s,u)&&b({type:`protagonist-templates.save`,...a===null?{}:{id:a.id},name:s,body:u})},children:[(0,F.jsxs)(`label`,{children:[D(`模板名称`),(0,F.jsx)(`input`,{"aria-label":D(`模板名称`),value:s,disabled:f,onChange:e=>c(e.currentTarget.value)})]}),(0,F.jsxs)(`label`,{children:[D(`模板正文`),(0,F.jsx)(`textarea`,{"aria-label":D(`模板正文`),value:u,disabled:f,onChange:e=>d(e.currentTarget.value)})]}),(0,F.jsx)(`small`,{children:D(`名称为 1–80 个字符;正文为自由 Markdown,最多 16 KiB。`)}),h&&(0,F.jsx)(`p`,{role:`alert`,children:h}),(0,F.jsxs)(`div`,{className:`button-row`,children:[(0,F.jsx)(`button`,{className:`primary-button`,type:`submit`,disabled:f||!_||!xe(s,u),children:D(`保存`)}),(0,F.jsx)(`button`,{type:`button`,disabled:f||a===null,onClick:()=>{a!==null&&v()&&b({type:`protagonist-templates.copy`,id:a.id})},children:D(`复制`)}),(0,F.jsx)(`button`,{type:`button`,className:`danger-button`,disabled:f||a===null,onClick:()=>{a!==null&&!m.current&&globalThis.confirm(D(`删除这个主角模板?`))&&b({type:`protagonist-templates.delete`,id:a.id})},children:D(`删除`)})]})]})]})]})})}function Ce({client:e,packages:t,selectedId:n,pending:r,modelConfigured:i,onSelect:a,onCreate:o,onEdit:s,onConfigureModel:c,onNavigationLockChange:u}){let[d,f]=(0,l.useState)([]),[p,m]=(0,l.useState)(``),[g,_]=(0,l.useState)(!0),[v,y]=(0,l.useState)(``),[b,x]=(0,l.useState)(0),[S,C]=(0,l.useState)(!1);(0,l.useEffect)(()=>{let t=!0;return e.request({type:`protagonist-templates.read`}).then(e=>{t&&(f(e.templates),y(``))}).catch(e=>{t&&y(e instanceof Error?e.message:D(`主角模板读取失败`))}).finally(()=>{t&&_(!1)}),()=>{t=!1}},[e,b]);let w=t.find(({localId:e})=>e===n),[T,O]=(0,l.useState)(null);return(0,l.useEffect)(()=>{let t=!0;return n!==``&&e.request({type:`content.read`,packageId:n}).then(({files:e})=>{t&&O({packageId:n,failure:``,opening:e.find(({path:e})=>e===`opening.md`)?.contents??``})}).catch(e=>{t&&O({packageId:n,opening:``,failure:e instanceof Error?e.message:D(`内容包读取失败`)})}),()=>{t=!1}},[e,n]),(0,F.jsxs)(`main`,{className:`create-world-screen`,children:[(0,F.jsxs)(`aside`,{children:[(0,F.jsx)(`h1`,{children:D(`新建世界`)}),(0,F.jsx)(`p`,{children:D(`选择一份内容包,从它的开场开始。`)}),t.length===0?(0,F.jsx)(`p`,{children:D(`先新建或导入一份内容包。`)}):(0,F.jsxs)(`label`,{children:[D(`内容包`),(0,F.jsx)(`select`,{"aria-label":D(`创建世界的内容包`),value:n,disabled:r,onChange:e=>a(e.currentTarget.value),children:t.map(e=>(0,F.jsxs)(`option`,{value:e.localId,children:[`${e.title} · ${h(e.gameplay,E())}`,e.status===`needs_repair`?D(` · 需要修复`):``]},e.localId))})]}),(0,F.jsxs)(`label`,{children:[D(`主角模板`),(0,F.jsxs)(`select`,{"aria-label":D(`主角模板`),value:p,disabled:r||g,onChange:e=>m(e.currentTarget.value),children:[(0,F.jsx)(`option`,{value:``,children:D(`不使用`)}),d.map(e=>(0,F.jsx)(`option`,{value:e.id,children:e.name},e.id))]})]}),(0,F.jsx)(`button`,{type:`button`,disabled:r||g||v!==``,onClick:()=>C(!0),children:D(`管理主角模板`)}),v&&(0,F.jsxs)(`p`,{role:`alert`,children:[v,(0,F.jsx)(`button`,{type:`button`,disabled:r,onClick:()=>{_(!0),x(e=>e+1)},children:D(`重新读取主角模板`)})]}),w?.status===`needs_repair`&&(0,F.jsx)(`p`,{role:`status`,children:D(`内容包需要修复后才能创建世界。`)}),!i&&(0,F.jsx)(`button`,{type:`button`,disabled:r,onClick:c,children:D(`配置模型连接`)}),(0,F.jsx)(`button`,{type:`button`,className:`primary-button`,disabled:r||!i||w?.status!==`usable`,onClick:()=>o(d.find(({id:e})=>e===p)),children:D(r?`正在创建世界…`:`从当前内容包创建`)}),(0,F.jsx)(`button`,{type:`button`,disabled:r,onClick:s,children:D(w===void 0?`新建内容包`:`完善这份内容包`)}),(0,F.jsx)(`small`,{children:D(`新世界独立保存,之后的游玩不会修改源内容包。`)})]}),(0,F.jsxs)(`article`,{"aria-label":D(`开场白预览`),children:[(0,F.jsx)(`span`,{children:D(`开场白`)}),(0,F.jsx)(`h2`,{children:w?.title}),w===void 0?null:(0,F.jsx)(`p`,{className:`create-world-gameplay`,children:h(w.gameplay,E())}),T?.packageId===n&&T.failure?(0,F.jsx)(`p`,{role:`alert`,children:T.failure}):T?.packageId===n?(0,F.jsx)(`p`,{className:`create-world-opening`,children:T.opening||D(`这份内容包还没有开场白。`)}):w===void 0?null:(0,F.jsx)(`p`,{role:`status`,children:D(`正在读取开场白…`)})]}),S&&(0,F.jsx)(Se,{client:e,templates:d,onChange:e=>{f(e.templates),e.templates.some(({id:e})=>e===p)||m(``)},onClose:()=>C(!1),...u===void 0?{}:{onNavigationLockChange:u}})]})}function we({text:e,onDismiss:t,className:n=``,role:r=`status`}){return(0,F.jsxs)(`div`,{className:`dismissible-notice ${n}`,role:r,children:[(0,F.jsx)(`span`,{children:e}),(0,F.jsx)(`button`,{type:`button`,"aria-label":D(`关闭提示`),title:D(`关闭提示`),onClick:t,children:`×`})]})}function Te(e,t=Date.now()){let n=Math.max(0,Math.floor((t-e)/1e3));if(n<60)return D(`刚刚`);let r=new Intl.RelativeTimeFormat(E(),{numeric:`always`});for(let[e,t]of[[`year`,31536e3],[`month`,2592e3],[`day`,86400],[`hour`,3600],[`minute`,60]])if(n>=t)return r.format(-Math.floor(n/t),e);return D(`刚刚`)}function Ee({contentPackages:e,worlds:t,worldBackupImport:n,renderWorldBackupExport:r,selectedPackageId:i,modelConfigured:a,activeModelName:o,currentPresetName:s,importArchive:c,importPending:u,samplePending:d=!1,onImportArchiveChange:f,onOpenPlayPresets:p=()=>void 0,onCreateWorld:m,onCreatePackage:g,onCreateSamplePackage:_,onConfigureModel:v,onImportPackage:y,onOpenPackage:b,onOpenWorld:x,onRenamePackage:S,onDeletePackage:C,onRenameWorld:w,onDeleteWorld:T}){let[O,k]=(0,l.useState)(null),[A,ee]=(0,l.useState)(``),[j,te]=(0,l.useState)(null),[ne,M]=(0,l.useState)(``),N=u||d,P=e.filter(({status:e})=>e===`usable`).length,re=e.length-P;return(0,F.jsxs)(`main`,{className:`home-dashboard`,children:[(0,F.jsxs)(`header`,{className:`home-page-heading`,children:[(0,F.jsx)(`h1`,{children:D(`世界工作区`)}),(0,F.jsx)(`p`,{children:D(`选择一个世界继续故事,或从内容包开始创作。`)})]}),(0,F.jsxs)(`div`,{className:`home-library-grid`,children:[(0,F.jsxs)(`section`,{className:`home-surface home-world-library`,"aria-labelledby":`home-worlds-title`,children:[(0,F.jsxs)(`header`,{className:`home-section-heading`,children:[(0,F.jsx)(`div`,{children:(0,F.jsx)(`h2`,{id:`home-worlds-title`,children:D(`继续游玩`)})}),(0,F.jsx)(`span`,{className:`home-count`,"aria-label":D(`{count} 个世界`,{count:t.length}),children:t.length.toLocaleString(E())}),(0,F.jsx)(`button`,{type:`button`,disabled:N,onClick:m,children:D(`新建世界`)}),n]}),t.length>0?(0,F.jsx)(`div`,{className:`home-world-grid`,children:t.map(e=>(0,F.jsxs)(`div`,{className:`home-world-card-shell`,children:[(0,F.jsxs)(`button`,{className:`home-world-card`,type:`button`,"aria-label":D(`打开世界:{title}`,{title:e.title}),disabled:N,onClick:()=>x(e.worldId),children:[(0,F.jsxs)(`span`,{className:`home-world-card-story`,children:[(0,F.jsx)(`strong`,{children:e.title}),(0,F.jsx)(`small`,{className:`home-world-card-gameplay`,children:h(e.gameplay,E())}),(0,F.jsxs)(`span`,{className:`home-world-card-activity`,children:[e.pendingUpgrade===!0?(0,F.jsx)(`span`,{children:D(`待升级`)}):e.lastActivityAt==null?(0,F.jsx)(`span`,{children:D(`活动时间未记录`)}):(0,F.jsx)(`time`,{dateTime:new Date(e.lastActivityAt).toISOString(),title:new Date(e.lastActivityAt).toLocaleString(E()),children:Te(e.lastActivityAt)}),e.pendingUpgrade===!0?null:(0,F.jsx)(`span`,{children:D(e.playerTurns===1?`1 回合`:`{count} 回合`,{count:(e.playerTurns??0).toLocaleString(E())})})]}),e.excerpt==null?null:(0,F.jsx)(`span`,{className:`home-world-card-excerpt`,title:e.excerpt,children:e.excerpt})]}),(0,F.jsxs)(`span`,{className:`home-world-card-action`,children:[D(`进入世界`),(0,F.jsx)(`span`,{"aria-hidden":`true`,children:`→`})]})]}),(0,F.jsxs)(`details`,{className:`home-world-card-actions`,children:[(0,F.jsx)(`summary`,{"aria-label":D(`世界操作:{title}`,{title:e.title}),children:`•••`}),(0,F.jsxs)(`div`,{children:[r?.(e),(0,F.jsx)(`button`,{className:`home-world-rename`,type:`button`,"aria-label":D(`重命名世界:{title}`,{title:e.title}),disabled:N,onClick:()=>{k(e.worldId),ee(e.title)},children:D(`重命名`)}),(0,F.jsx)(`button`,{className:`home-world-delete`,type:`button`,"aria-label":D(`删除世界:{title}`,{title:e.title}),disabled:N,onClick:()=>T(e),children:D(`删除`)})]})]}),O===e.worldId?(0,F.jsxs)(`form`,{className:`home-world-rename-form`,onSubmit:t=>{t.preventDefault();let n=A.trim();n!==``&&n!==e.title&&(w(e,n),k(null))},children:[(0,F.jsxs)(`label`,{children:[(0,F.jsx)(`span`,{children:D(`世界名称`)}),(0,F.jsx)(`input`,{"aria-label":D(`世界名称`),autoFocus:!0,maxLength:160,value:A,onChange:e=>ee(e.currentTarget.value)})]}),(0,F.jsxs)(`div`,{className:`button-row`,children:[(0,F.jsx)(`button`,{type:`submit`,"aria-label":D(`保存世界名称`),disabled:A.trim()===``||A.trim()===e.title,children:D(`保存`)}),(0,F.jsx)(`button`,{type:`button`,className:`secondary-button`,onClick:()=>k(null),children:D(`取消`)})]})]}):null]},e.worldId))}):(0,F.jsx)(`div`,{className:`home-empty-world`,children:(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`h3`,{children:D(`还没有正在游玩的世界`)}),(0,F.jsx)(`p`,{children:D(`先新建内容包,或上传一份 ZIP,再整理开场与世界文档并创建独立世界。`)})]})})]}),(0,F.jsxs)(`section`,{className:`home-surface home-content-library`,"aria-labelledby":`home-content-title`,children:[(0,F.jsxs)(`header`,{className:`home-section-heading`,children:[(0,F.jsx)(`div`,{children:(0,F.jsx)(`h2`,{id:`home-content-title`,children:D(`内容包`)})}),(0,F.jsx)(`button`,{type:`button`,disabled:N,onClick:g,children:D(`新建内容包`)}),(0,F.jsx)(`button`,{type:`button`,disabled:N,onClick:_,children:D(`从示例新建`)})]}),!a&&(0,F.jsx)(`button`,{type:`button`,disabled:N,onClick:v,children:D(`配置模型连接`)}),(0,F.jsxs)(`div`,{className:`home-content-layout`,children:[(0,F.jsx)(`div`,{children:e.length>0?(0,F.jsx)(`div`,{className:`home-package-list`,children:e.map(e=>(0,F.jsxs)(`div`,{className:`home-world-card-shell`,children:[(0,F.jsxs)(`button`,{className:`home-package-card`,type:`button`,"aria-label":D(`打开内容包:{name}`,{name:e.title}),"aria-pressed":e.localId===i,disabled:N,onClick:()=>b(e.localId),children:[(0,F.jsxs)(`span`,{className:`home-package-card-heading`,children:[(0,F.jsx)(`strong`,{children:e.title}),(0,F.jsx)(`span`,{className:`home-package-state ${e.status}`,children:e.status===`usable`?D(`可用`):D(`待修复`)})]}),(0,F.jsx)(`span`,{className:`home-package-card-gameplay`,children:h(e.gameplay,E())}),(0,F.jsx)(`span`,{className:`home-package-card-copy`,children:e.status===`usable`?D(`可编辑、预览并创建新世界`):D(`打开并修复当前树中的问题`)})]}),(0,F.jsxs)(`details`,{className:`home-world-card-actions`,children:[(0,F.jsx)(`summary`,{"aria-label":D(`内容包操作:{title}`,{title:e.title}),children:`•••`}),(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`button`,{className:`home-world-rename`,type:`button`,"aria-label":D(`重命名内容包:{title}`,{title:e.title}),disabled:N,onClick:()=>{te(e.localId),M(e.title)},children:D(`重命名`)}),(0,F.jsx)(`button`,{className:`home-world-delete`,type:`button`,"aria-label":D(`删除内容包:{title}`,{title:e.title}),disabled:N,onClick:()=>C(e),children:D(`删除`)})]})]}),j===e.localId?(0,F.jsxs)(`form`,{className:`home-world-rename-form`,onSubmit:t=>{t.preventDefault();let n=ne.trim();n!==``&&n!==e.title&&(S(e,n),te(null))},children:[(0,F.jsxs)(`label`,{children:[(0,F.jsx)(`span`,{children:D(`内容包标题`)}),(0,F.jsx)(`input`,{"aria-label":D(`内容包标题`),autoFocus:!0,maxLength:160,value:ne,onChange:e=>M(e.currentTarget.value)})]}),(0,F.jsxs)(`div`,{className:`button-row`,children:[(0,F.jsx)(`button`,{type:`submit`,"aria-label":D(`保存内容包标题`),disabled:ne.trim()===``||ne.trim()===e.title,children:D(`保存`)}),(0,F.jsx)(`button`,{type:`button`,className:`secondary-button`,onClick:()=>te(null),children:D(`取消`)})]})]}):null]},e.localId))}):(0,F.jsxs)(`div`,{className:`home-empty-packages`,children:[(0,F.jsx)(`h3`,{children:D(`从一组人类可读文件开始`)}),(0,F.jsx)(`p`,{children:D(`从雨夜旅店的示例故事开始,也可以新建或导入自己的内容包。`)}),(0,F.jsx)(`button`,{type:`button`,className:`primary-button`,disabled:N,onClick:_,children:D(d?`正在创建示例…`:`从示例开始`)})]})}),(0,F.jsxs)(`details`,{className:`home-import-disclosure`,children:[(0,F.jsx)(`summary`,{children:D(`导入内容包 ZIP`)}),(0,F.jsxs)(`form`,{className:`home-import-card`,onSubmit:e=>{e.preventDefault(),y()},children:[(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`h3`,{children:D(`导入 ZIP`)}),(0,F.jsx)(`p`,{children:D(`选择本应用导出或遵循相同结构的内容包 ZIP;导入会创建新的本地身份,不覆盖同名内容包。`)})]}),(0,F.jsxs)(`label`,{className:`home-zip-picker`,children:[(0,F.jsx)(`span`,{children:D(`内容包 ZIP`)}),(0,F.jsx)(`input`,{className:`home-zip-picker-input`,type:`file`,accept:`.zip,application/zip,application/x-zip-compressed`,"aria-label":D(`内容包 ZIP 文件`),disabled:N,onChange:e=>f(e.target.files?.[0]??null)},c===null?`empty`:`${c.name}:${c.size}:${c.lastModified}`),(0,F.jsx)(`span`,{className:`home-zip-picker-control`,children:(0,F.jsx)(`span`,{"aria-live":`polite`,children:c===null?D(`尚未选择`):`${c.name} · ${De(c.size)}`})})]}),(0,F.jsx)(`button`,{type:`submit`,disabled:c===null||N,children:D(u?`正在导入…`:`导入 ZIP`)})]})]})]})]})]}),(0,F.jsxs)(`footer`,{className:`home-status-line`,"aria-label":D(`工作区状态`),children:[(0,F.jsxs)(`span`,{children:[D(`模型连接`),` ·`,` `,a?o:D(`尚未配置`)]}),(0,F.jsxs)(`button`,{type:`button`,disabled:N,onClick:p,children:[D(`当前预设`),` · `,s??D(`尚未选择`)]}),(0,F.jsxs)(`span`,{children:[D(`可用内容包`),` · `,P,re>0&&D(` · {count} 份待修复`,{count:re})]})]})]})}function De(e){return e<1024?`${e} B`:e<1048576?`${(e/1024).toFixed(1)} KiB`:`${(e/1048576).toFixed(1)} MiB`}function Oe(e){return/\((?:-1|none|auto|minimal|low|medium|high|xhigh|max|\d+)\)$/iu.test(e)}function ke(){return{connectionId:null,name:D(`默认模型`),presetId:`custom`,provider:`chat_completions`,dialect:`standard`,baseUrl:``,apiKey:``,modelId:``,reasoningEffort:`provider_default`,reasoningSummary:`provider_default`,thinkingMode:`provider_default`,thinkingBudgetTokens:``,contextWindowTokens:`128000`,maxOutputTokens:`16000`}}function Ae({client:e,library:t,onLibraryChange:n,onNotice:r,onDirtyChange:i}){let a=t.connections.find(({id:e})=>e===t.activeConnectionId),[o,s]=(0,l.useState)(()=>a===void 0?ke():Me(a)),[c,u]=(0,l.useState)([]),[d,f]=(0,l.useState)(null),[p,m]=(0,l.useState)(!1),h=t.connections.find(({id:e})=>e===t.activeConnectionId),g=(0,l.useMemo)(()=>t.presets.find(({id:e})=>e===o.presetId),[o.presetId,t.presets]),_=o.connectionId===null;(0,l.useEffect)(()=>i(p),[p,i]),(0,l.useEffect)(()=>()=>{i(!1)},[i]);function v(e,t=!1){s(t=>({...t,...e})),m(!0),t&&u([])}function y(e){v({modelId:e,...o.provider===`anthropic_messages`&&o.thinkingMode===`provider_default`&&!Ie({...o,modelId:e})?{reasoningSummary:`provider_default`}:{}})}function b(e){let n=t.presets.find(({id:t})=>t===e);n!==void 0&&v(n.id===`custom`?{presetId:e}:{presetId:e,provider:n.provider,dialect:`standard`,...Fe(o,n.provider,`standard`),baseUrl:n.baseUrl,name:o.connectionId===null||o.name===D(`默认模型`)?n.name:o.name},!0)}function x(e){s(Me(e)),u([]),m(!1),r(``)}function S(){s(ke()),u([]),m(!1),r(``)}async function C(t){if(t.preventDefault(),d===null){f(`save`);try{let t=await e.request({type:`model.save`,connection:{...o.connectionId===null?{}:{connectionId:o.connectionId},name:o.name,presetId:o.presetId,provider:o.provider,dialect:o.dialect,baseUrl:o.baseUrl,...o.apiKey.length===0?{}:{apiKey:o.apiKey},modelId:o.modelId,reasoningEffort:o.reasoningEffort,reasoningSummary:o.reasoningSummary,thinkingMode:o.thinkingMode,thinkingBudgetTokens:o.thinkingMode===`enabled`?Number(o.thinkingBudgetTokens):null,contextWindowTokens:Number(o.contextWindowTokens),maxOutputTokens:Number(o.maxOutputTokens)}});n(t),m(!1);let i=t.connections.find(({id:e})=>e===t.activeConnectionId);i!==void 0&&x(i),r(D(`模型连接已保存并启用;后续请求只使用当前配置。`))}catch(e){r(Ne(e))}finally{f(null)}}}async function w(t){if(d===null){f(t);try{n(await e.request({type:`model.select`,connectionId:t})),r(D(`已切换当前模型配置;Runtime 不会自动故障转移。`))}catch(e){r(Ne(e))}finally{f(null)}}}async function T(i){if(d===null){f(`copy:${i.id}`);try{let a=Pe(i.name,t.connections),o=await e.request({type:`model.copy`,connectionId:i.id,name:a}),s=o.library.connections.find(({id:e})=>e===o.copiedConnectionId);if(s===void 0)throw Error(D(`Runtime 没有返回已克隆的模型配置`));n(o.library),x(s),r(D(`已克隆为「{name}」。副本保留本机凭据,但不会切换当前配置。`,{name:s.name}))}catch(e){r(Ne(e))}finally{f(null)}}}async function E(t){if(d===null){f(`delete:${t}`);try{n(await e.request({type:`model.delete`,connectionId:t})),o.connectionId===t&&S(),r(D(`模型配置已从本机删除。`))}catch(e){r(Ne(e))}finally{f(null)}}}async function O(){if(d===null){f(`models`);try{let t=await e.request({type:`model.models`,...o.connectionId===null?{}:{connectionId:o.connectionId},provider:o.provider,baseUrl:o.baseUrl,...o.apiKey.length===0?{}:{apiKey:o.apiKey}});u(t.models),r(D(`已从当前端点拉取 {count} 个模型 ID。`,{count:t.models.length}))}catch(e){r(Ne(e))}finally{f(null)}}}return(0,F.jsxs)(`section`,{className:`model-connection-screen`,"aria-labelledby":`model-title`,children:[(0,F.jsxs)(`header`,{className:`config-header`,children:[(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`p`,{className:`eyebrow`,children:`WORKSPACE SETTING`}),(0,F.jsx)(`h2`,{id:`model-title`,children:D(`模型连接`)}),(0,F.jsx)(`p`,{className:`field-note`,children:D(`保存多份本机配置并明确切换。API Key 不会返回浏览器;切换失败时 Runtime 不会悄悄改用另一份配置。`)})]}),(0,F.jsxs)(`div`,{className:`model-active-summary`,children:[(0,F.jsx)(`span`,{children:D(`当前配置`)}),(0,F.jsx)(`strong`,{children:h?.name??D(`尚未启用`)}),(0,F.jsx)(`small`,{children:h?.modelId??D(`先保存一份可用连接`)})]})]}),(0,F.jsxs)(`div`,{className:`config-layout`,children:[(0,F.jsxs)(`form`,{className:`config-form panel-card`,onSubmit:e=>void C(e),children:[(0,F.jsxs)(`div`,{className:`section-heading-row`,children:[(0,F.jsx)(`h3`,{children:o.connectionId===null?D(`新建配置`):D(`编辑配置`)}),o.connectionId!==null&&(0,F.jsx)(`button`,{className:`secondary-button`,disabled:p,onClick:S,type:`button`,children:D(`新建另一份`)})]}),(0,F.jsx)(`label`,{htmlFor:`model-preset`,children:D(`提供商`)}),(0,F.jsx)(`select`,{id:`model-preset`,value:o.presetId,onChange:e=>b(e.target.value),children:t.presets.map(e=>(0,F.jsx)(`option`,{value:e.id,children:e.name},e.id))}),(0,F.jsx)(`label`,{htmlFor:`model-name`,children:D(`配置名称`)}),(0,F.jsx)(`input`,{id:`model-name`,maxLength:160,required:!0,value:o.name,onChange:e=>v({name:e.target.value})}),(0,F.jsx)(`label`,{htmlFor:`model-provider`,children:D(`协议适配器`)}),(0,F.jsxs)(`select`,{id:`model-provider`,value:o.provider,onChange:e=>v({presetId:`custom`,provider:e.target.value,...Fe(o,e.target.value,o.dialect)},!0),children:[(0,F.jsx)(`option`,{value:`chat_completions`,children:`OpenAI-compatible Chat Completions`}),(0,F.jsx)(`option`,{value:`openai_responses`,children:`OpenAI Responses API`}),(0,F.jsx)(`option`,{value:`anthropic_messages`,children:`Anthropic Messages`})]}),(0,F.jsx)(`label`,{htmlFor:`model-dialect`,children:D(`端点方言`)}),(0,F.jsxs)(`select`,{id:`model-dialect`,value:o.dialect,onChange:e=>{let t=e.target.value;v({dialect:t,...Fe(o,o.provider,t)})},children:[(0,F.jsx)(`option`,{value:`standard`,children:D(`协议标准`)}),(0,F.jsx)(`option`,{value:`cliproxyapi`,children:`CLIProxyAPI`})]}),(0,F.jsxs)(`details`,{className:`parameter-help`,children:[(0,F.jsx)(`summary`,{children:D(`参数说明`)}),(0,F.jsx)(`p`,{className:`field-note`,children:D(`CLIProxyAPI 方言只启用代理明确支持的兼容参数;响应仍按所选协议解析,不从可见文本猜测思考块。Claude 的签名 thinking 请选 Responses 或 Anthropic Messages;Chat Completions 通常只有 reasoning_content,不能承诺无损续传签名。模型名的 (high) 等 thinking 后缀会覆盖请求参数,因此 Effort 与 Thinking 都必须保留“Provider 默认”。`)})]}),(0,F.jsxs)(`div`,{className:`model-reasoning-fields`,children:[(0,F.jsxs)(`label`,{htmlFor:`model-reasoning-effort`,children:[D(`Effort(推理强度)`),(0,F.jsxs)(`select`,{id:`model-reasoning-effort`,value:o.reasoningEffort,onChange:e=>v({reasoningEffort:e.target.value}),children:[(0,F.jsx)(`option`,{value:`provider_default`,children:D(`Provider 默认`)}),o.provider===`anthropic_messages`?null:(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(`option`,{value:`none`,children:`none`}),(0,F.jsx)(`option`,{value:`minimal`,children:`minimal`})]}),(0,F.jsx)(`option`,{value:`low`,children:`low`}),(0,F.jsx)(`option`,{value:`medium`,children:`medium`}),(0,F.jsx)(`option`,{value:`high`,children:`high`}),(0,F.jsx)(`option`,{value:`xhigh`,children:`xhigh`}),(0,F.jsx)(`option`,{value:`max`,children:`max`})]})]}),(0,F.jsxs)(`label`,{htmlFor:`model-thinking-mode`,children:[D(`Thinking(思考模式)`),(0,F.jsxs)(`select`,{id:`model-thinking-mode`,disabled:o.provider!==`anthropic_messages`,value:o.thinkingMode,onChange:e=>{let t=e.target.value;v({thinkingMode:t,thinkingBudgetTokens:t===`enabled`?o.thinkingBudgetTokens||Le(o.maxOutputTokens):``,reasoningSummary:t===`provider_default`||t===`disabled`?t===`provider_default`&&Ie({...o,thinkingMode:t})?o.reasoningSummary:`provider_default`:o.reasoningSummary})},children:[(0,F.jsx)(`option`,{value:`provider_default`,children:D(`Provider 默认`)}),(0,F.jsx)(`option`,{value:`adaptive`,children:`adaptive`}),(0,F.jsx)(`option`,{value:`enabled`,children:D(`手动 token 预算`)}),(0,F.jsx)(`option`,{value:`disabled`,children:`disabled`})]})]}),(0,F.jsxs)(`label`,{htmlFor:`model-reasoning-summary`,children:[D(`Thinking 返回内容`),(0,F.jsxs)(`select`,{id:`model-reasoning-summary`,disabled:o.provider===`chat_completions`&&o.dialect===`standard`||o.provider===`anthropic_messages`&&(o.thinkingMode===`provider_default`||o.thinkingMode===`disabled`)&&!Ie(o),value:o.reasoningSummary,onChange:e=>v({reasoningSummary:e.target.value}),children:[(0,F.jsx)(`option`,{value:`provider_default`,children:D(`Provider 默认`)}),(0,F.jsx)(`option`,{value:`auto`,children:`auto`}),o.provider===`openai_responses`?(0,F.jsxs)(F.Fragment,{children:[(0,F.jsx)(`option`,{value:`concise`,children:`concise`}),(0,F.jsx)(`option`,{value:`detailed`,children:`detailed`})]}):null,(0,F.jsx)(`option`,{value:`none`,children:`none`})]})]})]}),o.provider===`anthropic_messages`&&o.thinkingMode===`enabled`?(0,F.jsxs)(`label`,{htmlFor:`model-thinking-budget`,children:[D(`Thinking budget tokens`),(0,F.jsx)(`input`,{id:`model-thinking-budget`,min:1024,max:Math.max(1024,Number(o.maxOutputTokens)-1),required:!0,step:1,type:`number`,value:o.thinkingBudgetTokens,onChange:e=>v({thinkingBudgetTokens:e.target.value})})]}):null,(0,F.jsxs)(`details`,{className:`parameter-help`,children:[(0,F.jsx)(`summary`,{children:D(`参数说明`)}),(0,F.jsx)(`p`,{className:`field-note`,children:D(`Effort 控制整份响应投入;Anthropic Thinking 独立控制思考块,手动预算必须小于最大输出。Thinking 返回内容只进入模型诊断;原生续传块仍会原样保存。`)})]}),(0,F.jsx)(`label`,{htmlFor:`model-base-url`,children:`Base URL`}),(0,F.jsx)(`input`,{id:`model-base-url`,"aria-label":`Base URL`,required:!0,type:`url`,value:o.baseUrl,onChange:e=>v({presetId:`custom`,baseUrl:e.target.value},!0)}),g?.id!==`custom`&&(0,F.jsxs)(`p`,{className:`field-note`,children:[D(`已填入`),g?.name,` `,D(`的内置端点;手动修改后按自定义端点保存。`)]}),(0,F.jsx)(`label`,{htmlFor:`model-api-key`,children:`API Key`}),(0,F.jsx)(`input`,{autoComplete:`off`,id:`model-api-key`,"aria-label":`API Key`,required:_,type:`password`,value:o.apiKey,placeholder:o.connectionId===null?D(`新配置必填`):D(`留空以沿用本机保存的现有凭据`),onChange:e=>v({apiKey:e.target.value},!0)}),(0,F.jsx)(`p`,{className:`field-note`,children:D(`编辑已有配置时,API Key 留空会把本机保存的现有凭据用于当前协议和端点;填写新值会覆盖它。`)}),(0,F.jsxs)(`div`,{className:`model-picker-row`,children:[(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`label`,{htmlFor:`model-id`,children:D(`模型 ID`)}),(0,F.jsx)(`input`,{id:`model-id`,"aria-label":D(`模型 ID`),maxLength:512,required:!0,value:o.modelId,onChange:e=>y(e.target.value)})]}),(0,F.jsx)(`button`,{className:`secondary-button`,disabled:d!==null||o.baseUrl.length===0||_&&o.apiKey.length===0,onClick:()=>void O(),type:`button`,children:D(d===`models`?`正在拉取…`:`从端点拉取模型`)})]}),c.length>0&&(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`label`,{htmlFor:`fetched-model-id`,children:D(`已拉取的模型`)}),(0,F.jsxs)(`select`,{id:`fetched-model-id`,value:c.includes(o.modelId)?o.modelId:``,disabled:d!==null,onChange:e=>y(e.target.value),children:[(0,F.jsx)(`option`,{value:``,disabled:!0,children:D(`选择模型`)}),c.map(e=>(0,F.jsx)(`option`,{value:e,children:e},e))]}),(0,F.jsx)(`p`,{className:`field-note`,children:D(`选择后会填入模型 ID,也可以手动输入。`)})]}),(0,F.jsxs)(`div`,{className:`two-column-fields`,children:[(0,F.jsxs)(`label`,{children:[D(`真实 context window`),(0,F.jsx)(`input`,{min:4096,max:16777216,required:!0,step:1,type:`number`,value:o.contextWindowTokens,onChange:e=>v({contextWindowTokens:e.target.value})})]}),(0,F.jsxs)(`label`,{children:[D(`最大输出 tokens`),(0,F.jsx)(`input`,{min:256,max:16777215,required:!0,step:1,type:`number`,value:o.maxOutputTokens,onChange:e=>v({maxOutputTokens:e.target.value})})]})]}),(0,F.jsxs)(`div`,{className:`form-actions`,children:[(0,F.jsx)(`button`,{disabled:d!==null,type:`submit`,children:D(d===`save`?`正在保存…`:`保存模型连接并启用`)}),p&&o.connectionId!==null&&(0,F.jsx)(`button`,{className:`secondary-button`,disabled:d!==null,onClick:()=>{let e=t.connections.find(({id:e})=>e===o.connectionId);e!==void 0&&x(e)},type:`button`,children:D(`放弃修改`)}),p&&o.connectionId===null&&(0,F.jsx)(`button`,{className:`secondary-button`,disabled:d!==null,onClick:S,type:`button`,children:D(`重置新配置`)})]})]}),(0,F.jsxs)(`section`,{className:`config-list`,"aria-label":D(`已保存模型配置`),children:[(0,F.jsxs)(`div`,{className:`section-heading-row`,children:[(0,F.jsx)(`h3`,{children:D(`已保存配置`)}),(0,F.jsxs)(`span`,{children:[t.connections.length,` `,D(`份`)]})]}),t.connections.length===0&&(0,F.jsx)(`p`,{className:`panel-card`,children:D(`尚未保存模型配置。`)}),t.connections.map(e=>{let n=e.id===t.activeConnectionId;return(0,F.jsxs)(`article`,{className:`config-card panel-card${n?` active-model-config`:``}`,children:[(0,F.jsxs)(`div`,{className:`section-heading-row`,children:[(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`p`,{className:`eyebrow`,children:je(e.provider)}),(0,F.jsx)(`h3`,{children:e.name})]}),n&&(0,F.jsx)(`span`,{className:`active-config`,children:D(`当前使用`)})]}),(0,F.jsxs)(`details`,{className:`model-connection-details`,children:[(0,F.jsx)(`summary`,{children:e.modelId}),(0,F.jsxs)(`dl`,{children:[(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`dt`,{children:D(`模型`)}),(0,F.jsx)(`dd`,{children:e.modelId})]}),(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`dt`,{children:D(`端点`)}),(0,F.jsx)(`dd`,{children:e.baseUrl})]}),(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`dt`,{children:D(`窗口 / 输出`)}),(0,F.jsxs)(`dd`,{children:[e.contextWindowTokens.toLocaleString(),` /`,` `,e.maxOutputTokens.toLocaleString()]})]}),(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`dt`,{children:D(`方言 / Effort / Thinking`)}),(0,F.jsxs)(`dd`,{children:[e.dialect,` / `,e.reasoningEffort,` /`,` `,e.thinkingMode,e.thinkingBudgetTokens===null?``:` (${e.thinkingBudgetTokens.toLocaleString()} tokens)`,` `,`/ `,e.reasoningSummary]})]}),(0,F.jsxs)(`div`,{children:[(0,F.jsx)(`dt`,{children:D(`凭据`)}),(0,F.jsx)(`dd`,{children:e.hasApiKey?D(`已保存在本机`):D(`未配置`)})]})]})]}),(0,F.jsxs)(`div`,{className:`form-actions`,children:[!n&&(0,F.jsx)(`button`,{disabled:d!==null,onClick:()=>void w(e.id),type:`button`,children:d===e.id?D(`正在切换…`):D(`切换到此配置`)}),(0,F.jsx)(`button`,{className:`secondary-button`,disabled:d!==null||p,onClick:()=>void T(e),type:`button`,children:d===`copy:${e.id}`?D(`正在克隆…`):D(`克隆配置`)}),(0,F.jsx)(`button`,{className:`secondary-button`,disabled:d!==null||p||o.connectionId===e.id,onClick:()=>x(e),type:`button`,children:o.connectionId===e.id?D(`正在编辑`):D(`编辑`)}),(0,F.jsx)(`button`,{className:`danger-button`,disabled:d!==null||p||n&&t.connections.length>1,onClick:()=>void E(e.id),type:`button`,children:d===`delete:${e.id}`?D(`正在删除…`):D(`删除`)})]}),n&&t.connections.length>1&&(0,F.jsx)(`p`,{className:`field-note`,children:D(`先切换到另一份配置,才能删除当前配置。`)})]},e.id)})]})]})]})}function je(e){switch(e){case`chat_completions`:return`Chat Completions`;case`openai_responses`:return`OpenAI Responses`;case`anthropic_messages`:return`Anthropic Messages`}}function Me(e){return{connectionId:e.id,name:e.name,presetId:e.presetId,provider:e.provider,dialect:e.dialect,baseUrl:e.baseUrl,apiKey:``,modelId:e.modelId,reasoningEffort:e.reasoningEffort,reasoningSummary:e.reasoningSummary,thinkingMode:e.thinkingMode,thinkingBudgetTokens:e.thinkingBudgetTokens===null?``:String(e.thinkingBudgetTokens),contextWindowTokens:String(e.contextWindowTokens),maxOutputTokens:String(e.maxOutputTokens)}}function Ne(e){return e instanceof Error?e.message:D(`模型配置操作失败`)}function Pe(e,t){let n=new Set(t.map(({name:e})=>e));for(let r=1;r<=t.length+1;r+=1){let t=r===1?D(`(副本)`):D(`(副本 {number})`,{number:r}),i=`${e.slice(0,Math.max(0,160-t.length))}${t}`;if(!n.has(i))return i}throw Error(D(`无法生成不重复的副本名称`))}function Fe(e,t,n){let r=t===`anthropic_messages`&&(e.reasoningEffort===`none`||e.reasoningEffort===`minimal`)?`provider_default`:e.reasoningEffort,i=t===`anthropic_messages`&&e.provider===`anthropic_messages`?e.thinkingMode:`provider_default`,a=Ie({...e,provider:t,dialect:n,thinkingMode:i});return{reasoningEffort:r,reasoningSummary:t!==`openai_responses`&&(e.reasoningSummary===`concise`||e.reasoningSummary===`detailed`)||t===`chat_completions`&&n===`standard`||t===`anthropic_messages`&&(i===`provider_default`||i===`disabled`)&&!a?`provider_default`:e.reasoningSummary,thinkingMode:i,thinkingBudgetTokens:i===`enabled`?e.thinkingBudgetTokens:``}}function Ie(e){return e.provider===`anthropic_messages`&&e.dialect===`cliproxyapi`&&e.thinkingMode===`provider_default`&&Oe(e.modelId)}function Le(e){let t=Number(e);return!Number.isSafeInteger(t)||t<=1024?`1024`:String(Math.min(8192,t-1))}function Re(){if(typeof crypto.randomUUID==`function`)return crypto.randomUUID();let e=crypto.getRandomValues(new Uint8Array(16));e[6]=e[6]&15|64,e[8]=e[8]&63|128;let t=Array.from(e,e=>e.toString(16).padStart(2,`0`));return[t.slice(0,4).join(``),t.slice(4,6).join(``),t.slice(6,8).join(``),t.slice(8,10).join(``),t.slice(10).join(``)].join(`-`)}function ze(e){return[...e.map(({id:e})=>({id:e,kind:`user`,enabled:!0})),{id:`builtin:summary`,kind:`builtin`,enabled:!1},{id:`package:followups`,kind:`content-package`,enabled:!0}]}function Be(e){let t=e===`zh-CN`;return{body:t?`主叙事已完成。仅根据已向玩家呈现的叙事与明确可见事实整理简短场景回顾,通过 artifact_emit 输出 recap。不要从工具材料、未表露认知或场外状态补全幕后原因,不补写剧情或引入新事实。回顾只是显示产物,不替代状态维护、检查点或历史恢复。`:`The main narrative has settled. Emit a brief scene recap as recap using artifact_emit, using only narrative already shown to the player and explicitly visible facts. Do not fill in backstage causes from tool material, unexpressed knowledge or offstage state. Do not continue the story or introduce new facts. This display artifact does not replace state maintenance, checkpoints or history recovery.`,definition:{id:`builtin:summary`,displayName:t?`场景回顾(系统示例)`:`Scene recap (system example)`,prompt:{role:`author_instruction`,path:`builtin:followup-summary`},artifacts:[{name:`recap`,channel:`builtin:summary`,strategy:`replace`,contentType:`text/markdown`,rendererMode:`document`,save:`commit`,invalidation:`explicit_clear`,required:!0,maxEmits:1}],maxArtifactBytes:32768}}}function Ve(e,t){if(t.type===`delete-resource`){let n=Ue(e).get(t.path)??{declared:!1,sources:[]};if(n.declared||n.sources.length)throw Error(`Resource is referenced: ${t.path}${n.sources.length?` (${n.sources.join(`, `)})`:``}`);let r=structuredClone(e);return delete r.files[t.path],r.structure.extensionRefs=r.structure.extensionRefs.filter(e=>e!==t.path),r}if((t.type===`create-followup`||t.type===`clone-followup`)&&(e.structure.followups.some(e=>e.id===t.id)||e.structure.followupItems?.some(e=>e.id===t.id)||e.files[`prompts/${t.id}.md`]!==void 0))throw Error(`Request or prompt already exists: ${t.id}`);let n=structuredClone(e);if(t.type===`create-panel`)return n.structure.playerViewPanels.push({id:t.id,source:{kind:`player_view`,view:`status`},channel:`player.view.${t.id}`,key:`current`,mount:`sidebar`,rendererMode:`document`,config:{title:t.title,layout:`stack`,theme:`default`,empty:`message`,emptyMessage:t.emptyMessage,groups:[]}}),n;if(t.type===`remove-panel`)return n.structure.playerViewPanels=n.structure.playerViewPanels.filter(e=>e.id!==t.id),n;if(t.type===`remove-followup`||t.type===`remove-artifact`){let e=t.type===`remove-followup`?t.id:t.requestId,r=n.structure.followups.find(t=>t.id===e);if(!r)throw Error(`Request does not exist: ${e}`);let i=t.type===`remove-followup`?r.artifacts:r.artifacts.filter(e=>e.name===t.output),a=new Set(i.map(e=>e.channel));t.type===`remove-followup`?(n.structure.followupItems=(n.structure.followupItems??ze(n.structure.followups)).filter(t=>t.id!==e),n.structure.followups=n.structure.followups.filter(t=>t.id!==e)):r.artifacts=r.artifacts.filter(e=>e.name!==t.output);let o=new Set([...n.structure.followups.flatMap(e=>e.artifacts.map(e=>e.channel)),...n.structure.playerViewPanels.map(e=>e.channel)]);return n.structure.mounts=n.structure.mounts.filter(e=>!a.has(e.channel)||o.has(e.channel)),n}if(t.type===`create-followup`){let e=`prompts/${t.id}.md`,r=qe(`${t.id}.output_1`,`output_1`);return n.files[e]=t.body,n.structure.followupItems=[...n.structure.followupItems??ze(n.structure.followups),{id:t.id,kind:`user`,enabled:!0}],n.structure.followups.push({id:t.id,displayName:t.displayName,prompt:{role:`author_instruction`,path:e},artifacts:[r],maxArtifactBytes:32768}),n.structure.mounts.push({channel:r.channel,mount:`story`}),n}if(t.type===`add-artifact`){let e=n.structure.followups.find(e=>e.id===t.requestId);if(!e)throw Error(`Request does not exist: ${t.requestId}`);for(let[e,r]of Object.entries(t.files??{})){if(n.files[e]!==void 0)throw Error(`Resource already exists: ${e}`);n.files[e]=r}let r=We(t.artifact);for(let e of r)if(n.files[e]===void 0)throw Error(`Resource does not exist: ${e}`);return e.artifacts.push(structuredClone(t.artifact)),n.structure.mounts.some(e=>e.channel===t.artifact.channel)||n.structure.mounts.push({channel:t.artifact.channel,mount:t.mount??`story`}),n.structure.extensionRefs=[...new Set([...n.structure.extensionRefs,...r,...Object.keys(t.files??{})])],n}if(t.type===`clone-followup`){let r=t.source===`builtin:summary`?Be(E()):void 0,i=r?.definition??n.structure.followups.find(e=>e.id===t.source);if(!i)throw Error(`Clone source does not exist`);let a=structuredClone(i);a.id=t.id,a.displayName+=E()===`zh-CN`?` 副本`:` copy`;let o=new Map,s=r?.body??e.files[i.prompt.path];if(s===void 0)throw Error(`Resource does not exist: ${i.prompt.path}`);let c=[...i.artifacts.flatMap(We),...Ge(s)];for(let t of c){if(o.has(t))continue;if(e.files[t]===void 0)throw Error(`Resource does not exist: ${t}`);let n=t.lastIndexOf(`/`);o.set(t,`${t.slice(0,n)}/${Re()}-${t.slice(n+1)}`),c.push(...Ge(e.files[t]))}let l=e=>e.replace(Ke(),e=>o.get(e)??e);for(let[t,r]of o)n.files[r]=l(e.files[t]);a.prompt.path=`prompts/${t.id}.md`,n.files[a.prompt.path]=l(s);let u=new Map;return a.artifacts=a.artifacts.map(e=>{let n={...e},r=u.get(e.channel);return r||(r=`${t.id}.output_${u.size+1}`,u.set(e.channel,r)),n.channel=r,e.renderer&&(n.renderer=o.get(e.renderer)),e.regex&&(n.regex=o.get(e.regex)),e.scripts&&(n.scripts=e.scripts.map(e=>o.get(e))),e.assets&&(n.assets=e.assets.map(e=>o.get(e))),n}),n.structure.followupItems=[...n.structure.followupItems??ze(n.structure.followups),{id:a.id,kind:`user`,enabled:!0}],n.structure.followups.push(a),n.structure.mounts.push(...(r?i.artifacts.map(e=>({channel:e.channel,mount:`story`})):e.structure.mounts).filter(e=>u.has(e.channel)).map(e=>({...e,channel:u.get(e.channel)}))),n.structure.extensionRefs=[...new Set([...n.structure.extensionRefs,...o.values()])],n}let r=t.target,i=r.kind===`panel`?n.structure.playerViewPanels.find(e=>e.id===r.id):n.structure.followups.find(e=>e.id===r.requestId)?.artifacts.find(e=>e.name===r.output);if(!i)throw Error(`Display declaration does not exist`);let a=t.edit;if(a.type===`mode`)return i.rendererMode=a.mode,n;let{kind:o}=a;if(a.type===`unbind`)return o===`assets`||o===`scripts`?i[o]=(i[o]??[]).filter(e=>e!==a.path):i[o]===a.path&&(delete i[o],o===`renderer`&&(delete i.rendererRevision,i.rendererMode=`document`)),n;let s=a.type===`create`?He(a):a.path;if(a.type===`create`&&(n.files[s]=a.body),n.files[s]===void 0)throw Error(`Resource does not exist: ${s}`);return o===`assets`||o===`scripts`?i[o]=[...new Set([...i[o]??[],s])]:(i[o]=s,o===`renderer`&&(i.rendererRevision=Re())),n.structure.extensionRefs=[...new Set([...n.structure.extensionRefs,s])],n}function He(e){return`${{renderer:`renderers`,regex:`regex`,scripts:`scripts`,assets:`assets`}[e.kind]}/${e.name.trim()?`named-`+Array.from(e.name.trim().slice(0,24)).map(e=>/[A-Za-z0-9.-]/u.test(e)?e:`_u${e.codePointAt(0).toString(16)}_`).join(``):`resource`}.${Re()}.${e.suffix}`}function Ue(e){let t=new Map;function n(e){let n=t.get(e);return n||(n={declared:!1,sources:[]},t.set(e,n)),n}let r=e.structure,i=[r.settingImprovementPrompt?.path,...r.narrativePrompts.map(e=>e.path),...r.followups.flatMap(e=>[e.prompt.path,...e.artifacts.flatMap(We)]),...r.playerViewPanels.flatMap(We)];for(let e of i)e!==void 0&&(n(e).declared=!0);for(let[t,i]of Object.entries(e.files))if(t!==`preset.yaml`&&t!==r.callChainPath)for(let e of new Set(Ge(i)))e!==t&&n(e).sources.push(t);return t}function We(e){return[e.renderer,e.regex,...e.scripts??[],...e.assets??[]].filter(e=>e!==void 0)}function Ge(e){return[...e.matchAll(Ke())].map(e=>e[0])}function Ke(){return/(?:(?<![A-Za-z0-9_./:@%?=#\\-])|(?<=\s[A-Za-z_:][A-Za-z0-9_:.-]*[\t\n\f\r ]*=))(?:renderers\/[A-Za-z0-9][A-Za-z0-9._-]*\.html|regex\/[A-Za-z0-9][A-Za-z0-9._-]*\.yaml|scripts\/[A-Za-z0-9][A-Za-z0-9._-]*\.js|assets\/[A-Za-z0-9][A-Za-z0-9._/-]*)(?![A-Za-z0-9_./$?%#=\\-])/gu}function qe(e,t){return{name:t,displayName:E()===`zh-CN`?`新产物`:`New output`,channel:e,strategy:`replace`,contentType:`text/markdown`,rendererMode:`document`,save:`commit`,invalidation:`explicit_clear`,required:!1,maxEmits:1}}function Je(e){let t=e.split(`/`).at(-1)??e,n=t.replace(/^(?:[a-f0-9]{8}-[a-f0-9-]{27}-)+/u,``).replace(/\.[a-f0-9]{8}-[a-f0-9-]{27}(?=\.)/gu,``);return n!==t&&n.startsWith(`named-`)?n.slice(6).replace(/_u([a-f0-9]+)_/gu,(e,t)=>Number.parseInt(t,16)<=1114111?String.fromCodePoint(Number.parseInt(t,16)):`_u${t}_`):n}function Ye(e,t){let n=`example_${Re().replaceAll(`-`,``)}`,r=E()===`zh-CN`,i=`renderers/${n}.html`,a=`scripts/${n}.js`,o=`assets/${n}.css`,s=e===`actions`?[{label:r?`询问船夫`:`Ask the ferryman`,prompt:r?`我问船夫:这封信是谁交给你的?`:`I ask the ferryman who gave him the letter.`}]:{title:r?`雾港`:`Mist harbour`,body:r?`灯塔在暮色中亮起。`:`The lighthouse shines at dusk.`};return{artifact:{name:n,displayName:e===`actions`?r?`行动建议按钮`:`Action buttons`:r?`结构化场景卡片`:`Scene card`,purpose:e===`actions`?r?`提供下一步行动草稿,不自动发送。`:`Offer draft actions without sending them.`:r?`回顾已发生的场景。`:`Recap the established scene.`,channel:`${t}.${n}`,strategy:`replace`,contentType:`application/json`,renderer:i,rendererRevision:`v1`,rendererMode:`app`,scripts:[a],assets:[o],save:`commit`,invalidation:`new_operation`,required:!1,maxEmits:1,payloadContract:e===`actions`?{type:`array`,minItems:1,maxItems:6,items:{type:`object`,additionalProperties:!1,required:[`label`,`prompt`],properties:{label:{type:`string`,minLength:1,maxLength:80},prompt:{type:`string`,minLength:1,maxLength:1e3}}}}:{type:`object`,additionalProperties:!1,required:[`title`,`body`],properties:{title:{type:`string`,minLength:1,maxLength:80},body:{type:`string`,maxLength:4e3}}}},files:{[i]:`<!doctype html><html><body><main id="content"></main><p id="feedback" role="status"></p></body></html>`,[o]:`body { margin: 0; padding: 16px; color: #27333c; background: #f7f4eb; font: 14px/1.7 system-ui; } #content { display: grid; gap: 8px; } button { padding: 12px; border: 1px solid #b9ad95; background: white; text-align: left; cursor: pointer; } button:disabled { opacity: .5; cursor: default; }`,[a]:`// request and fromHost are helpers defined by this example, not a built-in SDK.
1048
1048
  (function () {
1049
1049
  var pending = new Map();
1050
1050
  var instanceId = document.documentElement.dataset.narraeonInstance;
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <meta name="color-scheme" content="dark" />
7
7
  <title>Narraeon</title>
8
- <script type="module" crossorigin src="/assets/index-BgBL8G-A.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-7GzLSUpU.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-BJ-1Mb0e.css">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "narraeon",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "A local-first workspace for living worlds.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -35,6 +35,7 @@
35
35
  "scripts": {
36
36
  "build": "npm run clean && npm run build:web && npm run build:node",
37
37
  "build:node": "tsc -p tsconfig.build.json",
38
+ "build:android": "node scripts/build-android-bundle.mjs",
38
39
  "build:web": "vite build",
39
40
  "check": "npm run typecheck && npm run lint && npm run format:check",
40
41
  "clean": "node scripts/clean-dist.mjs",
@@ -1,86 +0,0 @@
1
- import { constants } from "node:fs";
2
- import { copyFile, link, mkdir } from "node:fs/promises";
3
- import { dirname } from "node:path";
4
- const hardlinkFallbackCodes = new Set([
5
- "EXDEV",
6
- "EPERM",
7
- "EACCES",
8
- "EMLINK",
9
- "ENOTSUP",
10
- "EOPNOTSUPP",
11
- "ENOSYS",
12
- ]);
13
- const reflinkFallbackCodes = new Set([
14
- "EXDEV",
15
- "EPERM",
16
- "EACCES",
17
- "ENOTSUP",
18
- "EOPNOTSUPP",
19
- "EINVAL",
20
- "ENOSYS",
21
- ]);
22
- /**
23
- * Retains one file without aliasing mutable bytes. Immutable files prefer a
24
- * hard link; every other case walks reflink -> exclusive byte copy.
25
- */
26
- export async function cloneFilePhysically(input) {
27
- await mkdir(dirname(input.target), { recursive: true, mode: 0o700 });
28
- const strategy = process.env.NARRAEON_INTERNAL_TEST_CLONE_STRATEGY;
29
- if (input.immutable && strategy !== "reflink" && strategy !== "copy") {
30
- try {
31
- await link(input.source, input.target);
32
- return;
33
- }
34
- catch (error) {
35
- if (await acceptExistingTarget(error, input))
36
- return;
37
- if (!isFallbackError(error, hardlinkFallbackCodes))
38
- throw error;
39
- }
40
- }
41
- if (strategy !== "copy") {
42
- try {
43
- await copyFileAsReflink(input.source, input.target);
44
- return;
45
- }
46
- catch (error) {
47
- if (await acceptExistingTarget(error, input))
48
- return;
49
- if (!isFallbackError(error, reflinkFallbackCodes))
50
- throw error;
51
- }
52
- }
53
- try {
54
- await copyFile(input.source, input.target, constants.COPYFILE_EXCL);
55
- }
56
- catch (error) {
57
- if (await acceptExistingTarget(error, input))
58
- return;
59
- throw error;
60
- }
61
- }
62
- async function copyFileAsReflink(source, target) {
63
- const injectedErrorCode = process.env.NARRAEON_INTERNAL_TEST_REFLINK_ERROR_CODE;
64
- if (injectedErrorCode !== undefined) {
65
- const error = new Error(`${injectedErrorCode}: injected copy-on-write clone failure`);
66
- error.code = injectedErrorCode;
67
- error.path = source;
68
- error.syscall = "copyfile";
69
- throw error;
70
- }
71
- await copyFile(source, target, constants.COPYFILE_FICLONE_FORCE);
72
- }
73
- async function acceptExistingTarget(error, input) {
74
- if (!isNodeError(error) ||
75
- error.code !== "EEXIST" ||
76
- input.onTargetExists === undefined)
77
- return false;
78
- await input.onTargetExists(input.source, input.target);
79
- return true;
80
- }
81
- function isFallbackError(error, acceptedCodes) {
82
- return isNodeError(error) && acceptedCodes.has(error.code ?? "");
83
- }
84
- function isNodeError(error) {
85
- return error instanceof Error && "code" in error;
86
- }