@mokuyoaxis/dsh-iris 0.2.1 → 0.2.2

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 (42) hide show
  1. package/CHANGELOG.md +23 -1
  2. package/README.en.md +25 -25
  3. package/README.md +25 -25
  4. package/docs/AGENT_QUICKSTART.md +7 -7
  5. package/docs/ARCHITECTURE.md +10 -2
  6. package/docs/ARTIFACT_LIBRARY.md +1 -1
  7. package/docs/CLI_MANAGEMENT.md +5 -3
  8. package/docs/COMPOSITE_VISION.md +1 -1
  9. package/docs/CORE_RUNTIME_CONTRACT.md +9 -1
  10. package/docs/DSH_CORE_MIGRATION.md +1 -1
  11. package/docs/FAULT_INJECTION.md +10 -1
  12. package/docs/HEADLESS_CLI.md +4 -2
  13. package/docs/HOST_ADAPTER_CONTRACT.md +1 -1
  14. package/docs/MODEL_PORT_CONTRACT.md +4 -2
  15. package/docs/OCR_MODEL.md +1 -1
  16. package/docs/PROMPT_OPTIMIZER.md +33 -3
  17. package/docs/PROVIDER_ADAPTER_CONTRACT.md +10 -2
  18. package/docs/PROVIDER_HEALTH.md +1 -1
  19. package/docs/ROADMAP.md +10 -2
  20. package/docs/SECURITY.md +1 -1
  21. package/docs/TASK_SEMANTICS.md +1 -1
  22. package/docs/VISION_CLI.md +1 -1
  23. package/docs/VISION_MODEL.md +1 -1
  24. package/docs/WORKBENCH_ARTIFACTS.md +1 -1
  25. package/docs/releases/0.2.2.md +31 -0
  26. package/lib/actions.js +12 -16
  27. package/lib/adapters.js +88 -24
  28. package/lib/capability.js +23 -16
  29. package/lib/client.js +268 -22
  30. package/lib/config.js +13 -4
  31. package/lib/core-lease-recovery.js +64 -92
  32. package/lib/core-runtime.js +45 -47
  33. package/lib/core-writer-lease.js +134 -0
  34. package/lib/models.js +19 -37
  35. package/lib/prompt-optimizer-config.js +19 -9
  36. package/lib/prompt-optimizer-core.js +133 -7
  37. package/lib/provider-adapters.js +2 -1
  38. package/lib/provider-config-service.js +3 -4
  39. package/lib/provider-protocol.js +61 -0
  40. package/lib/provider-task-runner.js +6 -4
  41. package/package.json +1 -1
  42. package/user_guide.md +30 -16
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.2';
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; }',
@@ -812,7 +839,8 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
812
839
  var blocked = caps.map(function (c) { return m.health && m.health[c]; }).find(function (health) { return health && health.rateLimited; });
813
840
  return React.createElement('div', { key: m.id, className: 'iris-pm-mrow' },
814
841
  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); })),
842
+ React.createElement('span', { className: 'iris-pm-mcaps' }, caps.length ? caps.map(function (c) { return verBadge(m, c); }) : '未标注功能'),
843
+ React.createElement(ModelCapabilityEditor, { provider: p, model: m, onNote: props.onNote, onDone: props.onDone }),
816
844
  caps.includes('image-gen') ? React.createElement('label', { className: 'iris-pm-note' }, '图片协议 ',
817
845
  React.createElement('select', { value: m.imageProtocol || 'auto', onChange: function (e) { setImageProtocol(m, e.target.value); } },
818
846
  React.createElement('option', { value: 'auto' }, '继承账号默认(' + (p.mediaProtocol || '自动') + ')'),
@@ -834,6 +862,42 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
834
862
  React.createElement('button', { className: 'iris-pm-btn', onClick: function () { props.addModel(p, newId.trim()); setNewId(''); } }, '+ 添加')));
835
863
  }
836
864
 
865
+ function ModelCapabilityEditor(props) {
866
+ var openPair = React.useState(false);
867
+ var capsPair = React.useState([]);
868
+ var savingPair = React.useState(false);
869
+ var selected = capsPair[0];
870
+ var saving = savingPair[0];
871
+ function save() {
872
+ if (saving) return;
873
+ savingPair[1](true);
874
+ postAction('providers_set_model_caps', { id: props.provider.id, model_id: props.model.id, capabilities: selected })
875
+ .then(function (r) {
876
+ if (r.ok) {
877
+ openPair[1](false);
878
+ props.onNote('模型功能标签已保存');
879
+ props.onDone();
880
+ } else props.onNote(r.d.error || '保存失败');
881
+ }).finally(function () { savingPair[1](false); });
882
+ }
883
+ if (!openPair[0]) return React.createElement('button', { className: 'iris-pm-btn', title: '设置此模型可用于哪些功能',
884
+ onClick: function () { capsPair[1]((props.model.capabilities || []).slice()); openPair[1](true); } }, '设置功能');
885
+ return React.createElement('div', { className: 'iris-wb-row' },
886
+ CAP_ROWS.map(function (row) {
887
+ return React.createElement('label', { key: row.cap, className: 'iris-pm-note' },
888
+ React.createElement('input', { type: 'checkbox', value: row.cap, checked: selected.includes(row.cap), disabled: saving,
889
+ onChange: function (e) {
890
+ var checked = e.target.checked;
891
+ capsPair[1](function (previous) {
892
+ return checked ? previous.filter(function (cap) { return cap !== row.cap; }).concat(row.cap)
893
+ : previous.filter(function (cap) { return cap !== row.cap; });
894
+ });
895
+ } }), row.label);
896
+ }),
897
+ React.createElement('button', { className: 'iris-pm-btn', disabled: saving, onClick: save }, saving ? '保存中…' : '保存功能'),
898
+ React.createElement('button', { className: 'iris-pm-btn', disabled: saving, onClick: function () { openPair[1](false); } }, '取消'));
899
+ }
900
+
837
901
  function VisionInputEditor(props) {
838
902
  var p = props.provider;
839
903
  var m = props.model;
@@ -2674,6 +2738,14 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2674
2738
  selectedTask ? React.createElement(TaskDetailDrawer, { taskId: selectedTask }) : null);
2675
2739
  }
2676
2740
 
2741
+ var PROMPT_TARGET_OPTIONS = [
2742
+ { value: 'general', label: '通用', description: '整理目标、背景、约束与输出要求。' },
2743
+ { value: 'image', label: '画图', description: '整理已有主体、场景与构图要求,未指定细节不自动补齐。' },
2744
+ { value: 'image-edit', label: '改图', description: '明确要修改的部分,保留原图其余特征。' },
2745
+ { value: 'video', label: '视频', description: '组织动作、镜头与时间上的连续性。' },
2746
+ { value: 's2v', label: '数字人', description: '围绕已有首帧与音频设计表情、动作和镜头。' }
2747
+ ];
2748
+
2677
2749
  // 折叠相同首尾,只显示中间替换区;按 Unicode code point 比较,不拆开 emoji。
2678
2750
  function promptChanges(original, edited) {
2679
2751
  var before = Array.from(original);
@@ -2695,6 +2767,8 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2695
2767
  var openPair = React.useState(false);
2696
2768
  var open = openPair[0];
2697
2769
  var setOpen = openPair[1];
2770
+ var generalPair = React.useState(true);
2771
+ var generalOpen = generalPair[0];
2698
2772
  var targetPair = React.useState('general');
2699
2773
  var target = targetPair[0];
2700
2774
  var setTarget = targetPair[1];
@@ -2722,14 +2796,37 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2722
2796
  var editedPair = React.useState('');
2723
2797
  var edited = editedPair[0];
2724
2798
  var setEdited = editedPair[1];
2799
+ var editingPair = React.useState('faithful');
2800
+ var editing = editingPair[0];
2801
+ var suggestionsPair = React.useState(null);
2802
+ var suggestions = suggestionsPair[0];
2803
+ var setSuggestions = suggestionsPair[1];
2804
+ var selectedSuggestionsPair = React.useState([]);
2805
+ var selectedSuggestions = selectedSuggestionsPair[0];
2806
+ var setSelectedSuggestions = selectedSuggestionsPair[1];
2807
+ var undoPair = React.useState([]);
2808
+ var suggestionUndo = undoPair[0];
2809
+ var setSuggestionUndo = undoPair[1];
2810
+ var editedRef = React.useRef(edited);
2811
+ var resultRef = React.useRef(result);
2812
+ editedRef.current = edited;
2813
+ resultRef.current = result;
2725
2814
  var abortRef = React.useRef(null);
2726
2815
  var fileRef = React.useRef(null);
2816
+ var panelRef = React.useRef(null);
2727
2817
  var lastWrittenRef = React.useRef(null);
2728
2818
  var resultSessionRef = React.useRef(null);
2729
2819
  var sessionRef = React.useRef(props.sessionId);
2730
2820
  sessionRef.current = props.sessionId;
2731
2821
  var supportsRules = !!(configState && configState.capabilities && configState.capabilities.rules === true);
2732
2822
  var supportsAssembly = !!(configState && configState.capabilities && configState.capabilities.assemble === true);
2823
+ var editingCapabilities = configState && configState.capabilities && configState.capabilities.editing;
2824
+ var supportsFaithful = Array.isArray(editingCapabilities) && editingCapabilities.indexOf('faithful') >= 0 && editingCapabilities.indexOf('rewrite') >= 0;
2825
+ var supportsSuggestions = Array.isArray(editingCapabilities) && editingCapabilities.indexOf('suggestions') >= 0;
2826
+ function supportsTarget(value) {
2827
+ var available = configState && configState.capabilities && configState.capabilities.targets;
2828
+ return Array.isArray(available) ? available.indexOf(value) >= 0 : value !== 'image-edit';
2829
+ }
2733
2830
 
2734
2831
  function promptRequest(operation, body, signal) {
2735
2832
  return fetch('/iris/api/prompt-optimizer/' + operation, {
@@ -2769,10 +2866,35 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2769
2866
  React.useEffect(function () {
2770
2867
  cancel();
2771
2868
  setResult(null);
2869
+ setSuggestions(null); setSelectedSuggestions([]); setSuggestionUndo([]);
2772
2870
  lastWrittenRef.current = null;
2773
2871
  resultSessionRef.current = null;
2774
2872
  }, [props.sessionId]);
2775
2873
 
2874
+ React.useEffect(function () {
2875
+ if (!open) return;
2876
+ var previous = document.activeElement;
2877
+ var dialog = panelRef.current;
2878
+ if (dialog) { dialog.showModal(); dialog.focus(); }
2879
+ function keyboard(event) {
2880
+ if (event.key === 'Escape') { event.preventDefault(); closePanel(); }
2881
+ if (event.key !== 'Tab' || !panelRef.current) return;
2882
+ var items = Array.from(panelRef.current.querySelectorAll('button:not(:disabled), textarea:not(:disabled), summary, input:not(:disabled)'))
2883
+ .filter(function (item) { return item.getClientRects().length > 0; });
2884
+ if (!items.length) return;
2885
+ var first = items[0], last = items[items.length - 1];
2886
+ if (event.shiftKey && (document.activeElement === first || document.activeElement === panelRef.current)) {
2887
+ event.preventDefault(); last.focus();
2888
+ } else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); }
2889
+ }
2890
+ window.addEventListener('keydown', keyboard);
2891
+ return function () {
2892
+ window.removeEventListener('keydown', keyboard);
2893
+ if (dialog && dialog.open) dialog.close();
2894
+ if (previous && previous.isConnected) previous.focus();
2895
+ };
2896
+ }, [open]);
2897
+
2776
2898
  function publishConfig(data) {
2777
2899
  setConfigState(data);
2778
2900
  try { window.dispatchEvent(new CustomEvent('iris-prompt-config-changed', { detail: data })); } catch (_) {}
@@ -2789,10 +2911,14 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2789
2911
 
2790
2912
  function optimize(mode) {
2791
2913
  mode = mode === 'assemble' ? 'assemble' : 'optimize';
2914
+ if (!supportsTarget(target)) { setNote('当前后端尚未支持此优化目标,请更新并重新载入'); return; }
2792
2915
  if (mode === 'assemble' && !supportsAssembly) { setNote('当前后端尚未支持只组装,请更新并重新载入'); return; }
2793
2916
  if (!draft || !draft.trim()) { setNote('请先在输入框中写入提示词'); return; }
2794
2917
  if (refCount) { setNote('当前版本暂不改写含 @ 或 / 结构化引用的草稿,以免引用失效'); return; }
2795
2918
  if (phase !== 'plain') { setNote('输入框正忙,请稍后再试'); return; }
2919
+ if (mode === 'optimize' && supportsFaithful && editing === 'faithful' && optimizationText.trim()) {
2920
+ setNote('忠实整理只调整分段;使用本次改写规则前,请选择“改写表达”。'); return;
2921
+ }
2796
2922
  if (abortRef.current) abortRef.current.abort();
2797
2923
  var controller = new AbortController();
2798
2924
  var requestSessionId = props.sessionId;
@@ -2800,11 +2926,13 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2800
2926
  setBusy(true);
2801
2927
  setNote(mode === 'assemble' ? '正在组装,不调用模型…' : '正在调用模型优化…这可能产生模型费用');
2802
2928
  setResult(null);
2929
+ setSuggestions(null); setSelectedSuggestions([]); setSuggestionUndo([]);
2803
2930
  lastWrittenRef.current = null;
2804
2931
  promptRequest('optimize', {
2805
2932
  text: draft,
2806
2933
  target: target,
2807
2934
  mode: mode,
2935
+ editing: mode === 'optimize' && supportsFaithful ? editing : undefined,
2808
2936
  rules: selectedRules(mode),
2809
2937
  sessionId: props.sessionId,
2810
2938
  route: selectedRoute || undefined
@@ -2812,6 +2940,9 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2812
2940
  if (abortRef.current !== controller || controller.signal.aborted) return;
2813
2941
  if (sessionRef.current !== requestSessionId) throw new Error('会话已切换,请在当前会话重新生成预览');
2814
2942
  if (!data.ok || typeof data.optimized !== 'string' || typeof data.original !== 'string') throw new Error('未收到完整优化结果');
2943
+ if (data.original !== draft) throw new Error('结果原稿与本次请求不一致');
2944
+ if (mode === 'optimize' && supportsFaithful && editing === 'faithful'
2945
+ && (data.editing?.strategy !== 'faithful' || data.editing.coverage !== 'exact' || data.editing.order !== 'preserved')) throw new Error('后端未确认完整的忠实整理结果');
2815
2946
  setResult(data);
2816
2947
  setEdited(data.optimized);
2817
2948
  resultSessionRef.current = requestSessionId;
@@ -2825,6 +2956,69 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2825
2956
  });
2826
2957
  }
2827
2958
 
2959
+ function requestSuggestions() {
2960
+ if (!supportsSuggestions || !result || busy || !validEdited()) return;
2961
+ if (resultSessionRef.current !== props.sessionId) { setNote('会话已切换,请重新生成预览'); return; }
2962
+ var snapshot = edited;
2963
+ var previousResult = result;
2964
+ var requestSessionId = props.sessionId;
2965
+ var controller = new AbortController();
2966
+ abortRef.current = controller;
2967
+ setBusy(true); setNote('正在获取可选建议,正文保持原样;这会调用文本模型。');
2968
+ promptRequest('optimize', { text: snapshot, target: result.target, mode: 'optimize', editing: 'suggestions', rules: [],
2969
+ sessionId: props.sessionId, route: selectedRoute || undefined }, controller.signal).then(function (data) {
2970
+ if (abortRef.current !== controller || controller.signal.aborted) return;
2971
+ if (sessionRef.current !== requestSessionId || resultRef.current !== previousResult || editedRef.current !== snapshot) {
2972
+ setNote('预览已变化,本次建议未载入;请针对当前文字重新获取。'); return;
2973
+ }
2974
+ var ids = [];
2975
+ if (!data.ok || data.original !== snapshot || data.optimized !== snapshot || data.target !== previousResult.target || data.editing?.strategy !== 'suggestions'
2976
+ || !Array.isArray(data.suggestions) || data.suggestions.length > 6 || !Array.isArray(data.questions) || data.questions.length > 6
2977
+ || !data.suggestions.every(function (item) {
2978
+ if (!item || typeof item.id !== 'string' || !/^suggestion-[1-6]$/.test(item.id) || ids.indexOf(item.id) >= 0
2979
+ || typeof item.label !== 'string' || !item.label.trim() || item.label.length > 80
2980
+ || typeof item.text !== 'string' || !item.text.trim() || item.text.length > 600) return false;
2981
+ ids.push(item.id); return true;
2982
+ }) || !data.questions.every(function (text) { return typeof text === 'string' && text.trim() && text.length <= 600; })) throw new Error('未收到完整补充建议');
2983
+ setSuggestions({ source: snapshot, items: data.suggestions, questions: data.questions, adopted: [] });
2984
+ setSelectedSuggestions([]); setSuggestionUndo([]);
2985
+ setNote('建议尚未加入正文;只有采纳所选建议才会改变预览。');
2986
+ }).catch(function (err) {
2987
+ if (abortRef.current !== controller) return;
2988
+ setNote(controller.signal.aborted ? '已取消,原有预览保留' : String(err && err.message || err));
2989
+ }).finally(function () {
2990
+ if (abortRef.current === controller) { abortRef.current = null; setBusy(false); }
2991
+ });
2992
+ }
2993
+
2994
+ function adoptSuggestions() {
2995
+ if (!suggestions || busy || !selectedSuggestions.length) return;
2996
+ var items = suggestions.items.filter(function (item) { return selectedSuggestions.indexOf(item.id) >= 0 && suggestions.adopted.indexOf(item.id) < 0; });
2997
+ if (!items.length) return;
2998
+ var next = edited + '\n\n' + items.map(function (item) { return item.text; }).join('\n\n');
2999
+ if (next.length > 16000) { setNote('采纳后将超过 16000 字符,请减少建议或手动编辑'); return; }
3000
+ var ids = items.map(function (item) { return item.id; });
3001
+ setSuggestionUndo(suggestionUndo.concat([{ before: edited, after: next, ids: ids }]));
3002
+ setSuggestions(Object.assign({}, suggestions, { adopted: suggestions.adopted.concat(ids) }));
3003
+ setEdited(next); editedRef.current = next; setSelectedSuggestions([]);
3004
+ setNote('已将所选建议加入预览,尚未写回;可以撤回上次采纳。');
3005
+ }
3006
+
3007
+ function undoSuggestions() {
3008
+ var last = suggestionUndo[suggestionUndo.length - 1];
3009
+ if (!last || busy) return;
3010
+ if (edited !== last.after) { setNote('预览已被手动修改;请手动移除建议,撤回不会覆盖你的编辑。'); return; }
3011
+ setEdited(last.before); editedRef.current = last.before;
3012
+ setSuggestionUndo(suggestionUndo.slice(0, -1));
3013
+ if (suggestions) setSuggestions(Object.assign({}, suggestions, { adopted: suggestions.adopted.filter(function (id) { return last.ids.indexOf(id) < 0; }) }));
3014
+ setNote('已撤回上次采纳,尚未写回输入框。');
3015
+ }
3016
+
3017
+ function editPreview(value) {
3018
+ setEdited(value); editedRef.current = value;
3019
+ setSuggestions(null); setSelectedSuggestions([]); setSuggestionUndo([]);
3020
+ }
3021
+
2828
3022
  function cancel() {
2829
3023
  if (abortRef.current) {
2830
3024
  abortRef.current.abort();
@@ -2922,6 +3116,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2922
3116
  var reasoningLabel = reasoningSetting === 'off-if-supported' ? '支持时关闭' : reasoningSetting === 'provider-default' ? '供应商默认' : reasoningSetting === 'inherit' ? '跟随会话' : reasoningSetting;
2923
3117
  var outputBudget = configState && configState.config && configState.config.generation && configState.config.generation.maxOutputTokens || 1200;
2924
3118
  var changes = result ? promptChanges(result.original, edited) : null;
3119
+ var targetInfo = PROMPT_TARGET_OPTIONS.find(function (item) { return item.value === target; }) || PROMPT_TARGET_OPTIONS[0];
3120
+ var resultTarget = result && PROMPT_TARGET_OPTIONS.find(function (item) { return item.value === result.target; });
3121
+ var config = configState && configState.config;
3122
+ var targetTemplate = config && config.targets && config.targets[target];
2925
3123
  function ruleField(label, value, update, placeholder) {
2926
3124
  return React.createElement('label', { className: 'iris-po-field' }, React.createElement('span', {}, label),
2927
3125
  React.createElement('textarea', { value: value, disabled: busy || !supportsRules, placeholder: placeholder,
@@ -2931,20 +3129,43 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2931
3129
  var panel = null;
2932
3130
  if (open) {
2933
3131
  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(); } },
3132
+ React.createElement('dialog', { ref: panelRef, tabIndex: -1, className: 'iris-po-panel', role: 'dialog', 'aria-modal': 'true', 'aria-label': 'Iris 提示词优化',
3133
+ onCancel: function (event) { event.preventDefault(); closePanel(); },
3134
+ onClick: function (event) {
3135
+ event.stopPropagation();
3136
+ if (event.target !== event.currentTarget) return;
3137
+ var bounds = event.currentTarget.getBoundingClientRect();
3138
+ if (event.clientX < bounds.left || event.clientX > bounds.right || event.clientY < bounds.top || event.clientY > bounds.bottom) closePanel();
3139
+ } },
2936
3140
  React.createElement('div', { className: 'iris-po-head' },
2937
3141
  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'),
3142
+ React.createElement('button', { className: 'iris-po-close', onClick: closePanel, title: '关闭', 'aria-label': '关闭提示词优化' }, '✕')),
3143
+ React.createElement('div', { className: 'iris-po-intro' },
3144
+ React.createElement('div', { className: 'iris-po-muted' }, '按用途整理草稿,先预览和编辑,再决定是否写回。'),
3145
+ React.createElement('div', { className: 'iris-po-principles' },
3146
+ React.createElement('span', {}, '保留原意'), React.createElement('span', {}, '可查看改动'), React.createElement('span', {}, '确认后写回'))),
3147
+ React.createElement('details', { className: 'iris-po-section', open: generalOpen,
3148
+ onToggle: function (event) { generalPair[1](event.currentTarget.open); } },
3149
+ React.createElement('summary', {}, '一般功能',
3150
+ React.createElement('span', { className: 'iris-po-section-caption' }, targetInfo.label + ' · 提示词优化' + (result ? ' · 有预览' : ''))),
3151
+ React.createElement('div', { className: 'iris-po-section-body' },
3152
+ React.createElement('div', { className: 'iris-po-targets', role: 'group', 'aria-label': '优化目标' },
3153
+ PROMPT_TARGET_OPTIONS.map(function (item) {
3154
+ return React.createElement('button', { key: item.value, type: 'button', className: 'iris-po-target',
3155
+ 'aria-pressed': target === item.value, value: item.value,
3156
+ disabled: busy || !supportsTarget(item.value), title: supportsTarget(item.value) ? item.description : '当前后端未提供此目标',
3157
+ onClick: function () { setTarget(item.value); } }, item.label);
3158
+ })),
3159
+ React.createElement('div', { className: 'iris-po-muted' }, targetInfo.description),
3160
+ supportsFaithful ? React.createElement('div', { className: 'iris-po-editing', role: 'group', 'aria-label': '编辑方式' },
3161
+ ['faithful', 'rewrite'].map(function (strategy) {
3162
+ return React.createElement('label', { key: strategy }, React.createElement('input', { type: 'radio', name: 'iris-prompt-editing',
3163
+ value: strategy, checked: editing === strategy, disabled: busy, onChange: function () { editingPair[1](strategy); } }), strategy === 'faithful' ? '忠实整理(默认)' : '改写表达');
3164
+ }),
3165
+ React.createElement('div', { className: 'iris-po-muted' }, editing === 'faithful' ? '保持原文和句序,只调整分组与分段;不补充细节。' : '按本次规则改写,结果需逐项核对;未授权内容保持原意。')) : null,
3166
+ React.createElement('div', { className: 'iris-po-source' },
3167
+ React.createElement('div', { className: 'iris-po-source-head' }, '当前草稿', React.createElement('span', {}, (draft || '').length + ' 字符')),
3168
+ React.createElement('p', { className: 'iris-po-source-text' }, draft || '先在对话输入框写下你的想法。')),
2948
3169
  React.createElement('details', { className: 'iris-po-config' },
2949
3170
  React.createElement('summary', {}, '本次规则(可选)'),
2950
3171
  React.createElement('div', { className: 'iris-po-rules' },
@@ -2953,12 +3174,32 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2953
3174
  ruleField('输出后缀 · 原样加入结果末尾', suffixText, setSuffixText, '例如:禁止添加文字和水印。')),
2954
3175
  React.createElement('div', { className: 'iris-po-muted' }, supportsRules ? '非空内容表示本次选择;只组装保留原稿,只添加前后缀。刷新后重置,不修改 JSON 配置。' : '当前后端尚未支持规则,请更新后重新载入。')),
2955
3176
  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 ? '处理中…' : '智能优化'),
3177
+ 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
3178
  React.createElement('button', { className: 'iris-po-btn', disabled: busy || !supportsAssembly || !draft || !draft.trim() || phase !== 'plain' || refCount > 0, onClick: function () { optimize('assemble'); } }, '只组装(不调用模型)'),
2958
3179
  busy ? React.createElement('button', { className: 'iris-po-btn', onClick: cancel }, '取消') : null),
3180
+ React.createElement('div', { className: 'iris-po-muted' }, '智能优化调用文本模型,可能产生费用;只组装原稿与前后缀。'),
2959
3181
  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); } }),
3182
+ React.createElement('div', { className: 'iris-po-result-label' }, '结果预览 · ' + (resultTarget ? resultTarget.label + ' · ' : '') + '可编辑 · ' + edited.length + ' 字符'),
3183
+ React.createElement('textarea', { 'aria-label': '结果预览', value: edited, onChange: function (event) { editPreview(event.target.value); } }),
3184
+ result.editing?.strategy === 'faithful' ? React.createElement('div', { className: 'iris-po-muted' }, '本次整理保留了全部原文片段与句序;手动编辑或采纳建议后,请重新核对内容。') : null,
3185
+ supportsSuggestions ? React.createElement('details', { className: 'iris-po-config' },
3186
+ React.createElement('summary', {}, '可选补充与待确认'),
3187
+ React.createElement('div', { className: 'iris-po-muted' }, '建议独立生成,默认未采纳;不会自动加入正文或写回。每次获取会调用文本模型。'),
3188
+ React.createElement('button', { className: 'iris-po-btn', disabled: busy || !edited.trim(), onClick: requestSuggestions }, '获取补充建议'),
3189
+ suggestions ? React.createElement('div', {},
3190
+ suggestions.items.map(function (item) {
3191
+ var adopted = suggestions.adopted.indexOf(item.id) >= 0;
3192
+ return React.createElement('div', { className: 'iris-po-suggestion', key: item.id },
3193
+ React.createElement('label', {}, React.createElement('input', { type: 'checkbox', checked: adopted || selectedSuggestions.indexOf(item.id) >= 0, disabled: busy || adopted,
3194
+ onChange: function (event) { setSelectedSuggestions(event.target.checked ? selectedSuggestions.concat([item.id]) : selectedSuggestions.filter(function (id) { return id !== item.id; })); } }), item.label + (adopted ? ' · 已加入预览' : '')),
3195
+ React.createElement('p', {}, item.text));
3196
+ }),
3197
+ !suggestions.items.length ? React.createElement('div', { className: 'iris-po-muted' }, '没有额外补充建议。') : null,
3198
+ suggestions.questions.length ? React.createElement('div', { className: 'iris-po-suggestion' }, React.createElement('strong', {}, '待确认 · 尚未作为要求'),
3199
+ suggestions.questions.map(function (question, index) { return React.createElement('p', { key: index }, question); })) : null,
3200
+ React.createElement('div', { className: 'iris-po-actions' },
3201
+ React.createElement('button', { className: 'iris-po-btn', disabled: busy || !selectedSuggestions.length, onClick: adoptSuggestions }, '采纳所选建议'),
3202
+ React.createElement('button', { className: 'iris-po-btn', disabled: busy || !suggestionUndo.length, onClick: undoSuggestions }, '撤回上次采纳'))) : null) : null,
2962
3203
  React.createElement('details', { className: 'iris-po-config' }, React.createElement('summary', {}, '原文与改动'),
2963
3204
  React.createElement('textarea', { 'aria-label': '优化前原文', readOnly: true, value: result.original }),
2964
3205
  React.createElement('div', { className: 'iris-po-muted' }, '以下折叠相同首尾,显示中间替换区:'),
@@ -2977,9 +3218,14 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
2977
3218
  React.createElement('button', { className: 'iris-po-btn', onClick: copyResult }, '复制'),
2978
3219
  React.createElement('button', { className: 'iris-po-btn', disabled: busy, onClick: function () { optimize(result.mode); } }, '重新生成预览'),
2979
3220
  React.createElement('button', { className: 'iris-po-btn', onClick: function () { replaceDraft(result.original); } }, '恢复原文'))) : null,
2980
- note ? React.createElement('div', { className: 'iris-po-note' }, note) : null,
3221
+ note ? React.createElement('div', { className: 'iris-po-note', role: 'status' }, note) : null)),
2981
3222
  React.createElement('details', { className: 'iris-po-config' },
2982
- React.createElement('summary', {}, 'JSON 配置'),
3223
+ React.createElement('summary', {}, '指令与模型设置'),
3224
+ React.createElement('div', { className: 'iris-po-route', title: routeLabel }, '模型:' + routeLabel + ' · 思考:' + reasoningLabel + ' · 输出:' + outputBudget + ' tokens'),
3225
+ React.createElement('details', {}, React.createElement('summary', {}, '查看当前功能的优化指令'),
3226
+ React.createElement('div', { className: 'iris-po-template' }, config && config.systemPrompt || '配置尚未载入'),
3227
+ React.createElement('div', { className: 'iris-po-template' }, targetTemplate || '当前后端未提供此目标模板')),
3228
+ React.createElement('div', { className: 'iris-po-result-label' }, 'JSON 配置'),
2983
3229
  React.createElement('div', { className: 'iris-po-muted' }, '可导出后修改 systemPrompt、targets、route 和 generation,再重新导入。'),
2984
3230
  React.createElement('div', { className: 'iris-po-actions' },
2985
3231
  React.createElement('button', { className: 'iris-po-btn', onClick: exportConfig }, '导出'),
package/lib/config.js CHANGED
@@ -66,7 +66,7 @@ function normalizeStoredProvider(provider) {
66
66
  const changed = provider.mediaProtocol !== selected.mediaProtocol
67
67
  || (provider.protocolInferred === true) !== selected.protocolInferred;
68
68
  Object.assign(provider, selected);
69
- return models.migrateProviderModels(provider) || changed;
69
+ return changed;
70
70
  }
71
71
 
72
72
  export function irisHome() {
@@ -565,10 +565,19 @@ export function setProviderModels(id, modelEntries) {
565
565
  export function mergeDiscoveredModels(id, modelEntries) {
566
566
  const provider = load().providers.find((p) => p.id === id);
567
567
  if (!provider) return null;
568
- const merged = new Map(models.providerModels(provider).map((m) => [m.id, m]));
568
+ const merged = new Map(models.providerModels(provider).map((m) => {
569
+ const raw = provider.models?.find((entry) => String(typeof entry === 'string' ? entry : entry?.id || '').trim() === m.id);
570
+ // 读取投影的 [] 不能伪造成用户明确清空;未标注条目以后仍可接收可信元数据。
571
+ if (!Array.isArray(raw?.capabilities) && !models.hasConfiguredModelCapabilities(provider, m.id)) {
572
+ const entry = { ...m };
573
+ delete entry.capabilities;
574
+ return [m.id, entry];
575
+ }
576
+ return [m.id, m];
577
+ }));
569
578
  for (const model of modelEntries) {
570
579
  const previous = merged.get(model.id);
571
- if (previous?.source === 'manual') continue;
580
+ if (previous && models.hasConfiguredModelCapabilities(provider, model.id)) continue;
572
581
  merged.set(model.id, { ...previous, ...model, source: 'discovered' });
573
582
  }
574
583
  return setProviderModels(id, [...merged.values()]);
@@ -631,7 +640,7 @@ export function addProviderModel(id, modelId, capabilities) {
631
640
  if (!Array.isArray(p.models)) p.models = [];
632
641
  let entry = findModelEntry(p, name);
633
642
  if (!entry) {
634
- entry = { id: name, capabilities: Array.isArray(capabilities) ? capabilities : [], source: 'manual' };
643
+ entry = { id: name, capabilities: Array.isArray(capabilities) ? capabilities : undefined, source: 'manual' };
635
644
  p.models.push(entry);
636
645
  } else if (Array.isArray(capabilities)) {
637
646
  entry.capabilities = capabilities;