@mokuyoaxis/dsh-iris 0.2.1 → 0.2.3

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 (76) hide show
  1. package/.dsh/skills/iris-compose-media/SKILL.md +23 -73
  2. package/.dsh/skills/iris-compose-media/agents/openai.yaml +1 -1
  3. package/.dsh/skills/iris-compose-media/references/task-v2-recovery.md +23 -22
  4. package/.dsh/skills/iris-compose-media/references/workflows.md +25 -30
  5. package/.dsh/skills/iris-use/SKILL.md +41 -0
  6. package/.dsh/skills/iris-use/agents/openai.yaml +4 -0
  7. package/.dsh/skills/iris-use/references/media-contract.md +73 -0
  8. package/.dsh/skills/iris-verify-ui/SKILL.md +9 -7
  9. package/.dsh/skills/iris-verify-ui/agents/openai.yaml +1 -1
  10. package/.dsh/skills/iris-verify-ui/references/verdict-and-degradation.md +3 -0
  11. package/CHANGELOG.md +48 -2
  12. package/README.en.md +47 -40
  13. package/README.md +45 -39
  14. package/bin/dsh-iris.js +27 -6
  15. package/docs/AGENT_QUICKSTART.md +9 -9
  16. package/docs/ARCHITECTURE.md +11 -3
  17. package/docs/ARTIFACT_LIBRARY.md +1 -1
  18. package/docs/CLI_MANAGEMENT.md +5 -3
  19. package/docs/COMPOSITE_VISION.md +1 -1
  20. package/docs/CORE_RUNTIME_CONTRACT.md +12 -2
  21. package/docs/DSH_CORE_MIGRATION.md +7 -3
  22. package/docs/FAULT_INJECTION.md +11 -1
  23. package/docs/HEADLESS_CLI.md +10 -4
  24. package/docs/HOST_ADAPTER_CONTRACT.md +1 -1
  25. package/docs/HOST_DOCTOR.md +3 -3
  26. package/docs/MODEL_PORT_CONTRACT.md +9 -3
  27. package/docs/MUSIC.md +77 -0
  28. package/docs/OCR_MODEL.md +1 -1
  29. package/docs/PROMPT_OPTIMIZER.md +74 -6
  30. package/docs/PROVIDER_ADAPTER_CONTRACT.md +12 -2
  31. package/docs/PROVIDER_HEALTH.md +1 -1
  32. package/docs/ROADMAP.md +15 -18
  33. package/docs/SECURITY.md +4 -3
  34. package/docs/SYNC_DELIVERY_RECOVERY.md +39 -0
  35. package/docs/TASK_SEMANTICS.md +5 -5
  36. package/docs/VISION_CLI.md +1 -1
  37. package/docs/VISION_MODEL.md +1 -1
  38. package/docs/WORKBENCH_ARTIFACTS.md +1 -1
  39. package/docs/releases/0.2.2.md +31 -0
  40. package/docs/releases/0.2.3.md +54 -0
  41. package/lib/actions.js +52 -19
  42. package/lib/adapters.js +89 -24
  43. package/lib/api.js +14 -5
  44. package/lib/bundled-skills.js +1 -1
  45. package/lib/capability.js +25 -16
  46. package/lib/client.js +374 -43
  47. package/lib/command-service.js +12 -3
  48. package/lib/config.js +39 -6
  49. package/lib/core-artifact-store.js +44 -6
  50. package/lib/core-delivery-cache.js +145 -0
  51. package/lib/core-lease-recovery.js +64 -92
  52. package/lib/core-maintenance.js +12 -1
  53. package/lib/core-runtime.js +45 -47
  54. package/lib/core-tasks.js +19 -5
  55. package/lib/core-user-projection.js +2 -2
  56. package/lib/core-writer-lease.js +134 -0
  57. package/lib/dsh-core-adapter.js +14 -4
  58. package/lib/dsh-text-model-adapter.js +15 -2
  59. package/lib/generation-input.js +10 -0
  60. package/lib/host-runtime.js +4 -2
  61. package/lib/index.js +23 -4
  62. package/lib/models.js +23 -38
  63. package/lib/music-generation.js +21 -0
  64. package/lib/music-options.js +18 -0
  65. package/lib/music-transport.js +46 -0
  66. package/lib/prompt-optimizer-config.js +111 -30
  67. package/lib/prompt-optimizer-core.js +183 -12
  68. package/lib/prompt-optimizer.js +20 -14
  69. package/lib/provider-adapter.js +1 -0
  70. package/lib/provider-adapters.js +26 -2
  71. package/lib/provider-catalog.js +18 -7
  72. package/lib/provider-config-service.js +16 -5
  73. package/lib/provider-protocol.js +73 -0
  74. package/lib/provider-task-runner.js +86 -9
  75. package/package.json +4 -2
  76. package/user_guide.md +52 -24
package/lib/client.js CHANGED
@@ -21,7 +21,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
21
21
  var exports = module.exports;
22
22
 
23
23
  var React = require('react');
24
- var IRIS_CLIENT_VERSION = '0.2.1';
24
+ var IRIS_CLIENT_VERSION = '0.2.3';
25
25
  var HOST_CLIENT_PROTOCOL_VERSION = 0;
26
26
  var reportedSeats = [];
27
27
 
@@ -299,9 +299,31 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
299
299
  '.iris-po-trigger:hover { transform: translateY(-2px) scale(1.10); filter: drop-shadow(0 5px 10px rgba(112,137,255,.42)); }',
300
300
  '.iris-po-trigger:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #7aa2f7); outline-offset: 2px; }',
301
301
  '.iris-po-backdrop { position: fixed; inset: 0; z-index: 10019; background: rgba(8,10,18,.30); backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); touch-action: none; animation: iris-po-fade .16s ease-out; }',
302
- '.iris-po-panel { position: fixed; z-index: 10020; right: max(14px, env(safe-area-inset-right)); bottom: max(78px, calc(env(safe-area-inset-bottom) + 68px)); width: min(440px, calc(100vw - 28px)); max-height: min(680px, calc(100dvh - 104px)); overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; touch-action: pan-y; box-sizing: border-box; padding: 18px; display: grid; gap: 13px; background-color: rgba(35,36,47,.84); background: radial-gradient(circle at 10% -5%, rgba(132,160,255,.24), transparent 42%), color-mix(in srgb, var(--dsw-alias-bg-layer-1, #26262e) 82%, transparent); color: var(--dsw-alias-label-primary, #eeeeF2); border: 1px solid rgba(150,164,215,.25); border-radius: 20px; box-shadow: 0 24px 70px rgba(0,0,0,.48), inset 0 1px 0 rgba(255,255,255,.07); backdrop-filter: blur(28px) saturate(1.28); -webkit-backdrop-filter: blur(28px) saturate(1.28); animation: iris-po-rise .20s cubic-bezier(.2,.8,.2,1); scrollbar-width: thin; }',
302
+ '.iris-po-panel:where(dialog) { left: auto; top: auto; margin: 0; max-width: none; }',
303
+ 'dialog.iris-po-panel::backdrop { background: rgba(8,10,18,.30); backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); }',
304
+ '.iris-po-panel { position: fixed; z-index: 10020; right: max(14px, env(safe-area-inset-right)); bottom: max(78px, calc(env(safe-area-inset-bottom) + 68px)); width: min(560px, calc(100vw - 28px)); max-height: min(780px, calc(100dvh - 104px)); overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; touch-action: pan-y; box-sizing: border-box; padding: 18px; display: grid; gap: 14px; background-color: rgba(35,36,47,.84); background: radial-gradient(circle at 10% -5%, rgba(132,160,255,.20), transparent 42%), color-mix(in srgb, var(--dsw-alias-bg-layer-1, #26262e) 92%, transparent); color: var(--dsw-alias-label-primary, #eeeeF2); border: 1px solid rgba(150,164,215,.25); border-radius: 22px; box-shadow: 0 24px 70px rgba(0,0,0,.36), inset 0 1px 0 rgba(255,255,255,.07); backdrop-filter: blur(28px) saturate(1.28); -webkit-backdrop-filter: blur(28px) saturate(1.28); animation: iris-po-rise .20s cubic-bezier(.2,.8,.2,1); scrollbar-width: thin; outline: none; }',
303
305
  '.iris-po-head { position: sticky; top: -18px; z-index: 1; margin: -18px -18px 0; padding: 16px 18px 10px; display: flex; align-items: center; justify-content: space-between; gap: 10px; background: linear-gradient(to bottom, color-mix(in srgb, var(--dsw-alias-bg-layer-1, #26262e) 88%, transparent) 72%, transparent); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); }',
304
- '.iris-po-title { display: inline-flex; align-items: center; gap: 9px; font-size: 14px; letter-spacing: .01em; }',
306
+ '.iris-po-title { display: inline-flex; align-items: center; gap: 9px; font-size: 16px; letter-spacing: .01em; }',
307
+ '.iris-po-intro { display: grid; gap: 9px; }',
308
+ '.iris-po-principles { display: flex; flex-wrap: wrap; gap: 6px; }',
309
+ '.iris-po-principles span { padding: 3px 8px; border-radius: 999px; background: rgba(113,139,219,.09); color: var(--dsw-alias-label-secondary, #b4bdd5); font-size: 10px; }',
310
+ '.iris-po-section { min-width: 0; border: 1px solid rgba(137,155,218,.22); border-radius: 16px; background: rgba(128,150,221,.045); overflow: hidden; }',
311
+ '.iris-po-section > summary { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 14px; cursor: pointer; list-style: none; font-size: 13px; font-weight: 650; }',
312
+ '.iris-po-section > summary::-webkit-details-marker { display: none; }',
313
+ '.iris-po-section > summary::after { content: "⌄"; color: var(--dsw-alias-label-secondary, #a6a6b2); transition: transform .15s; }',
314
+ '.iris-po-section[open] > summary::after { transform: rotate(180deg); }',
315
+ '.iris-po-section-caption { margin-left: auto; font-size: 11px; font-weight: 400; color: var(--dsw-alias-label-secondary, #a6a6b2); }',
316
+ '.iris-po-section-body { min-width: 0; display: grid; gap: 13px; padding: 0 14px 14px; }',
317
+ '.iris-po-section:not([open]) > .iris-po-section-body { display: none; }',
318
+ '.iris-po-targets { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 5px; }',
319
+ '.iris-po-target { min-width: 0; min-height: 42px; padding: 7px 3px; border: 1px solid transparent; border-radius: 10px; font: inherit; font-size: 12px; cursor: pointer; color: var(--dsw-alias-label-secondary, #a6a6b2); background: rgba(128,150,221,.05); }',
320
+ '.iris-po-target[aria-pressed="true"] { color: var(--dsw-alias-label-primary, #eeeef2); border-color: rgba(124,147,225,.48); background: rgba(124,147,225,.18); box-shadow: inset 0 1px 0 rgba(255,255,255,.1); font-weight: 650; }',
321
+ '.iris-po-target:disabled { opacity: .45; cursor: not-allowed; }',
322
+ '.iris-po-target:focus-visible, .iris-po-section > summary:focus-visible, .iris-po-config > summary:focus-visible { outline: 2px solid #8d9fe6; outline-offset: 2px; }',
323
+ '.iris-po-source { min-width: 0; padding: 11px 12px; border-radius: 12px; border: 1px solid rgba(150,164,215,.14); background: rgba(128,150,221,.04); }',
324
+ '.iris-po-source-head { display: flex; justify-content: space-between; gap: 10px; font-size: 11px; color: var(--dsw-alias-label-secondary, #a6a6b2); }',
325
+ '.iris-po-source-text { margin: 8px 0 0; max-height: 112px; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; font-size: 12px; line-height: 1.65; }',
326
+ '.iris-po-template { white-space: pre-wrap; overflow-wrap: anywhere; max-height: 200px; overflow: auto; font-size: 11px; line-height: 1.65; padding: 10px; border-radius: 10px; background: rgba(128,150,221,.06); }',
305
327
  '.iris-po-mark, .iris-po-settings-mark { display: inline-flex; align-items: center; justify-content: center; width: 29px; height: 29px; border-radius: 999px; background: rgba(255,255,255,.055); box-shadow: inset 0 1px 0 rgba(255,255,255,.10), 0 5px 16px rgba(67,83,168,.16); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); filter: drop-shadow(0 2px 5px rgba(105,128,235,.16)); }',
306
328
  '.iris-po-close { width: 30px; height: 30px; display: inline-flex; align-items: center; justify-content: center; border: 0; border-radius: 999px; background: rgba(255,255,255,.05); color: var(--dsw-alias-label-secondary, #a6a6b2); font-size: 14px; cursor: pointer; transition: background .15s, color .15s; }',
307
329
  '.iris-po-close:hover { background: rgba(255,255,255,.10); color: var(--dsw-alias-label-primary, #eeeef2); }',
@@ -313,6 +335,11 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
313
335
  '.iris-po-field select:focus, .iris-po-field textarea:focus, .iris-po-result textarea:focus { border-color: rgba(122,162,247,.72); background: rgba(255,255,255,.075); box-shadow: 0 0 0 3px rgba(122,162,247,.11); }',
314
336
  '.iris-po-field textarea { min-height: 60px; resize: vertical; line-height: 1.55; }',
315
337
  '.iris-po-rules { display: grid; gap: 10px; }',
338
+ '.iris-po-editing { display: grid; gap: 8px; padding: 10px 12px; border: 1px solid rgba(150,164,215,.16); border-radius: 12px; }',
339
+ '.iris-po-editing label { display: flex; gap: 8px; align-items: center; font-size: 12px; cursor: pointer; }',
340
+ '.iris-po-suggestion { display: grid; gap: 7px; padding: 10px; margin-top: 8px; border-radius: 10px; background: rgba(128,150,221,.065); overflow-wrap: anywhere; }',
341
+ '.iris-po-suggestion label { display: flex; align-items: start; gap: 8px; font-size: 12px; }',
342
+ '.iris-po-suggestion p { margin: 0; white-space: pre-wrap; font-size: 12px; line-height: 1.6; }',
316
343
  '.iris-po-diff { white-space: pre-wrap; overflow-wrap: anywhere; max-height: 28dvh; overflow-y: auto; font-size: 12px; line-height: 1.6; }',
317
344
  '.iris-po-diff del, .iris-po-diff ins { display: block; padding: 6px 8px; border-radius: 7px; margin: 5px 0; }',
318
345
  '.iris-po-diff del { background: rgba(226,113,126,.12); }',
@@ -324,7 +351,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
324
351
  '.iris-po-primary { background: linear-gradient(135deg, #779cf3, #8c79de); color: #fff; border-color: rgba(174,187,255,.55); font-weight: 650; box-shadow: 0 6px 18px rgba(76,92,188,.22); }',
325
352
  '.iris-po-primary:hover { transform: translateY(-1px); box-shadow: 0 8px 22px rgba(76,92,188,.32); }',
326
353
  '.iris-po-primary:disabled, .iris-po-btn:disabled { opacity: .45; cursor: not-allowed; transform: none; box-shadow: none; }',
327
- '.iris-po-result { display: grid; gap: 8px; padding: 12px; border: 1px solid rgba(137,155,218,.18); border-radius: 15px; background: rgba(10,12,20,.16); }',
354
+ '.iris-po-result { display: grid; gap: 8px; padding: 12px; border: 1px solid rgba(137,155,218,.18); border-radius: 15px; background: rgba(128,150,221,.045); }',
328
355
  '.iris-po-result-label { font-size: 11px; font-weight: 650; letter-spacing: .02em; }',
329
356
  '.iris-po-result textarea { min-height: 156px; max-height: 34dvh; resize: vertical; line-height: 1.58; }',
330
357
  '.iris-po-config { padding: 11px 12px; border: 1px solid rgba(150,164,215,.14); border-radius: 13px; background: rgba(255,255,255,.025); font-size: 11px; }',
@@ -783,7 +810,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
783
810
  }
784
811
 
785
812
  /* ---- 模型池(阶段 9 P4):发现/手动模型 + verified 标记 + 逐模型测试/移除 + 手动添加 ---- */
786
- var CAP_SHORT = { 'image-gen': '图', 'video-gen': '视', 'tts': '音', 'transcribe': '转', 'vision': '视👁' };
813
+ var CAP_SHORT = { 'image-gen': '图', 'video-gen': '视', 'tts': '音', 'music-gen': '乐', 'transcribe': '转', 'vision': '视👁' };
787
814
  function ModelPool(props) {
788
815
  var p = props.provider;
789
816
  var addPair = React.useState('');
@@ -794,6 +821,11 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
794
821
  postAction('providers_set_model_image_protocol', { id: p.id, model_id: m.id, imageProtocol: value })
795
822
  .then(function (r) { props.onNote(r.ok ? '模型图片协议已更新' : (r.d.error || '更新失败')); if (r.ok) props.onDone(); });
796
823
  }
824
+ function setMusicProtocol(m, value) {
825
+ if (!value) return;
826
+ postAction('providers_set_model_music_protocol', { id: p.id, model_id: m.id, musicProtocol: value })
827
+ .then(function (r) { props.onNote(r.ok ? '模型音乐协议已更新' : (r.d.error || '更新失败')); if (r.ok) props.onDone(); });
828
+ }
797
829
  function verBadge(m, c) {
798
830
  var health = m.health && m.health[c];
799
831
  var status = health && HEALTH_META[health.status] ? health.status : 'configured';
@@ -812,7 +844,8 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
812
844
  var blocked = caps.map(function (c) { return m.health && m.health[c]; }).find(function (health) { return health && health.rateLimited; });
813
845
  return React.createElement('div', { key: m.id, className: 'iris-pm-mrow' },
814
846
  React.createElement('span', { className: 'iris-pm-mname' }, m.id, m.source === 'manual' ? React.createElement('span', { className: 'iris-pm-mtag' }, '手动') : null),
815
- React.createElement('span', { className: 'iris-pm-mcaps' }, caps.map(function (c) { return verBadge(m, c); })),
847
+ React.createElement('span', { className: 'iris-pm-mcaps' }, caps.length ? caps.map(function (c) { return verBadge(m, c); }) : '未标注功能'),
848
+ React.createElement(ModelCapabilityEditor, { provider: p, model: m, onNote: props.onNote, onDone: props.onDone }),
816
849
  caps.includes('image-gen') ? React.createElement('label', { className: 'iris-pm-note' }, '图片协议 ',
817
850
  React.createElement('select', { value: m.imageProtocol || 'auto', onChange: function (e) { setImageProtocol(m, e.target.value); } },
818
851
  React.createElement('option', { value: 'auto' }, '继承账号默认(' + (p.mediaProtocol || '自动') + ')'),
@@ -821,6 +854,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
821
854
  React.createElement('option', { value: 'openai-chat-images' }, '聊天生图(网关兼容)'),
822
855
  React.createElement('option', { value: 'openai-responses-images' }, 'Responses 生图')),
823
856
  m.imageRouting && m.imageRouting.protocolInferred ? ' · 协议待确认' : null) : null,
857
+ caps.includes('music-gen') ? React.createElement('label', { className: 'iris-pm-note' }, '音乐协议 ',
858
+ React.createElement('select', { value: m.musicProtocol || '', onChange: function (e) { setMusicProtocol(m, e.target.value); } },
859
+ React.createElement('option', { value: '' }, '请选择音乐协议'),
860
+ React.createElement('option', { value: 'gemini-web-music' }, 'Chat Completions(内嵌音频)'))) : null,
824
861
  caps.includes('vision') || (caps.includes('image-gen') && m.imageRouting && m.imageRouting.mediaProtocol === 'openai-chat-images') ? React.createElement(VisionInputEditor, { key: 'vision-input', provider: p, model: m,
825
862
  onNote: props.onNote, onDone: props.onDone }) : null,
826
863
  blocked ? React.createElement('span', { className: 'iris-pm-ver failed ' + healthClass(blocked), title: healthText(blocked) }, rateLimitLabel(blocked)) : null,
@@ -834,6 +871,42 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
834
871
  React.createElement('button', { className: 'iris-pm-btn', onClick: function () { props.addModel(p, newId.trim()); setNewId(''); } }, '+ 添加')));
835
872
  }
836
873
 
874
+ function ModelCapabilityEditor(props) {
875
+ var openPair = React.useState(false);
876
+ var capsPair = React.useState([]);
877
+ var savingPair = React.useState(false);
878
+ var selected = capsPair[0];
879
+ var saving = savingPair[0];
880
+ function save() {
881
+ if (saving) return;
882
+ savingPair[1](true);
883
+ postAction('providers_set_model_caps', { id: props.provider.id, model_id: props.model.id, capabilities: selected })
884
+ .then(function (r) {
885
+ if (r.ok) {
886
+ openPair[1](false);
887
+ props.onNote('模型功能标签已保存');
888
+ props.onDone();
889
+ } else props.onNote(r.d.error || '保存失败');
890
+ }).finally(function () { savingPair[1](false); });
891
+ }
892
+ if (!openPair[0]) return React.createElement('button', { className: 'iris-pm-btn', title: '设置此模型可用于哪些功能',
893
+ onClick: function () { capsPair[1]((props.model.capabilities || []).slice()); openPair[1](true); } }, '设置功能');
894
+ return React.createElement('div', { className: 'iris-wb-row' },
895
+ CAP_ROWS.map(function (row) {
896
+ return React.createElement('label', { key: row.cap, className: 'iris-pm-note' },
897
+ React.createElement('input', { type: 'checkbox', value: row.cap, checked: selected.includes(row.cap), disabled: saving,
898
+ onChange: function (e) {
899
+ var checked = e.target.checked;
900
+ capsPair[1](function (previous) {
901
+ return checked ? previous.filter(function (cap) { return cap !== row.cap; }).concat(row.cap)
902
+ : previous.filter(function (cap) { return cap !== row.cap; });
903
+ });
904
+ } }), row.label);
905
+ }),
906
+ React.createElement('button', { className: 'iris-pm-btn', disabled: saving, onClick: save }, saving ? '保存中…' : '保存功能'),
907
+ React.createElement('button', { className: 'iris-pm-btn', disabled: saving, onClick: function () { openPair[1](false); } }, '取消'));
908
+ }
909
+
837
910
  function VisionInputEditor(props) {
838
911
  var p = props.provider;
839
912
  var m = props.model;
@@ -936,7 +1009,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
936
1009
  postAction('providers_discover', { id: p.id }).then(function (r) { setNote(r.d.text || r.d.error || '发现完成'); refresh(); });
937
1010
  }
938
1011
  function testModel(p, m, capability) {
939
- var noProbe = capability === 'video-gen' || capability === 'transcribe';
1012
+ var noProbe = capability === 'video-gen' || capability === 'transcribe' || capability === 'music-gen';
940
1013
  if (!noProbe && !window.confirm('将用模型「' + m.id + '」发起一次真实的 ' + capability + ' 请求,可能产生费用。继续吗?')) return;
941
1014
  setNote('验证 ' + m.id + ' · ' + capability + '…');
942
1015
  postAction('providers_test_model', { id: p.id, model_id: m.id, capability: capability, confirm_paid: !noProbe })
@@ -1102,7 +1175,8 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1102
1175
  if (!r.ok || !r.d) return;
1103
1176
  var opts = (r.d.poolByCapability && r.d.poolByCapability[capability]) || [];
1104
1177
  var ord = (r.d.order && r.d.order[capability]) || [];
1105
- var assignedRef = ord[0] || (opts[0] && opts[0].ref) || '';
1178
+ if (capability === 'music-gen') opts = opts.filter(function (option) { return ord.includes(option.ref); });
1179
+ var assignedRef = ord[0] || (capability !== 'music-gen' && opts[0] && opts[0].ref) || '';
1106
1180
  var assignedOpt = opts.find(function (o) { return o.ref === assignedRef; });
1107
1181
  // 不再自动填 vals.model:留空 = 用能力分配的解析结果(避免绕过分配)
1108
1182
  setModelInfo({ assigned: assignedOpt ? assignedOpt.label : '', assignedRef: assignedRef, options: opts, loaded: true });
@@ -1119,7 +1193,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1119
1193
  })
1120
1194
  .then(function (r) { return r.json().then(function (d) { return { ok: r.ok, d: d }; }); })
1121
1195
  .then(function (res) {
1122
- setRun(res.ok ? { ok: true, text: res.d.text || '', img: res.d.imageUrl || res.d.imageDataUrl || null } : { ok: false, text: res.d.error || '执行失败' });
1196
+ setRun(res.ok ? { ok: true, text: res.d.text || '', img: res.d.imageUrl || res.d.imageDataUrl || null, audio: res.d.audioUrl || null } : { ok: false, text: res.d.error || '执行失败' });
1123
1197
  if (res.ok && res.d && res.d.storage === 'core') window.dispatchEvent(new CustomEvent('iris-core-artifacts-changed'));
1124
1198
  })
1125
1199
  .catch(function () { setRun({ ok: false, text: '网络错误' }); });
@@ -1129,6 +1203,14 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1129
1203
  if (open) {
1130
1204
  var inputs = fields.map(function (f) {
1131
1205
  var node;
1206
+ if (f.key === 'model' && capability === 'music-gen') {
1207
+ node = React.createElement('select', { value: vals.model || '', onChange: function (e) { setVal('model', e.target.value); } },
1208
+ React.createElement('option', { value: '' }, '按 Music 分配顺序'),
1209
+ ((modelInfo && modelInfo.options) || []).map(function (option) {
1210
+ return React.createElement('option', { key: option.ref, value: option.ref }, option.label);
1211
+ }));
1212
+ return React.createElement('div', { key: f.key, className: 'iris-act-field' }, React.createElement('label', {}, f.label), node);
1213
+ }
1132
1214
  if (f.type === 'file') {
1133
1215
  node = React.createElement(FileField, { value: vals[f.key] || '', accept: f.accept || '', onChange: function (v) { setVal(f.key, v); } });
1134
1216
  return React.createElement('div', { key: f.key, className: 'iris-act-field' },
@@ -1156,6 +1238,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1156
1238
  result = React.createElement('div', { className: 'iris-act-result' },
1157
1239
  React.createElement('div', {}, run.text),
1158
1240
  React.createElement('img', { className: 'iris-act-img', src: run.img, alt: '结果' }));
1241
+ } else if (run.ok && run.audio) {
1242
+ result = React.createElement('div', { className: 'iris-act-result' }, React.createElement('div', {}, run.text),
1243
+ React.createElement('audio', { src: run.audio, controls: true, preload: 'metadata' }),
1244
+ React.createElement('a', { href: run.audio, download: '' }, '下载音乐'));
1159
1245
  } else {
1160
1246
  result = React.createElement('div', { className: 'iris-act-result' + (run.ok ? '' : ' err') }, run.text);
1161
1247
  }
@@ -1168,7 +1254,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1168
1254
  modelBar = React.createElement('div', { className: 'iris-act-field' },
1169
1255
  React.createElement('label', {},
1170
1256
  React.createElement('span', { className: 'iris-pm-dot ' + healthClass(cardHealth) }, cardHealth.retryAt ? '◷' : HEALTH_META[cardHealth.status].symbol),
1171
- ' 模型:' + (shown || '自动') + (available ? '' : '(无可用模型)')),
1257
+ ' 模型:' + (shown || (capability === 'music-gen' ? '未分配' : '自动')) + (available ? '' : '(无可用模型)')),
1172
1258
  React.createElement('div', { className: 'iris-wb-muted' },
1173
1259
  healthText(cardHealth) + ' · 改分配 → 上方「能力分配(failover 顺序)」'));
1174
1260
  }
@@ -1208,6 +1294,12 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1208
1294
  { action: 'tts', title: '🔊 语音合成', capability: 'tts', fields: [
1209
1295
  { key: 'text', label: '文本', type: 'textarea', placeholder: '要合成的文字' },
1210
1296
  { key: 'voice', label: '音色 (可选)', placeholder: '如 Cherry' }] },
1297
+ { action: 'music', title: '🎵 音乐生成', capability: 'music-gen', fields: [
1298
+ { key: 'prompt', label: '音乐描述', type: 'textarea', placeholder: '描述风格、情绪、乐器;歌词、演唱语言和期望时长也可写在这里' },
1299
+ { key: 'model', label: '音乐模型' },
1300
+ { key: 'timeout_ms', label: '单次等待上限(毫秒,可选)', type: 'number', placeholder: '300000' },
1301
+ { key: 'retry_502', label: '502重新生成次数(可选,会额外占用调用额度)', type: 'number', placeholder: '2(0关闭,最多5)' },
1302
+ { key: 'retry_delay_ms', label: '重试等待(毫秒,可选)', type: 'number', placeholder: '5000' }] },
1211
1303
  { action: 'transcribe', title: '🎙️ 音频转写', capability: 'transcribe', fields: [
1212
1304
  { key: 'audio_path', label: '音频文件', type: 'file', accept: 'audio/*', placeholder: '/path/audio.wav' }] },
1213
1305
  { action: 'video_frames', title: '🎞️ 视频抽帧', fields: [
@@ -1261,6 +1353,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1261
1353
  { cap: 'image-gen', label: '🎨 画图' },
1262
1354
  { cap: 'video-gen', label: '🎬 视频' },
1263
1355
  { cap: 'tts', label: '🔊 语音' },
1356
+ { cap: 'music-gen', label: '🎵 音乐' },
1264
1357
  { cap: 'transcribe', label: '🎙️ 转写' },
1265
1358
  { cap: 'vision', label: '👁 视觉' }
1266
1359
  ];
@@ -1306,7 +1399,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1306
1399
  });
1307
1400
  return React.createElement('div', { key: row.cap, className: 'iris-cap-row' },
1308
1401
  React.createElement('span', { className: 'iris-cap-name' }, row.label),
1309
- chips.length ? chips : React.createElement('span', { className: 'iris-cap-auto' }, '自动(按池顺序)'),
1402
+ chips.length ? chips : React.createElement('span', { className: 'iris-cap-auto' }, row.cap === 'music-gen' ? '尚未分配音乐模型' : '自动(按池顺序)'),
1310
1403
  available.length ? React.createElement('select', {
1311
1404
  className: 'iris-pm-model-sel', value: '',
1312
1405
  onChange: function (e) { if (e.target.value) save(row.cap, list.concat([e.target.value])); }
@@ -1924,7 +2017,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
1924
2017
  }
1925
2018
 
1926
2019
  function coreCapabilityLabel(value) {
1927
- return ({ image: '图片生成', video: '视频生成', tts: '语音合成', transcribe: '音频转写' })[value] || String(value || '媒体任务');
2020
+ return ({ image: '图片生成', video: '视频生成', tts: '语音合成', music: '音乐生成', transcribe: '音频转写' })[value] || String(value || '媒体任务');
1928
2021
  }
1929
2022
 
1930
2023
  /* 高级诊断与用户任务区共用五类用户状态投影;与服务端 lib/core-user-projection.js 保持同一映射,
@@ -2458,6 +2551,24 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2458
2551
  note ? React.createElement('div', { className: 'iris-clean-note', style: { width: '100%' } }, note) : null);
2459
2552
  }
2460
2553
 
2554
+ function promptConfigFeedback(state) {
2555
+ var warnings = state && state.feedback && state.feedback.warnings;
2556
+ if (!Array.isArray(warnings) || !warnings.length) return null;
2557
+ return React.createElement('div', { className: 'iris-po-note', role: 'status' }, warnings.map(function (warning, index) {
2558
+ var text = '';
2559
+ if (warning.code === 'config-fallback') {
2560
+ text = warning.reason === 'read-failed' ? '配置读取失败,当前使用 Iris 默认设置。' : '配置内容无效,当前使用 Iris 默认设置。';
2561
+ text += warning.isolation === 'saved' ? '原文件已隔离保留,可在修正后重新导入。'
2562
+ : warning.isolation === 'failed' ? '隔离未完成,Iris 没有覆盖原文件;请检查原文件。' : '原文件未移动;请检查文件读取权限。';
2563
+ } else if (warning.code === 'ignored-fields' && Array.isArray(warning.fields)) {
2564
+ text = '已忽略不支持或当前路由未使用的字段:' + warning.fields.filter(function (field) { return typeof field === 'string'; }).map(function (field) { return JSON.stringify(field); }).join('、');
2565
+ if (warning.count > warning.fields.length) text += '(共 ' + warning.count + ' 个,仅显示前 ' + warning.fields.length + ' 个)';
2566
+ text += '。请检查字段名;这些字段未保存到配置。';
2567
+ }
2568
+ return text ? React.createElement('div', { key: index }, text) : null;
2569
+ }));
2570
+ }
2571
+
2461
2572
  function PromptOptimizerSettings() {
2462
2573
  var pair = React.useState(null);
2463
2574
  var state = pair[0];
@@ -2502,6 +2613,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2502
2613
  React.createElement('span', { style: { flex: 1 } }, enabled ? '对话框入口已启用' : '对话框入口已关闭'),
2503
2614
  React.createElement('button', { className: 'iris-pm-btn', onClick: toggle }, enabled ? '关闭入口' : '重新启用')),
2504
2615
  React.createElement('div', { className: 'iris-wb-muted' }, '这里只控制对话框提示词优化入口;Iris 工作台、Agent 工具和任务后台始终保持运行。'),
2616
+ promptConfigFeedback(state),
2505
2617
  note ? React.createElement('div', { className: 'iris-clean-note' }, note) : null);
2506
2618
  }
2507
2619
 
@@ -2674,6 +2786,14 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2674
2786
  selectedTask ? React.createElement(TaskDetailDrawer, { taskId: selectedTask }) : null);
2675
2787
  }
2676
2788
 
2789
+ var PROMPT_TARGET_OPTIONS = [
2790
+ { value: 'general', label: '通用', description: '整理目标、背景、约束与输出要求。' },
2791
+ { value: 'image', label: '画图', description: '整理已有主体、场景与构图要求,未指定细节不自动补齐。' },
2792
+ { value: 'image-edit', label: '改图', description: '明确要修改的部分,保留原图其余特征。' },
2793
+ { value: 'video', label: '视频', description: '组织动作、镜头与时间上的连续性。' },
2794
+ { value: 's2v', label: '数字人', description: '围绕已有首帧与音频设计表情、动作和镜头。' }
2795
+ ];
2796
+
2677
2797
  // 折叠相同首尾,只显示中间替换区;按 Unicode code point 比较,不拆开 emoji。
2678
2798
  function promptChanges(original, edited) {
2679
2799
  var before = Array.from(original);
@@ -2685,6 +2805,18 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2685
2805
  return { removed: before.slice(start, before.length - end).join(''), added: after.slice(start, after.length - end).join('') };
2686
2806
  }
2687
2807
 
2808
+ function promptRequestSnapshot(processing, title) {
2809
+ var request = processing && processing.request;
2810
+ if (!request || typeof request.system !== 'string' || typeof request.prompt !== 'string') return null;
2811
+ return React.createElement('details', { className: 'iris-po-config' },
2812
+ React.createElement('summary', {}, title),
2813
+ React.createElement('div', { className: 'iris-po-muted' }, 'Iris 发送的指令快照;宿主或供应商后续处理可能不同。'),
2814
+ React.createElement('div', { className: 'iris-po-result-label' }, '系统指令 · 含本次规则与输出协议'),
2815
+ React.createElement('div', { className: 'iris-po-template', 'aria-label': title + ' · 系统指令' }, request.system),
2816
+ React.createElement('div', { className: 'iris-po-result-label' }, '用户输入 · 含素材包装'),
2817
+ React.createElement('div', { className: 'iris-po-template', 'aria-label': title + ' · 用户输入' }, request.prompt));
2818
+ }
2819
+
2688
2820
  function PromptOptimizerControl(props) {
2689
2821
  var draft = props.useInput(function (value) { return value.draft; });
2690
2822
  var phase = props.useInput(function (value) { return value.phase; });
@@ -2695,6 +2827,8 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2695
2827
  var openPair = React.useState(false);
2696
2828
  var open = openPair[0];
2697
2829
  var setOpen = openPair[1];
2830
+ var generalPair = React.useState(true);
2831
+ var generalOpen = generalPair[0];
2698
2832
  var targetPair = React.useState('general');
2699
2833
  var target = targetPair[0];
2700
2834
  var setTarget = targetPair[1];
@@ -2722,14 +2856,39 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2722
2856
  var editedPair = React.useState('');
2723
2857
  var edited = editedPair[0];
2724
2858
  var setEdited = editedPair[1];
2859
+ var editingPair = React.useState('faithful');
2860
+ var editing = editingPair[0];
2861
+ var suggestionsPair = React.useState(null);
2862
+ var suggestions = suggestionsPair[0];
2863
+ var setSuggestions = suggestionsPair[1];
2864
+ var selectedSuggestionsPair = React.useState([]);
2865
+ var selectedSuggestions = selectedSuggestionsPair[0];
2866
+ var setSelectedSuggestions = selectedSuggestionsPair[1];
2867
+ var undoPair = React.useState([]);
2868
+ var suggestionUndo = undoPair[0];
2869
+ var setSuggestionUndo = undoPair[1];
2870
+ var editedRef = React.useRef(edited);
2871
+ var resultRef = React.useRef(result);
2872
+ editedRef.current = edited;
2873
+ resultRef.current = result;
2725
2874
  var abortRef = React.useRef(null);
2726
2875
  var fileRef = React.useRef(null);
2876
+ var panelRef = React.useRef(null);
2727
2877
  var lastWrittenRef = React.useRef(null);
2728
2878
  var resultSessionRef = React.useRef(null);
2729
2879
  var sessionRef = React.useRef(props.sessionId);
2730
2880
  sessionRef.current = props.sessionId;
2731
2881
  var supportsRules = !!(configState && configState.capabilities && configState.capabilities.rules === true);
2732
2882
  var supportsAssembly = !!(configState && configState.capabilities && configState.capabilities.assemble === true);
2883
+ var editingCapabilities = configState && configState.capabilities && configState.capabilities.editing;
2884
+ var supportsFaithful = Array.isArray(editingCapabilities) && editingCapabilities.indexOf('faithful') >= 0 && editingCapabilities.indexOf('rewrite') >= 0;
2885
+ var supportsLocalFaithful = supportsFaithful && configState.capabilities.localFaithful === true;
2886
+ var supportsSuggestions = Array.isArray(editingCapabilities) && editingCapabilities.indexOf('suggestions') >= 0;
2887
+ var supportsResetInstructions = !!(configState && configState.capabilities && configState.capabilities.resetInstructions === true);
2888
+ function supportsTarget(value) {
2889
+ var available = configState && configState.capabilities && configState.capabilities.targets;
2890
+ return Array.isArray(available) ? available.indexOf(value) >= 0 : value !== 'image-edit';
2891
+ }
2733
2892
 
2734
2893
  function promptRequest(operation, body, signal) {
2735
2894
  return fetch('/iris/api/prompt-optimizer/' + operation, {
@@ -2769,10 +2928,35 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2769
2928
  React.useEffect(function () {
2770
2929
  cancel();
2771
2930
  setResult(null);
2931
+ setSuggestions(null); setSelectedSuggestions([]); setSuggestionUndo([]);
2772
2932
  lastWrittenRef.current = null;
2773
2933
  resultSessionRef.current = null;
2774
2934
  }, [props.sessionId]);
2775
2935
 
2936
+ React.useEffect(function () {
2937
+ if (!open) return;
2938
+ var previous = document.activeElement;
2939
+ var dialog = panelRef.current;
2940
+ if (dialog) { dialog.showModal(); dialog.focus(); }
2941
+ function keyboard(event) {
2942
+ if (event.key === 'Escape') { event.preventDefault(); closePanel(); }
2943
+ if (event.key !== 'Tab' || !panelRef.current) return;
2944
+ var items = Array.from(panelRef.current.querySelectorAll('button:not(:disabled), textarea:not(:disabled), summary, input:not(:disabled)'))
2945
+ .filter(function (item) { return item.getClientRects().length > 0; });
2946
+ if (!items.length) return;
2947
+ var first = items[0], last = items[items.length - 1];
2948
+ if (event.shiftKey && (document.activeElement === first || document.activeElement === panelRef.current)) {
2949
+ event.preventDefault(); last.focus();
2950
+ } else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); }
2951
+ }
2952
+ window.addEventListener('keydown', keyboard);
2953
+ return function () {
2954
+ window.removeEventListener('keydown', keyboard);
2955
+ if (dialog && dialog.open) dialog.close();
2956
+ if (previous && previous.isConnected) previous.focus();
2957
+ };
2958
+ }, [open]);
2959
+
2776
2960
  function publishConfig(data) {
2777
2961
  setConfigState(data);
2778
2962
  try { window.dispatchEvent(new CustomEvent('iris-prompt-config-changed', { detail: data })); } catch (_) {}
@@ -2789,22 +2973,28 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2789
2973
 
2790
2974
  function optimize(mode) {
2791
2975
  mode = mode === 'assemble' ? 'assemble' : 'optimize';
2976
+ if (!supportsTarget(target)) { setNote('当前后端尚未支持此优化目标,请更新并重新载入'); return; }
2792
2977
  if (mode === 'assemble' && !supportsAssembly) { setNote('当前后端尚未支持只组装,请更新并重新载入'); return; }
2793
2978
  if (!draft || !draft.trim()) { setNote('请先在输入框中写入提示词'); return; }
2794
2979
  if (refCount) { setNote('当前版本暂不改写含 @ 或 / 结构化引用的草稿,以免引用失效'); return; }
2795
2980
  if (phase !== 'plain') { setNote('输入框正忙,请稍后再试'); return; }
2981
+ if (mode === 'optimize' && supportsFaithful && editing === 'faithful' && optimizationText.trim()) {
2982
+ setNote('忠实整理只调整分段;使用本次改写规则前,请选择“改写表达”。'); return;
2983
+ }
2796
2984
  if (abortRef.current) abortRef.current.abort();
2797
2985
  var controller = new AbortController();
2798
2986
  var requestSessionId = props.sessionId;
2987
+ var previousResult = result;
2988
+ var previewSnapshot = edited;
2799
2989
  abortRef.current = controller;
2800
2990
  setBusy(true);
2801
- setNote(mode === 'assemble' ? '正在组装,不调用模型…' : '正在调用模型优化…这可能产生模型费用');
2802
- setResult(null);
2803
- lastWrittenRef.current = null;
2991
+ setNote(mode === 'assemble' ? '正在组装,不调用模型…' : supportsLocalFaithful && editing === 'faithful'
2992
+ ? '正在忠实整理…需要分段时可能调用模型并产生费用' : '正在调用模型优化…这可能产生模型费用');
2804
2993
  promptRequest('optimize', {
2805
2994
  text: draft,
2806
2995
  target: target,
2807
2996
  mode: mode,
2997
+ editing: mode === 'optimize' && supportsFaithful ? editing : undefined,
2808
2998
  rules: selectedRules(mode),
2809
2999
  sessionId: props.sessionId,
2810
3000
  route: selectedRoute || undefined
@@ -2812,19 +3002,91 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2812
3002
  if (abortRef.current !== controller || controller.signal.aborted) return;
2813
3003
  if (sessionRef.current !== requestSessionId) throw new Error('会话已切换,请在当前会话重新生成预览');
2814
3004
  if (!data.ok || typeof data.optimized !== 'string' || typeof data.original !== 'string') throw new Error('未收到完整优化结果');
3005
+ if (data.original !== draft) throw new Error('结果原稿与本次请求不一致');
3006
+ if (mode === 'optimize' && supportsFaithful && editing === 'faithful'
3007
+ && (data.editing?.strategy !== 'faithful' || data.editing.coverage !== 'exact' || data.editing.order !== 'preserved')) throw new Error('后端未确认完整的忠实整理结果');
3008
+ if (resultRef.current !== previousResult || editedRef.current !== previewSnapshot) {
3009
+ setNote('预览已变化,本次结果未替换;你的编辑已保留。'); return;
3010
+ }
2815
3011
  setResult(data);
2816
3012
  setEdited(data.optimized);
3013
+ setSuggestions(null); setSelectedSuggestions([]); setSuggestionUndo([]);
3014
+ lastWrittenRef.current = null;
2817
3015
  resultSessionRef.current = requestSessionId;
2818
- setNote('已生成预览,尚未写回输入框');
3016
+ setNote(data.execution?.reason === 'single-part-faithful' && data.execution.modelInvocations === 0
3017
+ ? '原文无需分段,未调用模型;尚未写回输入框' : '已生成预览,尚未写回输入框');
2819
3018
  }).catch(function (err) {
2820
3019
  if (abortRef.current !== controller) return;
2821
3020
  if (controller.signal.aborted) setNote('已取消');
2822
- else setNote(String(err && err.message || err));
3021
+ else setNote(String(err && err.message || err) + (previousResult ? ';原有预览已保留' : ''));
2823
3022
  }).finally(function () {
2824
3023
  if (abortRef.current === controller) { abortRef.current = null; setBusy(false); }
2825
3024
  });
2826
3025
  }
2827
3026
 
3027
+ function requestSuggestions() {
3028
+ if (!supportsSuggestions || !result || busy || !validEdited()) return;
3029
+ if (resultSessionRef.current !== props.sessionId) { setNote('会话已切换,请重新生成预览'); return; }
3030
+ var snapshot = edited;
3031
+ var previousResult = result;
3032
+ var requestSessionId = props.sessionId;
3033
+ var controller = new AbortController();
3034
+ abortRef.current = controller;
3035
+ setBusy(true); setNote('正在获取可选建议,正文保持原样;这会调用文本模型。');
3036
+ promptRequest('optimize', { text: snapshot, target: result.target, mode: 'optimize', editing: 'suggestions', rules: [],
3037
+ sessionId: props.sessionId, route: selectedRoute || undefined }, controller.signal).then(function (data) {
3038
+ if (abortRef.current !== controller || controller.signal.aborted) return;
3039
+ if (sessionRef.current !== requestSessionId || resultRef.current !== previousResult || editedRef.current !== snapshot) {
3040
+ setNote('预览已变化,本次建议未载入;请针对当前文字重新获取。'); return;
3041
+ }
3042
+ var ids = [];
3043
+ if (!data.ok || data.original !== snapshot || data.optimized !== snapshot || data.target !== previousResult.target || data.editing?.strategy !== 'suggestions'
3044
+ || !Array.isArray(data.suggestions) || data.suggestions.length > 6 || !Array.isArray(data.questions) || data.questions.length > 6
3045
+ || !data.suggestions.every(function (item) {
3046
+ if (!item || typeof item.id !== 'string' || !/^suggestion-[1-6]$/.test(item.id) || ids.indexOf(item.id) >= 0
3047
+ || typeof item.label !== 'string' || !item.label.trim() || item.label.length > 80
3048
+ || typeof item.text !== 'string' || !item.text.trim() || item.text.length > 600) return false;
3049
+ ids.push(item.id); return true;
3050
+ }) || !data.questions.every(function (text) { return typeof text === 'string' && text.trim() && text.length <= 600; })) throw new Error('未收到完整补充建议');
3051
+ setSuggestions({ source: snapshot, items: data.suggestions, questions: data.questions, adopted: [], processing: data.processing });
3052
+ setSelectedSuggestions([]); setSuggestionUndo([]);
3053
+ setNote('建议尚未加入正文;只有采纳所选建议才会改变预览。');
3054
+ }).catch(function (err) {
3055
+ if (abortRef.current !== controller) return;
3056
+ setNote(controller.signal.aborted ? '已取消,原有预览保留' : String(err && err.message || err));
3057
+ }).finally(function () {
3058
+ if (abortRef.current === controller) { abortRef.current = null; setBusy(false); }
3059
+ });
3060
+ }
3061
+
3062
+ function adoptSuggestions() {
3063
+ if (!suggestions || busy || !selectedSuggestions.length) return;
3064
+ var items = suggestions.items.filter(function (item) { return selectedSuggestions.indexOf(item.id) >= 0 && suggestions.adopted.indexOf(item.id) < 0; });
3065
+ if (!items.length) return;
3066
+ var next = edited + '\n\n' + items.map(function (item) { return item.text; }).join('\n\n');
3067
+ if (next.length > 16000) { setNote('采纳后将超过 16000 字符,请减少建议或手动编辑'); return; }
3068
+ var ids = items.map(function (item) { return item.id; });
3069
+ setSuggestionUndo(suggestionUndo.concat([{ before: edited, after: next, ids: ids }]));
3070
+ setSuggestions(Object.assign({}, suggestions, { adopted: suggestions.adopted.concat(ids) }));
3071
+ setEdited(next); editedRef.current = next; setSelectedSuggestions([]);
3072
+ setNote('已将所选建议加入预览,尚未写回;可以撤回上次采纳。');
3073
+ }
3074
+
3075
+ function undoSuggestions() {
3076
+ var last = suggestionUndo[suggestionUndo.length - 1];
3077
+ if (!last || busy) return;
3078
+ if (edited !== last.after) { setNote('预览已被手动修改;请手动移除建议,撤回不会覆盖你的编辑。'); return; }
3079
+ setEdited(last.before); editedRef.current = last.before;
3080
+ setSuggestionUndo(suggestionUndo.slice(0, -1));
3081
+ if (suggestions) setSuggestions(Object.assign({}, suggestions, { adopted: suggestions.adopted.filter(function (id) { return last.ids.indexOf(id) < 0; }) }));
3082
+ setNote('已撤回上次采纳,尚未写回输入框。');
3083
+ }
3084
+
3085
+ function editPreview(value) {
3086
+ setEdited(value); editedRef.current = value;
3087
+ setSuggestions(null); setSelectedSuggestions([]); setSuggestionUndo([]);
3088
+ }
3089
+
2828
3090
  function cancel() {
2829
3091
  if (abortRef.current) {
2830
3092
  abortRef.current.abort();
@@ -2866,7 +3128,8 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2866
3128
  event.target.value = '';
2867
3129
  if (!file) return;
2868
3130
  file.text().then(function (raw) {
2869
- var parsed = JSON.parse(raw);
3131
+ var parsed;
3132
+ try { parsed = JSON.parse(raw); } catch (_) { throw new Error('文件不是有效 JSON,请检查格式后重新导入'); }
2870
3133
  return promptRequest('import', { config: parsed });
2871
3134
  }).then(function (data) {
2872
3135
  publishConfig(data);
@@ -2892,11 +3155,14 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2892
3155
  }
2893
3156
  }
2894
3157
 
2895
- function resetConfig() {
2896
- if (!window.confirm('恢复 Iris 内置默认 Prompt、目标模板和会话模型路由?')) return;
2897
- promptRequest('reset', {}).then(function (data) {
3158
+ function resetConfig(scope) {
3159
+ if (scope === 'instructions' && !supportsResetInstructions) { setNote('当前后端不支持仅恢复指令,请更新后重新载入'); return; }
3160
+ var question = scope === 'instructions' ? '仅恢复 Iris 内置系统指令和全部用途模板?模型路由、生成参数和入口开关保持不变。'
3161
+ : '恢复全部 Iris 提示词设置?系统指令、全部用途模板、模型路由、生成参数与预算、入口开关都会重置;模型路由恢复为当前会话,入口重新启用。';
3162
+ if (!window.confirm(question)) return;
3163
+ promptRequest('reset', { scope: scope }).then(function (data) {
2898
3164
  publishConfig(data);
2899
- setNote('已恢复 Iris 内置默认配置');
3165
+ setNote(scope === 'instructions' ? '已恢复默认指令;模型路由、生成参数与入口开关保持不变' : '已恢复全部 Iris 提示词设置');
2900
3166
  }).catch(function (err) {
2901
3167
  setNote('重置失败:' + String(err && err.message || err));
2902
3168
  });
@@ -2922,6 +3188,11 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2922
3188
  var reasoningLabel = reasoningSetting === 'off-if-supported' ? '支持时关闭' : reasoningSetting === 'provider-default' ? '供应商默认' : reasoningSetting === 'inherit' ? '跟随会话' : reasoningSetting;
2923
3189
  var outputBudget = configState && configState.config && configState.config.generation && configState.config.generation.maxOutputTokens || 1200;
2924
3190
  var changes = result ? promptChanges(result.original, edited) : null;
3191
+ var targetInfo = PROMPT_TARGET_OPTIONS.find(function (item) { return item.value === target; }) || PROMPT_TARGET_OPTIONS[0];
3192
+ var resultTarget = result && PROMPT_TARGET_OPTIONS.find(function (item) { return item.value === result.target; });
3193
+ var config = configState && configState.config;
3194
+ var targetTemplate = config && config.targets && config.targets[target];
3195
+ var requestConfigFeedback = result && promptConfigFeedback({ feedback: result.configFeedback });
2925
3196
  function ruleField(label, value, update, placeholder) {
2926
3197
  return React.createElement('label', { className: 'iris-po-field' }, React.createElement('span', {}, label),
2927
3198
  React.createElement('textarea', { value: value, disabled: busy || !supportsRules, placeholder: placeholder,
@@ -2931,20 +3202,44 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2931
3202
  var panel = null;
2932
3203
  if (open) {
2933
3204
  panel = React.createElement(React.Fragment, {},
2934
- React.createElement('div', { className: 'iris-po-backdrop', onClick: closePanel, 'aria-hidden': 'true' }),
2935
- React.createElement('div', { className: 'iris-po-panel', role: 'dialog', 'aria-modal': 'true', 'aria-label': 'Iris 提示词优化', onClick: function (e) { e.stopPropagation(); } },
3205
+ React.createElement('dialog', { ref: panelRef, tabIndex: -1, className: 'iris-po-panel', role: 'dialog', 'aria-modal': 'true', 'aria-label': 'Iris 提示词优化',
3206
+ onCancel: function (event) { event.preventDefault(); closePanel(); },
3207
+ onClick: function (event) {
3208
+ event.stopPropagation();
3209
+ if (event.target !== event.currentTarget) return;
3210
+ var bounds = event.currentTarget.getBoundingClientRect();
3211
+ if (event.clientX < bounds.left || event.clientX > bounds.right || event.clientY < bounds.top || event.clientY > bounds.bottom) closePanel();
3212
+ } },
2936
3213
  React.createElement('div', { className: 'iris-po-head' },
2937
3214
  React.createElement('strong', { className: 'iris-po-title' }, React.createElement('span', { className: 'iris-po-mark' }, '🫧'), 'Iris 提示词优化'),
2938
- React.createElement('button', { className: 'iris-po-close', onClick: closePanel, title: '关闭' }, '✕')),
2939
- React.createElement('div', { className: 'iris-po-muted' }, '只处理当前未发送草稿,不读取会话历史;调用模型可能产生费用。'),
2940
- React.createElement('label', { className: 'iris-po-field' },
2941
- React.createElement('span', {}, '优化目标'),
2942
- React.createElement('select', { value: target, disabled: busy, onChange: function (e) { setTarget(e.target.value); } },
2943
- React.createElement('option', { value: 'general' }, '通用对话'),
2944
- React.createElement('option', { value: 'image' }, '图片生成'),
2945
- React.createElement('option', { value: 'video' }, '视频生成'),
2946
- React.createElement('option', { value: 's2v' }, '首尾帧视频'))),
2947
- React.createElement('div', { className: 'iris-po-route', title: routeLabel }, '模型:' + routeLabel + ' · 思考:' + reasoningLabel + ' · 输出:' + outputBudget + ' tokens'),
3215
+ React.createElement('button', { className: 'iris-po-close', onClick: closePanel, title: '关闭', 'aria-label': '关闭提示词优化' }, '✕')),
3216
+ React.createElement('div', { className: 'iris-po-intro' },
3217
+ React.createElement('div', { className: 'iris-po-muted' }, '按用途整理草稿,先预览和编辑,再决定是否写回。'),
3218
+ promptConfigFeedback(configState),
3219
+ React.createElement('div', { className: 'iris-po-principles' },
3220
+ React.createElement('span', {}, '保留原意'), React.createElement('span', {}, '可查看改动'), React.createElement('span', {}, '确认后写回'))),
3221
+ React.createElement('details', { className: 'iris-po-section', open: generalOpen,
3222
+ onToggle: function (event) { generalPair[1](event.currentTarget.open); } },
3223
+ React.createElement('summary', {}, '一般功能',
3224
+ React.createElement('span', { className: 'iris-po-section-caption' }, targetInfo.label + ' · 提示词优化' + (result ? ' · 有预览' : ''))),
3225
+ React.createElement('div', { className: 'iris-po-section-body' },
3226
+ React.createElement('div', { className: 'iris-po-targets', role: 'group', 'aria-label': '优化目标' },
3227
+ PROMPT_TARGET_OPTIONS.map(function (item) {
3228
+ return React.createElement('button', { key: item.value, type: 'button', className: 'iris-po-target',
3229
+ 'aria-pressed': target === item.value, value: item.value,
3230
+ disabled: busy || !supportsTarget(item.value), title: supportsTarget(item.value) ? item.description : '当前后端未提供此目标',
3231
+ onClick: function () { setTarget(item.value); } }, item.label);
3232
+ })),
3233
+ React.createElement('div', { className: 'iris-po-muted' }, targetInfo.description),
3234
+ supportsFaithful ? React.createElement('div', { className: 'iris-po-editing', role: 'group', 'aria-label': '编辑方式' },
3235
+ ['faithful', 'rewrite'].map(function (strategy) {
3236
+ return React.createElement('label', { key: strategy }, React.createElement('input', { type: 'radio', name: 'iris-prompt-editing',
3237
+ value: strategy, checked: editing === strategy, disabled: busy, onChange: function () { editingPair[1](strategy); } }), strategy === 'faithful' ? '忠实整理(默认)' : '改写表达');
3238
+ }),
3239
+ React.createElement('div', { className: 'iris-po-muted' }, editing === 'faithful' ? '保持原文和句序,只调整分组与分段;不补充细节。' : '按本次规则改写,结果需逐项核对;未授权内容保持原意。')) : null,
3240
+ React.createElement('div', { className: 'iris-po-source' },
3241
+ React.createElement('div', { className: 'iris-po-source-head' }, '当前草稿', React.createElement('span', {}, (draft || '').length + ' 字符')),
3242
+ React.createElement('p', { className: 'iris-po-source-text' }, draft || '先在对话输入框写下你的想法。')),
2948
3243
  React.createElement('details', { className: 'iris-po-config' },
2949
3244
  React.createElement('summary', {}, '本次规则(可选)'),
2950
3245
  React.createElement('div', { className: 'iris-po-rules' },
@@ -2953,12 +3248,35 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2953
3248
  ruleField('输出后缀 · 原样加入结果末尾', suffixText, setSuffixText, '例如:禁止添加文字和水印。')),
2954
3249
  React.createElement('div', { className: 'iris-po-muted' }, supportsRules ? '非空内容表示本次选择;只组装保留原稿,只添加前后缀。刷新后重置,不修改 JSON 配置。' : '当前后端尚未支持规则,请更新后重新载入。')),
2955
3250
  React.createElement('div', { className: 'iris-po-actions' },
2956
- React.createElement('button', { className: 'iris-po-primary', disabled: busy || !draft || !draft.trim() || phase !== 'plain' || refCount > 0, onClick: function () { optimize('optimize'); } }, busy ? '处理中…' : '智能优化'),
3251
+ React.createElement('button', { className: 'iris-po-primary', disabled: busy || !draft || !draft.trim() || phase !== 'plain' || refCount > 0, onClick: function () { optimize('optimize'); } }, busy ? '处理中…' : supportsFaithful ? editing === 'faithful' ? '忠实整理' : '改写表达' : '智能优化'),
2957
3252
  React.createElement('button', { className: 'iris-po-btn', disabled: busy || !supportsAssembly || !draft || !draft.trim() || phase !== 'plain' || refCount > 0, onClick: function () { optimize('assemble'); } }, '只组装(不调用模型)'),
2958
3253
  busy ? React.createElement('button', { className: 'iris-po-btn', onClick: cancel }, '取消') : null),
3254
+ React.createElement('div', { className: 'iris-po-muted' }, supportsLocalFaithful
3255
+ ? '单片段忠实整理与只组装不调用模型;其他优化及获取建议可能产生模型费用。'
3256
+ : '智能优化调用文本模型,可能产生费用;只组装原稿与前后缀。'),
2959
3257
  result ? React.createElement('div', { className: 'iris-po-result' },
2960
- React.createElement('div', { className: 'iris-po-result-label' }, '结果预览 · 可编辑'),
2961
- React.createElement('textarea', { 'aria-label': '结果预览', value: edited, onChange: function (event) { setEdited(event.target.value); } }),
3258
+ React.createElement('div', { className: 'iris-po-result-label' }, '结果预览 · ' + (resultTarget ? resultTarget.label + ' · ' : '') + '可编辑 · ' + edited.length + ' 字符'),
3259
+ React.createElement('textarea', { 'aria-label': '结果预览', value: edited, onChange: function (event) { editPreview(event.target.value); } }),
3260
+ result.editing?.strategy === 'faithful' ? React.createElement('div', { className: 'iris-po-muted' }, '本次整理保留了全部原文片段与句序;手动编辑或采纳建议后,请重新核对内容。') : null,
3261
+ supportsSuggestions ? React.createElement('details', { className: 'iris-po-config' },
3262
+ React.createElement('summary', {}, '可选补充与待确认'),
3263
+ React.createElement('div', { className: 'iris-po-muted' }, '建议独立生成,默认未采纳;不会自动加入正文或写回。每次获取会调用文本模型。'),
3264
+ React.createElement('button', { className: 'iris-po-btn', disabled: busy || !edited.trim(), onClick: requestSuggestions }, '获取补充建议'),
3265
+ suggestions ? React.createElement('div', {},
3266
+ suggestions.items.map(function (item) {
3267
+ var adopted = suggestions.adopted.indexOf(item.id) >= 0;
3268
+ return React.createElement('div', { className: 'iris-po-suggestion', key: item.id },
3269
+ React.createElement('label', {}, React.createElement('input', { type: 'checkbox', checked: adopted || selectedSuggestions.indexOf(item.id) >= 0, disabled: busy || adopted,
3270
+ onChange: function (event) { setSelectedSuggestions(event.target.checked ? selectedSuggestions.concat([item.id]) : selectedSuggestions.filter(function (id) { return id !== item.id; })); } }), item.label + (adopted ? ' · 已加入预览' : '')),
3271
+ React.createElement('p', {}, item.text));
3272
+ }),
3273
+ !suggestions.items.length ? React.createElement('div', { className: 'iris-po-muted' }, '没有额外补充建议。') : null,
3274
+ suggestions.questions.length ? React.createElement('div', { className: 'iris-po-suggestion' }, React.createElement('strong', {}, '待确认 · 尚未作为要求'),
3275
+ suggestions.questions.map(function (question, index) { return React.createElement('p', { key: index }, question); })) : null,
3276
+ promptRequestSnapshot(suggestions.processing, '本次建议完整指令'),
3277
+ React.createElement('div', { className: 'iris-po-actions' },
3278
+ React.createElement('button', { className: 'iris-po-btn', disabled: busy || !selectedSuggestions.length, onClick: adoptSuggestions }, '采纳所选建议'),
3279
+ React.createElement('button', { className: 'iris-po-btn', disabled: busy || !suggestionUndo.length, onClick: undoSuggestions }, '撤回上次采纳'))) : null) : null,
2962
3280
  React.createElement('details', { className: 'iris-po-config' }, React.createElement('summary', {}, '原文与改动'),
2963
3281
  React.createElement('textarea', { 'aria-label': '优化前原文', readOnly: true, value: result.original }),
2964
3282
  React.createElement('div', { className: 'iris-po-muted' }, '以下折叠相同首尾,显示中间替换区:'),
@@ -2971,20 +3289,33 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2971
3289
  return React.createElement('div', { className: 'iris-po-diff', key: rule.id }, rule.label + '(' + (rule.kind === 'optimization' ? '指导改写' : rule.position === 'prefix' ? '输出前缀' : '输出后缀') + ')\n' + rule.text);
2972
3290
  }) : React.createElement('div', { className: 'iris-po-muted' }, '未选择额外规则'),
2973
3291
  result.processing ? React.createElement('div', { className: 'iris-po-diff' }, '优化处理规则:\n' + result.processing.systemPrompt + '\n\n目标模板:\n' + result.processing.targetTemplate) : null),
2974
- React.createElement('div', { className: 'iris-po-route' }, result.route ? '实际使用:' + result.route.provider + ' / ' + result.route.model + ' · 思考:' + (result.route.reasoningEffort === 'provider-default' ? '供应商默认' : result.route.reasoningEffort) : '本次只组装,未调用模型'),
3292
+ promptRequestSnapshot(result.processing, '本次正文处理完整指令'),
3293
+ React.createElement('div', { className: 'iris-po-route' }, result.route ? '实际使用:' + result.route.provider + ' / ' + result.route.model + ' · 思考:' + (result.route.reasoningEffort === 'provider-default' ? '供应商默认' : result.route.reasoningEffort)
3294
+ : result.execution?.reason === 'single-part-faithful' && result.execution.modelInvocations === 0 ? '忠实整理:无需分段,未调用模型'
3295
+ : result.mode === 'assemble' ? '本次只组装,未调用模型' : '未提供模型调用信息'),
3296
+ result.route && result.route.reason ? React.createElement('div', { className: 'iris-po-muted' }, '选择原因:' + result.route.reason + ' · ' + (result.route.textInput === 'supported' ? '宿主声明支持文本输入' : '文本输入能力未知')) : null,
3297
+ requestConfigFeedback ? React.createElement('details', { className: 'iris-po-config' },
3298
+ React.createElement('summary', {}, '本次请求的配置反馈'), requestConfigFeedback) : null,
2975
3299
  React.createElement('div', { className: 'iris-po-actions' },
2976
3300
  React.createElement('button', { className: 'iris-po-primary', onClick: function () { replaceDraft(edited); } }, '写回输入框'),
2977
3301
  React.createElement('button', { className: 'iris-po-btn', onClick: copyResult }, '复制'),
2978
3302
  React.createElement('button', { className: 'iris-po-btn', disabled: busy, onClick: function () { optimize(result.mode); } }, '重新生成预览'),
2979
3303
  React.createElement('button', { className: 'iris-po-btn', onClick: function () { replaceDraft(result.original); } }, '恢复原文'))) : null,
2980
- note ? React.createElement('div', { className: 'iris-po-note' }, note) : null,
3304
+ note ? React.createElement('div', { className: 'iris-po-note', role: 'status' }, note) : null)),
2981
3305
  React.createElement('details', { className: 'iris-po-config' },
2982
- React.createElement('summary', {}, 'JSON 配置'),
2983
- React.createElement('div', { className: 'iris-po-muted' }, '可导出后修改 systemPrompt、targets、route 和 generation,再重新导入。'),
3306
+ React.createElement('summary', {}, '指令与模型设置'),
3307
+ React.createElement('div', { className: 'iris-po-route', title: routeLabel }, '模型:' + routeLabel + ' · 思考:' + reasoningLabel + ' · 输出:' + outputBudget + ' tokens'),
3308
+ React.createElement('details', {}, React.createElement('summary', {}, '查看当前功能的优化指令'),
3309
+ React.createElement('div', { className: 'iris-po-template' }, config && config.systemPrompt || '配置尚未载入'),
3310
+ React.createElement('div', { className: 'iris-po-template' }, targetTemplate || '当前后端未提供此目标模板')),
3311
+ React.createElement('div', { className: 'iris-po-result-label' }, 'JSON 配置'),
3312
+ React.createElement('div', { className: 'iris-po-muted' }, '导入会替换当前配置;局部 JSON 缺少的字段使用内置默认值。可导出后修改 systemPrompt、targets、route 和 generation,再重新导入。'),
2984
3313
  React.createElement('div', { className: 'iris-po-actions' },
2985
3314
  React.createElement('button', { className: 'iris-po-btn', onClick: exportConfig }, '导出'),
2986
- React.createElement('button', { className: 'iris-po-btn', onClick: function () { if (fileRef.current) fileRef.current.click(); } }, '导入'),
2987
- React.createElement('button', { className: 'iris-po-btn danger', onClick: resetConfig }, '恢复默认'),
3315
+ React.createElement('button', { className: 'iris-po-btn', disabled: busy, onClick: function () { if (fileRef.current) fileRef.current.click(); } }, '导入'),
3316
+ React.createElement('button', { className: 'iris-po-btn', disabled: busy || !supportsResetInstructions,
3317
+ title: supportsResetInstructions ? '只更新系统指令与全部用途模板' : '当前后端尚未支持仅恢复指令', onClick: function () { resetConfig('instructions'); } }, '仅恢复默认指令'),
3318
+ React.createElement('button', { className: 'iris-po-btn danger', disabled: busy, onClick: function () { resetConfig('all'); } }, '恢复全部设置'),
2988
3319
  React.createElement('input', { ref: fileRef, className: 'iris-po-file', type: 'file', accept: 'application/json,.json', onChange: importConfig })),
2989
3320
  React.createElement('div', { className: 'iris-po-muted' }, '当前:' + (configState && configState.source === 'custom' ? '用户 JSON' : 'Iris 默认'))),
2990
3321
  React.createElement('button', { className: 'iris-po-disable', disabled: busy, onClick: disableEntry }, '关闭此对话入口(工作台仍保留)')));