@mokuyoaxis/dsh-iris 0.1.0 → 0.1.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 (46) hide show
  1. package/CHANGELOG.md +72 -1
  2. package/README.en.md +245 -0
  3. package/README.md +81 -13
  4. package/cordis.patch.yml +1 -1
  5. package/docs/ROADMAP.md +49 -21
  6. package/docs/assets/diagrams/iris-architecture.drawio +1 -0
  7. package/docs/assets/diagrams/iris-architecture.png +0 -0
  8. package/docs/assets/diagrams/iris-architecture.svg +63 -0
  9. package/docs/assets/diagrams/iris-task-lifecycle.drawio +1 -0
  10. package/docs/assets/diagrams/iris-task-lifecycle.png +0 -0
  11. package/docs/assets/diagrams/iris-task-lifecycle.svg +64 -0
  12. package/docs/assets/diagrams/iris-workflow-compose.drawio +1 -0
  13. package/docs/assets/diagrams/iris-workflow-compose.png +0 -0
  14. package/docs/assets/diagrams/iris-workflow-compose.svg +41 -0
  15. package/docs/assets/examples/iris-flower-generated.webp +0 -0
  16. package/docs/assets/screenshots/image-task-succeeded.webp +0 -0
  17. package/docs/assets/screenshots/prompt-general-after.webp +0 -0
  18. package/docs/assets/screenshots/prompt-general-before.webp +0 -0
  19. package/docs/assets/screenshots/prompt-general-preview.webp +0 -0
  20. package/docs/assets/screenshots/prompt-image-before.webp +0 -0
  21. package/docs/assets/screenshots/prompt-image-preview.webp +0 -0
  22. package/docs/releases/0.1.1.md +51 -0
  23. package/docs/releases/0.1.2.md +26 -0
  24. package/docs/screenshots.md +34 -0
  25. package/lib/actions.js +33 -27
  26. package/lib/adapters.js +42 -26
  27. package/lib/api.js +73 -20
  28. package/lib/bundled-skills.js +62 -0
  29. package/lib/capability.js +1 -1
  30. package/lib/client.js +378 -26
  31. package/lib/config.js +56 -22
  32. package/lib/guard.js +37 -24
  33. package/lib/index.js +21 -9
  34. package/lib/locate.js +1 -1
  35. package/lib/media-probe.js +4 -4
  36. package/lib/media.js +1 -1
  37. package/lib/private-storage.js +77 -0
  38. package/lib/prompt-optimizer-config.js +170 -0
  39. package/lib/prompt-optimizer.js +244 -0
  40. package/lib/provider-protocol.js +25 -0
  41. package/lib/render.js +3 -2
  42. package/lib/tasks.js +21 -13
  43. package/lib/vision.js +2 -2
  44. package/package.json +25 -17
  45. package/screenshots.json +6 -0
  46. package/user_guide.md +97 -11
package/lib/client.js CHANGED
@@ -16,7 +16,7 @@
16
16
  * webServer 前缀模式),client 侧 fetch 轮询。全走标量,
17
17
  * apiKey 永不明文,产物给授权播放链接。
18
18
  */
19
- window.__ModuleLoader__.load({ id: 'dsh-iris', factory: (require) => {
19
+ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) => {
20
20
  var module = { exports: {} };
21
21
  var exports = module.exports;
22
22
 
@@ -111,7 +111,7 @@ window.__ModuleLoader__.load({ id: 'dsh-iris', factory: (require) => {
111
111
  '.iris-bubble.lit .iris-bubble-btn { border-color: var(--dsw-alias-state-success-primary, #6fcf6f); box-shadow: 0 0 14px var(--dsw-alias-state-success-primary, #6fcf6f); }',
112
112
  '.iris-bubble.dim .iris-bubble-btn { opacity: 0.55; border-color: var(--dsw-alias-border-l1, #3a3a44); }',
113
113
  '.iris-bubble-badge { position: absolute; top: -2px; right: -2px; min-width: 16px; height: 16px; border-radius: 8px; background: var(--dsw-alias-state-warn-primary, #d9a941); color: #1a1a1e; font-size: 10px; font-weight: 700; display: flex; align-items: center; justify-content: center; padding: 0 3px; }',
114
- '.iris-bubble-panel { position: fixed; z-index: 9998; width: 360px; max-height: min(520px, 70vh); overflow: auto; background: var(--dsw-alias-bg-layer-1, #26262e); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 12px; padding: 12px 14px; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.45); }',
114
+ '.iris-bubble-panel { position: fixed; z-index: 9998; width: 360px; max-height: min(520px, 70vh); overflow: auto; background: var(--dsw-alias-bg-layer-1, #26262e); border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 12px; padding: 12px 14px; font-size: 12px; line-height: 1.45; box-shadow: 0 8px 30px rgba(0, 0, 0, 0.45); }',
115
115
  '.iris-bubble-panel-head { position: absolute; top: 6px; right: 6px; z-index: 2; }',
116
116
  '.iris-bubble-close { border: none; background: transparent; color: var(--dsw-alias-label-secondary, #9a9a9a); font-size: 14px; cursor: pointer; padding: 4px 6px; }',
117
117
  '.iris-bubble-close:hover { color: var(--dsw-alias-label-primary, #e6e6e6); }',
@@ -160,7 +160,48 @@ window.__ModuleLoader__.load({ id: 'dsh-iris', factory: (require) => {
160
160
  '.iris-ff-list { display: grid; gap: 2px; max-height: 160px; overflow: auto; border: 1px solid var(--dsw-alias-border-l1, #3a3a44); border-radius: 6px; padding: 4px; background: var(--dsw-alias-bg-layer-2, #32323c); }',
161
161
  '.iris-ff-item { display: flex; align-items: center; gap: 6px; font-size: 11px; cursor: pointer; padding: 2px 4px; border-radius: 4px; }',
162
162
  '.iris-ff-item:hover { background: var(--dsw-alias-bg-layer-1, #26262e); }',
163
- '.iris-ff-item .iris-ff-src { color: var(--dsw-alias-brand-primary, #7aa2f7); font-size: 10px; }'
163
+ '.iris-ff-item .iris-ff-src { color: var(--dsw-alias-brand-primary, #7aa2f7); font-size: 10px; }',
164
+ /* 对话框级提示词优化器:桌面浮层 + 窄屏底部 Sheet */
165
+ '.iris-po-wrap { position: relative; display: inline-flex; flex: 0 0 auto; }',
166
+ '.iris-po-trigger { appearance: none; -webkit-appearance: none; width: 30px; height: 30px; padding: 0; display: inline-flex; align-items: center; justify-content: center; border: 0; outline: 0; background: transparent; color: inherit; font-size: 19px; line-height: 1; cursor: pointer; box-shadow: none; filter: drop-shadow(0 3px 7px rgba(91,113,220,.22)); transition: transform .18s ease, filter .18s ease, opacity .18s ease; }',
167
+ '.iris-po-trigger:hover { transform: translateY(-2px) scale(1.10); filter: drop-shadow(0 5px 10px rgba(112,137,255,.42)); }',
168
+ '.iris-po-trigger:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary, #7aa2f7); outline-offset: 2px; }',
169
+ '.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; }',
170
+ '.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; }',
171
+ '.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); }',
172
+ '.iris-po-title { display: inline-flex; align-items: center; gap: 9px; font-size: 14px; letter-spacing: .01em; }',
173
+ '.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)); }',
174
+ '.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; }',
175
+ '.iris-po-close:hover { background: rgba(255,255,255,.10); color: var(--dsw-alias-label-primary, #eeeef2); }',
176
+ '.iris-po-muted, .iris-po-route, .iris-po-note { font-size: 11px; line-height: 1.55; color: var(--dsw-alias-label-secondary, #a6a6b2); word-break: break-word; }',
177
+ '.iris-po-route { padding: 7px 10px; border-radius: 10px; background: rgba(255,255,255,.045); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }',
178
+ '.iris-po-note { padding: 8px 10px; border-radius: 10px; color: var(--dsw-alias-state-warn-primary, #dfbd69); background: rgba(217,169,65,.08); }',
179
+ '.iris-po-field { display: grid; gap: 6px; font-size: 11px; color: var(--dsw-alias-label-secondary, #a6a6b2); }',
180
+ '.iris-po-field select, .iris-po-result textarea { width: 100%; box-sizing: border-box; background: rgba(255,255,255,.055); color: var(--dsw-alias-label-primary, #eeeef2); border: 1px solid rgba(150,164,215,.20); border-radius: 11px; padding: 9px 11px; font: inherit; font-size: 12px; outline: none; transition: border-color .15s, background .15s, box-shadow .15s; }',
181
+ '.iris-po-field select: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); }',
182
+ '.iris-po-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }',
183
+ '.iris-po-btn, .iris-po-primary { min-height: 34px; border: 1px solid rgba(150,164,215,.20); border-radius: 10px; padding: 6px 11px; font: inherit; font-size: 11px; cursor: pointer; transition: transform .15s, background .15s, border-color .15s; }',
184
+ '.iris-po-btn { background: rgba(255,255,255,.045); color: var(--dsw-alias-label-secondary, #b0b0bb); }',
185
+ '.iris-po-btn:hover { background: rgba(255,255,255,.085); color: var(--dsw-alias-label-primary, #eeeef2); border-color: rgba(150,174,255,.50); }',
186
+ '.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); }',
187
+ '.iris-po-primary:hover { transform: translateY(-1px); box-shadow: 0 8px 22px rgba(76,92,188,.32); }',
188
+ '.iris-po-primary:disabled, .iris-po-btn:disabled { opacity: .45; cursor: not-allowed; transform: none; box-shadow: none; }',
189
+ '.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); }',
190
+ '.iris-po-result-label { font-size: 11px; font-weight: 650; letter-spacing: .02em; }',
191
+ '.iris-po-result textarea { min-height: 156px; max-height: 34dvh; resize: vertical; line-height: 1.58; }',
192
+ '.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; }',
193
+ '.iris-po-config summary { cursor: pointer; color: var(--dsw-alias-label-secondary, #a6a6b2); }',
194
+ '.iris-po-config[open] summary { margin-bottom: 9px; color: var(--dsw-alias-label-primary, #eeeef2); }',
195
+ '.iris-po-config .iris-po-actions { margin: 9px 0; }',
196
+ '.iris-po-btn.danger:hover { color: var(--dsw-alias-state-error-primary, #eb7d86); border-color: rgba(235,125,134,.55); background: rgba(235,125,134,.08); }',
197
+ '.iris-po-file { display: none; }',
198
+ '.iris-po-disable { justify-self: start; border: 0; padding: 2px 0; background: transparent; color: var(--dsw-alias-label-tertiary, #777986); font: inherit; font-size: 10px; cursor: pointer; }',
199
+ '.iris-po-disable:hover { color: var(--dsw-alias-state-error-primary, #eb7d86); }',
200
+ '@keyframes iris-po-fade { from { opacity: 0; } to { opacity: 1; } }',
201
+ '@keyframes iris-po-rise { from { opacity: 0; transform: translateY(12px) scale(.985); } to { opacity: 1; transform: translateY(0) scale(1); } }',
202
+ '@media (max-width: 640px) { .iris-po-backdrop { background: rgba(6,8,14,.48); backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px); } .iris-po-panel { left: max(8px, env(safe-area-inset-left)); right: max(8px, env(safe-area-inset-right)); bottom: max(8px, env(safe-area-inset-bottom)); width: auto; max-height: calc(100dvh - max(16px, env(safe-area-inset-top))); padding: 16px; gap: 11px; border-radius: 22px; } .iris-po-head { top: -16px; margin: -16px -16px 0; padding: 14px 16px 10px; } .iris-po-btn, .iris-po-primary { min-height: 42px; flex: 1 1 auto; } .iris-po-actions .iris-po-primary:first-child { flex-basis: 100%; } .iris-po-result textarea { min-height: 122px; max-height: 28dvh; resize: none; } .iris-po-route { white-space: normal; } }',
203
+ '@media (max-width: 360px) { .iris-po-panel { left: 4px; right: 4px; bottom: max(4px, env(safe-area-inset-bottom)); padding: 13px; border-radius: 18px; } .iris-po-head { top: -13px; margin: -13px -13px 0; padding: 12px 13px 8px; } .iris-po-actions { gap: 6px; } }',
204
+ '@media (prefers-reduced-motion: reduce) { .iris-po-panel, .iris-po-backdrop { animation: none; } .iris-po-trigger, .iris-po-btn, .iris-po-primary { transition: none; } }',
164
205
  ].join('\n');
165
206
 
166
207
  function adoptStyles() {
@@ -250,6 +291,15 @@ window.__ModuleLoader__.load({ id: 'dsh-iris', factory: (require) => {
250
291
  return sharedState;
251
292
  }
252
293
 
294
+ /** 功能卡就绪态直接来自共享 Iris 状态,不再等用户点开卡片后才加载。 */
295
+ function capabilityReady(state, capability) {
296
+ if (!capability) return true;
297
+ var providers = (state && state.providers) || [];
298
+ return providers.some(function (p) {
299
+ return p.enabled && p.apiKeyHint && Array.isArray(p.capabilities) && p.capabilities.indexOf(capability) >= 0;
300
+ });
301
+ }
302
+
253
303
  /* ---- 任务详情抽屉:按需拉取 /iris/api/task/:id ---- */
254
304
  function useTaskDetail(taskId) {
255
305
  var pair = React.useState(null);
@@ -438,15 +488,15 @@ window.__ModuleLoader__.load({ id: 'dsh-iris', factory: (require) => {
438
488
  var models = p.models || [];
439
489
  function verBadge(m, c) {
440
490
  var v = m.verified && m.verified[c];
441
- if (!v) return React.createElement('span', { className: 'iris-pm-ver', title: '未实测' }, c + '?');
442
- return React.createElement('span', { className: 'iris-pm-ver ' + (v.ok ? 'ok' : 'bad'), title: (v.ok ? '实测通过 ' : '实测未通过 ') + (v.note || '') }, c + (v.ok ? '✓' : '✗'));
491
+ var cls = 'iris-pm-ver' + (v ? (' ' + (v.ok ? 'ok' : 'bad')) : '');
492
+ var title = v ? ((v.ok ? '实测通过:' : '实测未通过:') + (v.note || '')) : '点击单独验证此能力';
493
+ return React.createElement('button', { key: c, className: cls, title: title, onClick: function () { props.testModel(p, m, c); } }, c + (v ? (v.ok ? '✓' : '✗') : '?'));
443
494
  }
444
495
  var rows = models.map(function (m) {
445
496
  var caps = m.capabilities || [];
446
497
  return React.createElement('div', { key: m.id, className: 'iris-pm-mrow' },
447
498
  React.createElement('span', { className: 'iris-pm-mname' }, m.id, m.source === 'manual' ? React.createElement('span', { className: 'iris-pm-mtag' }, '手动') : null),
448
499
  React.createElement('span', { className: 'iris-pm-mcaps' }, caps.map(function (c) { return verBadge(m, c); })),
449
- React.createElement('button', { className: 'iris-pm-btn', title: '逐能力实测(image/tts 产生真实调用)', onClick: function () { props.testModel(p, m); } }, '测'),
450
500
  React.createElement('button', { className: 'iris-pm-btn danger', title: '从池移除', onClick: function () { props.removeModel(p, m); } }, '✕'));
451
501
  });
452
502
  return React.createElement('div', { className: 'iris-pm-pool' },
@@ -466,7 +516,7 @@ window.__ModuleLoader__.load({ id: 'dsh-iris', factory: (require) => {
466
516
  var addingPair = React.useState(false);
467
517
  var adding = addingPair[0];
468
518
  var setAdding = addingPair[1];
469
- var newForm = React.useState({ name: '', baseUrl: '', apiKey: '', type: 'openai' });
519
+ var newForm = React.useState({ name: '', baseUrl: '', apiKey: '', type: 'openai', mediaProtocol: 'auto' });
470
520
  var notePair = React.useState('');
471
521
  var note = notePair[0];
472
522
  var setNote = notePair[1];
@@ -493,23 +543,25 @@ window.__ModuleLoader__.load({ id: 'dsh-iris', factory: (require) => {
493
543
  postAction('providers_remove', { id: p.id }).then(function (r) { if (r.ok) refresh(); else setNote(r.d.error || '删除失败'); });
494
544
  }
495
545
  function testVision(p) {
546
+ if (!window.confirm('视觉实测会向供应商发送一张内置测试图,可能产生费用。继续吗?')) return;
496
547
  setNote('测试「' + (p.name || p.id) + '」的视觉能力…');
497
- postAction('providers_test_vision', { id: p.id }).then(function (r) { setNote(r.d.text || r.d.error || '测试完成'); refresh(); });
548
+ postAction('providers_test_vision', { id: p.id, confirm_paid: true }).then(function (r) { setNote(r.d.text || r.d.error || '测试完成'); refresh(); });
498
549
  }
499
550
  function discover(p) {
500
551
  setNote('正在发现「' + (p.name || p.id) + '」的模型…');
501
552
  postAction('providers_discover', { id: p.id }).then(function (r) { setNote(r.d.text || r.d.error || '发现完成'); refresh(); });
502
553
  }
503
- function testModel(p, m) {
504
- var caps = (m.capabilities || []).length ? m.capabilities : ['vision'];
505
- setNote('实测 ' + m.id + '(' + caps.join('/') + ',image/tts 会产生真实调用)…');
506
- // 逐个能力测,串行;每个存 verified
507
- caps.reduce(function (chain, c) {
508
- return chain.then(function () {
509
- return postAction('providers_test_model', { id: p.id, model_id: m.id, capability: c })
510
- .then(function (r) { setNote(r.d.text || r.d.error || ''); });
511
- });
512
- }, Promise.resolve()).then(function () { refresh(); });
554
+ function testModel(p, m, capability) {
555
+ var noProbe = capability === 'video-gen' || capability === 'transcribe';
556
+ if (!noProbe && !window.confirm('将用模型「' + m.id + '」发起一次真实的 ' + capability + ' 请求,可能产生费用。继续吗?')) return;
557
+ setNote('验证 ' + m.id + ' · ' + capability + '…');
558
+ postAction('providers_test_model', { id: p.id, model_id: m.id, capability: capability, confirm_paid: !noProbe })
559
+ .then(function (r) { setNote(r.d.text || r.d.error || ''); refresh(); });
560
+ }
561
+ function setProtocol(p, value) {
562
+ postAction('providers_upsert', { id: p.id, mediaProtocol: value }).then(function (r) {
563
+ setNote(r.ok ? '媒体协议已更新' : (r.d.error || '更新失败')); refresh();
564
+ });
513
565
  }
514
566
  function removeModel(p, m) {
515
567
  if (!window.confirm('从池中移除模型「' + m.id + '」?')) return;
@@ -525,6 +577,9 @@ window.__ModuleLoader__.load({ id: 'dsh-iris', factory: (require) => {
525
577
  if (open) {
526
578
  body = React.createElement('div', { className: 'iris-pm-body' },
527
579
  React.createElement('div', { className: 'iris-pm-note' }, 'Key ' + (p.apiKeyHint || '未配置') + ' · ' + (p.type || '') + ' · ' + (p.mediaProtocol || '')),
580
+ React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, '媒体协议'), React.createElement('select', { value: p.mediaProtocol || 'openai-images', onChange: function (e) { setProtocol(p, e.target.value); } },
581
+ React.createElement('option', { value: 'dashscope' }, 'DashScope(仅阿里云官方 HTTPS)'),
582
+ React.createElement('option', { value: 'openai-images' }, 'OpenAI Images 兼容'))),
528
583
  React.createElement('div', { className: 'iris-pm-note' }, '能力: ' + ((p.capabilities || []).join(' / ') || '无')),
529
584
  React.createElement(ModelPool, { provider: p, onNote: setNote, onDone: refresh, testModel: testModel, removeModel: removeModel, addModel: addModel }),
530
585
  React.createElement('div', { className: 'iris-wb-row' },
@@ -544,6 +599,10 @@ window.__ModuleLoader__.load({ id: 'dsh-iris', factory: (require) => {
544
599
  React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, '名称'), React.createElement('input', { value: newForm[0].name, placeholder: '如 阿里云百炼', onChange: function (e) { newForm[1]({ ...newForm[0], name: e.target.value }); } })),
545
600
  React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, 'Base URL *'), React.createElement('input', { value: newForm[0].baseUrl, placeholder: 'https://dashscope.aliyuncs.com/compatible-mode/v1', onChange: function (e) { newForm[1]({ ...newForm[0], baseUrl: e.target.value }); } })),
546
601
  React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, 'API Key *'), React.createElement('input', { type: 'password', value: newForm[0].apiKey, placeholder: 'sk-...', onChange: function (e) { newForm[1]({ ...newForm[0], apiKey: e.target.value }); } })),
602
+ React.createElement('div', { className: 'iris-pm-field' }, React.createElement('label', {}, '媒体协议'), React.createElement('select', { value: newForm[0].mediaProtocol, onChange: function (e) { newForm[1]({ ...newForm[0], mediaProtocol: e.target.value }); } },
603
+ React.createElement('option', { value: 'auto' }, '自动(按 Base URL 安全判断)'),
604
+ React.createElement('option', { value: 'dashscope' }, 'DashScope(阿里云官方)'),
605
+ React.createElement('option', { value: 'openai-images' }, 'OpenAI Images 兼容'))),
547
606
  React.createElement('div', { className: 'iris-wb-row' },
548
607
  React.createElement('button', { className: 'iris-act-run', onClick: addProvider }, '保存'),
549
608
  React.createElement('button', { className: 'iris-pm-btn', onClick: function () { setAdding(false); } }, '取消')));
@@ -641,6 +700,7 @@ window.__ModuleLoader__.load({ id: 'dsh-iris', factory: (require) => {
641
700
  var modelPair = React.useState(null); // { assigned, options:[{id,providerId}], tested }
642
701
  var modelInfo = modelPair[0];
643
702
  var setModelInfo = modelPair[1];
703
+ var irisState = useIrisState(5000);
644
704
  function setVal(key, value) { var nv = {}; Object.keys(vals).forEach(function (k) { nv[k] = vals[k]; }); nv[key] = value; setVals(nv); }
645
705
  function loadModels() {
646
706
  if (!capability) return;
@@ -720,11 +780,13 @@ window.__ModuleLoader__.load({ id: 'dsh-iris', factory: (require) => {
720
780
  run === 'running' ? '执行中…' : '执行'),
721
781
  result);
722
782
  }
723
- var headLit = !capability || (modelInfo && modelInfo.loaded && modelInfo.options.length > 0);
783
+ var headError = !!(run && run !== 'running' && run.ok === false);
784
+ var headLit = !capability || (capabilityReady(irisState, capability) && !headError);
785
+ var headTitle = headError ? '最近一次执行出错' : (headLit ? 'API 与能力已配置' : '缺少可用 API 或能力模型');
724
786
  return React.createElement('div', { className: 'iris-act-card' + (open ? ' open' : '') + (headLit ? ' lit' : ' dim') },
725
787
  React.createElement('div', { className: 'iris-act-head', onClick: function () { setOpen(!open); } },
726
788
  React.createElement('span', { className: 'iris-act-title' }, title),
727
- capability ? React.createElement('span', { className: headLit ? 'iris-pm-dot lit' : 'iris-pm-dot', title: headLit ? '模型可用' : '无可用模型' }, '●') : null,
789
+ capability ? React.createElement('span', { className: headLit ? 'iris-pm-dot lit' : 'iris-pm-dot', title: headTitle }, '●') : null,
728
790
  React.createElement('span', { className: 'iris-act-arrow' }, '▶')),
729
791
  body);
730
792
  }
@@ -948,6 +1010,53 @@ window.__ModuleLoader__.load({ id: 'dsh-iris', factory: (require) => {
948
1010
  note ? React.createElement('div', { className: 'iris-clean-note', style: { width: '100%' } }, note) : null);
949
1011
  }
950
1012
 
1013
+ function PromptOptimizerSettings() {
1014
+ var pair = React.useState(null);
1015
+ var state = pair[0];
1016
+ var setState = pair[1];
1017
+ var notePair = React.useState('');
1018
+ var note = notePair[0];
1019
+ var setNote = notePair[1];
1020
+
1021
+ function load() {
1022
+ fetch('/iris/api/prompt-optimizer/config')
1023
+ .then(function (res) { return res.ok ? res.json() : null; })
1024
+ .then(function (data) { if (data && data.config) setState(data); })
1025
+ .catch(function () { setNote('读取提示词优化配置失败'); });
1026
+ }
1027
+ React.useEffect(function () {
1028
+ function changed(event) { if (event.detail && event.detail.config) setState(event.detail); }
1029
+ window.addEventListener('iris-prompt-config-changed', changed);
1030
+ load();
1031
+ return function () { window.removeEventListener('iris-prompt-config-changed', changed); };
1032
+ }, []);
1033
+
1034
+ function toggle() {
1035
+ var enabled = !(state && state.config && state.config.enabled !== false);
1036
+ fetch('/iris/api/prompt-optimizer/enabled', {
1037
+ method: 'POST',
1038
+ headers: { 'Content-Type': 'application/json' },
1039
+ body: JSON.stringify({ enabled: enabled })
1040
+ }).then(function (res) {
1041
+ return res.json().then(function (data) {
1042
+ if (!res.ok) throw new Error(data.error || '更新失败');
1043
+ setState(data);
1044
+ window.dispatchEvent(new CustomEvent('iris-prompt-config-changed', { detail: data }));
1045
+ setNote(enabled ? '对话框 🫧 入口已启用' : '对话框入口已关闭;Iris 工作台与后台能力不受影响');
1046
+ });
1047
+ }).catch(function (err) { setNote(String(err && err.message || err)); });
1048
+ }
1049
+
1050
+ var enabled = !state || !state.config || state.config.enabled !== false;
1051
+ return React.createElement('div', { className: 'iris-wb-card' },
1052
+ React.createElement('div', { className: 'iris-wb-row' },
1053
+ React.createElement('span', { className: 'iris-po-settings-mark' }, '🫧'),
1054
+ React.createElement('span', { style: { flex: 1 } }, enabled ? '对话框入口已启用' : '对话框入口已关闭'),
1055
+ React.createElement('button', { className: 'iris-pm-btn', onClick: toggle }, enabled ? '关闭入口' : '重新启用')),
1056
+ React.createElement('div', { className: 'iris-wb-muted' }, '这里只控制对话框提示词优化入口;Iris 工作台、Agent 工具和任务后台始终保持运行。'),
1057
+ note ? React.createElement('div', { className: 'iris-clean-note' }, note) : null);
1058
+ }
1059
+
951
1060
  function WorkbenchPanel() {
952
1061
  var state = useIrisState(5000);
953
1062
  var running = (state && state.tasks && state.tasks.running) || [];
@@ -966,6 +1075,8 @@ window.__ModuleLoader__.load({ id: 'dsh-iris', factory: (require) => {
966
1075
  React.createElement(CapabilityAssigner, {}),
967
1076
  React.createElement('div', { className: 'iris-wb-sec' }, 'Iris 泡泡快捷卡片(勾选后显示在「⚡ 常用」标签)'),
968
1077
  React.createElement(BubbleCardPicker, {}),
1078
+ React.createElement('div', { className: 'iris-wb-sec' }, '对话框提示词优化'),
1079
+ React.createElement(PromptOptimizerSettings, {}),
969
1080
  React.createElement('div', { className: 'iris-wb-sec' }, '运行中任务'),
970
1081
  React.createElement('div', { className: 'iris-wb-box' },
971
1082
  running.length ? running.map(function (t) { return taskRow(t, true, selectedTask === t.id, onSelect); })
@@ -993,17 +1104,21 @@ window.__ModuleLoader__.load({ id: 'dsh-iris', factory: (require) => {
993
1104
  function onSelect(id) { setSelectedTask(id); }
994
1105
  var body;
995
1106
  if (tab === 'tasks') {
996
- var shown = recent.slice(0, 6);
1107
+ // 快捷面板只承载当前任务与可直接查看的成功产物;失败/取消历史留在完整工作台。
1108
+ var succeeded = recent.filter(function (t) {
1109
+ return t.status === 'succeeded' && Array.isArray(t.media) && t.media.length > 0;
1110
+ });
1111
+ var shown = succeeded.slice(0, 6);
997
1112
  body = React.createElement('div', { className: 'iris-wb-box' },
998
1113
  running.length ? React.createElement('div', {},
999
1114
  React.createElement('div', { className: 'iris-wb-sec' }, '运行中'),
1000
1115
  running.map(function (t) { return taskRow(t, true, selectedTask === t.id, onSelect); })) : null,
1001
- React.createElement('div', { className: 'iris-wb-sec' }, '最近'),
1116
+ React.createElement('div', { className: 'iris-wb-sec' }, '最近完成'),
1002
1117
  shown.length ? shown.map(function (t) { return taskRowMini(t, selectedTask === t.id, onSelect); })
1003
- : React.createElement('div', { className: 'iris-wb-empty' }, '尚无任务记录'),
1004
- recent.length > 6 || recentTotal > recent.length
1118
+ : React.createElement('div', { className: 'iris-wb-empty' }, '尚无成功产物'),
1119
+ succeeded.length > 6 || recentTotal > succeeded.length
1005
1120
  ? React.createElement('div', { className: 'iris-bubble-more', style: { cursor: 'default' } },
1006
- '共 ' + recentTotal + ' 条 · 完整历史与清理见设置页') : null);
1121
+ '失败、取消与完整历史请到 Iris 工作台查看') : null);
1007
1122
  } else {
1008
1123
  var defs = CARD_DEFS.filter(function (d) { return cards.indexOf(d.action) >= 0; });
1009
1124
  body = defs.length
@@ -1018,6 +1133,234 @@ window.__ModuleLoader__.load({ id: 'dsh-iris', factory: (require) => {
1018
1133
  selectedTask ? React.createElement(TaskDetailDrawer, { taskId: selectedTask }) : null);
1019
1134
  }
1020
1135
 
1136
+ function PromptOptimizerControl(props) {
1137
+ var draft = props.useInput(function (value) { return value.draft; });
1138
+ var phase = props.useInput(function (value) { return value.phase; });
1139
+ var refCount = props.useInput(function (value) { return (value.occurrences || []).length; });
1140
+ var projection = props.useProjection('modelSelection');
1141
+ var selectedRoute = projection && (projection.current || projection.selection || projection.next || projection.lastUsed || (projection.provider && projection.model ? projection : null));
1142
+
1143
+ var openPair = React.useState(false);
1144
+ var open = openPair[0];
1145
+ var setOpen = openPair[1];
1146
+ var targetPair = React.useState('general');
1147
+ var target = targetPair[0];
1148
+ var setTarget = targetPair[1];
1149
+ var busyPair = React.useState(false);
1150
+ var busy = busyPair[0];
1151
+ var setBusy = busyPair[1];
1152
+ var resultPair = React.useState(null);
1153
+ var result = resultPair[0];
1154
+ var setResult = resultPair[1];
1155
+ var notePair = React.useState('');
1156
+ var note = notePair[0];
1157
+ var setNote = notePair[1];
1158
+ var configPair = React.useState(null);
1159
+ var configState = configPair[0];
1160
+ var setConfigState = configPair[1];
1161
+ var abortRef = React.useRef(null);
1162
+ var fileRef = React.useRef(null);
1163
+
1164
+ function promptRequest(operation, body, signal) {
1165
+ return fetch('/iris/api/prompt-optimizer/' + operation, {
1166
+ method: 'POST',
1167
+ headers: { 'Content-Type': 'application/json' },
1168
+ body: JSON.stringify(body || {}),
1169
+ signal: signal
1170
+ }).then(function (res) {
1171
+ return res.json().catch(function () { return {}; }).then(function (data) {
1172
+ if (!res.ok) throw new Error(data.error || ('请求失败 (' + res.status + ')'));
1173
+ return data;
1174
+ });
1175
+ });
1176
+ }
1177
+
1178
+ function loadConfig() {
1179
+ fetch('/iris/api/prompt-optimizer/config')
1180
+ .then(function (res) { return res.ok ? res.json() : null; })
1181
+ .then(function (data) { if (data && data.config) setConfigState(data); })
1182
+ .catch(function () { setNote('无法读取提示词优化配置'); });
1183
+ }
1184
+
1185
+ React.useEffect(function () {
1186
+ return function () {
1187
+ if (abortRef.current) abortRef.current.abort();
1188
+ };
1189
+ }, []);
1190
+
1191
+ React.useEffect(function () {
1192
+ function changed(event) { if (event.detail && event.detail.config) setConfigState(event.detail); }
1193
+ window.addEventListener('iris-prompt-config-changed', changed);
1194
+ loadConfig();
1195
+ return function () { window.removeEventListener('iris-prompt-config-changed', changed); };
1196
+ }, []);
1197
+
1198
+ function publishConfig(data) {
1199
+ setConfigState(data);
1200
+ try { window.dispatchEvent(new CustomEvent('iris-prompt-config-changed', { detail: data })); } catch (_) {}
1201
+ }
1202
+
1203
+ function optimize() {
1204
+ if (!draft || !draft.trim()) { setNote('请先在输入框中写入提示词'); return; }
1205
+ if (refCount) { setNote('当前版本暂不改写含 @ 或 / 结构化引用的草稿,以免引用失效'); return; }
1206
+ if (phase !== 'plain') { setNote('输入框正忙,请稍后再试'); return; }
1207
+ if (abortRef.current) abortRef.current.abort();
1208
+ var controller = new AbortController();
1209
+ abortRef.current = controller;
1210
+ setBusy(true);
1211
+ setNote('正在调用模型优化…这可能产生模型费用');
1212
+ setResult(null);
1213
+ promptRequest('optimize', {
1214
+ text: draft,
1215
+ target: target,
1216
+ sessionId: props.sessionId,
1217
+ route: selectedRoute || undefined
1218
+ }, controller.signal).then(function (data) {
1219
+ setResult(data);
1220
+ setNote('已生成预览,尚未写回输入框');
1221
+ }).catch(function (err) {
1222
+ if (controller.signal.aborted) setNote('已取消');
1223
+ else setNote(String(err && err.message || err));
1224
+ }).finally(function () {
1225
+ if (abortRef.current === controller) abortRef.current = null;
1226
+ setBusy(false);
1227
+ });
1228
+ }
1229
+
1230
+ function cancel() {
1231
+ if (abortRef.current) abortRef.current.abort();
1232
+ }
1233
+
1234
+ function replaceDraft(text) {
1235
+ if (!result) return;
1236
+ if (draft !== result.original && !window.confirm('输入框内容在优化期间已变化,仍要替换吗?')) return;
1237
+ props.inputActions.setDraft(text);
1238
+ setNote(text === result.original ? '已恢复优化前原文' : '已写回输入框;不会自动发送');
1239
+ }
1240
+
1241
+ function copyResult() {
1242
+ if (!result) return;
1243
+ if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
1244
+ navigator.clipboard.writeText(result.optimized).then(function () { setNote('已复制优化结果'); }).catch(function () { setNote('复制失败,请手动选择文本'); });
1245
+ } else {
1246
+ setNote('浏览器不支持自动复制,请手动选择文本');
1247
+ }
1248
+ }
1249
+
1250
+ function importConfig(event) {
1251
+ var file = event.target.files && event.target.files[0];
1252
+ event.target.value = '';
1253
+ if (!file) return;
1254
+ file.text().then(function (raw) {
1255
+ var parsed = JSON.parse(raw);
1256
+ return promptRequest('import', { config: parsed });
1257
+ }).then(function (data) {
1258
+ publishConfig(data);
1259
+ setNote('JSON 配置已导入并生效');
1260
+ }).catch(function (err) {
1261
+ setNote('导入失败:' + String(err && err.message || err));
1262
+ });
1263
+ }
1264
+
1265
+ function exportConfig() {
1266
+ if (!configState || !configState.config) { setNote('配置尚未载入'); return; }
1267
+ try {
1268
+ var blob = new Blob([JSON.stringify(configState.config, null, 2) + '\n'], { type: 'application/json' });
1269
+ var url = URL.createObjectURL(blob);
1270
+ var a = document.createElement('a');
1271
+ a.href = url;
1272
+ a.download = 'iris-prompt-optimizer.json';
1273
+ a.click();
1274
+ setTimeout(function () { URL.revokeObjectURL(url); }, 0);
1275
+ setNote('已导出当前 JSON 配置');
1276
+ } catch (_) {
1277
+ setNote('导出失败');
1278
+ }
1279
+ }
1280
+
1281
+ function resetConfig() {
1282
+ if (!window.confirm('恢复 Iris 内置默认 Prompt、目标模板和会话模型路由?')) return;
1283
+ promptRequest('reset', {}).then(function (data) {
1284
+ publishConfig(data);
1285
+ setNote('已恢复 Iris 内置默认配置');
1286
+ }).catch(function (err) {
1287
+ setNote('重置失败:' + String(err && err.message || err));
1288
+ });
1289
+ }
1290
+
1291
+ function disableEntry() {
1292
+ if (busy) return;
1293
+ promptRequest('enabled', { enabled: false }).then(function (data) {
1294
+ publishConfig(data);
1295
+ setOpen(false);
1296
+ }).catch(function (err) { setNote('关闭失败:' + String(err && err.message || err)); });
1297
+ }
1298
+
1299
+ if (configState && configState.config && configState.config.enabled === false) return null;
1300
+
1301
+ var routeLabel = 'DSH 默认模型';
1302
+ if (configState && configState.config && configState.config.route.mode === 'fixed') {
1303
+ routeLabel = configState.config.route.provider + ' / ' + configState.config.route.model;
1304
+ } else if (selectedRoute) {
1305
+ routeLabel = selectedRoute.provider + ' / ' + selectedRoute.model;
1306
+ }
1307
+ var reasoningSetting = configState && configState.config && configState.config.generation && configState.config.generation.reasoningEffort || 'off-if-supported';
1308
+ var reasoningLabel = reasoningSetting === 'off-if-supported' ? '支持时关闭' : reasoningSetting === 'provider-default' ? '供应商默认' : reasoningSetting === 'inherit' ? '跟随会话' : reasoningSetting;
1309
+ var outputBudget = configState && configState.config && configState.config.generation && configState.config.generation.maxOutputTokens || 1200;
1310
+
1311
+ var panel = null;
1312
+ if (open) {
1313
+ panel = React.createElement(React.Fragment, {},
1314
+ React.createElement('div', { className: 'iris-po-backdrop', onClick: function () { setOpen(false); }, 'aria-hidden': 'true' }),
1315
+ React.createElement('div', { className: 'iris-po-panel', role: 'dialog', 'aria-modal': 'true', 'aria-label': 'Iris 提示词优化', onClick: function (e) { e.stopPropagation(); } },
1316
+ React.createElement('div', { className: 'iris-po-head' },
1317
+ React.createElement('strong', { className: 'iris-po-title' }, React.createElement('span', { className: 'iris-po-mark' }, '🫧'), 'Iris 提示词优化'),
1318
+ React.createElement('button', { className: 'iris-po-close', onClick: function () { setOpen(false); }, title: '关闭' }, '✕')),
1319
+ React.createElement('div', { className: 'iris-po-muted' }, '只处理当前未发送草稿,不读取会话历史;调用模型可能产生费用。'),
1320
+ React.createElement('label', { className: 'iris-po-field' },
1321
+ React.createElement('span', {}, '优化目标'),
1322
+ React.createElement('select', { value: target, disabled: busy, onChange: function (e) { setTarget(e.target.value); } },
1323
+ React.createElement('option', { value: 'general' }, '通用对话'),
1324
+ React.createElement('option', { value: 'image' }, '图片生成'),
1325
+ React.createElement('option', { value: 'video' }, '视频生成'),
1326
+ React.createElement('option', { value: 's2v' }, '首尾帧视频'))),
1327
+ React.createElement('div', { className: 'iris-po-route', title: routeLabel }, '模型:' + routeLabel + ' · 思考:' + reasoningLabel + ' · 输出:' + outputBudget + ' tokens'),
1328
+ React.createElement('div', { className: 'iris-po-actions' },
1329
+ React.createElement('button', { className: 'iris-po-primary', disabled: busy || !draft || !draft.trim() || phase !== 'plain' || refCount > 0, onClick: optimize }, busy ? '优化中…' : '生成预览'),
1330
+ busy ? React.createElement('button', { className: 'iris-po-btn', onClick: cancel }, '取消') : null),
1331
+ result ? React.createElement('div', { className: 'iris-po-result' },
1332
+ React.createElement('div', { className: 'iris-po-result-label' }, '优化结果'),
1333
+ React.createElement('textarea', { readOnly: true, value: result.optimized }),
1334
+ React.createElement('div', { className: 'iris-po-route' }, '实际使用:' + result.route.provider + ' / ' + result.route.model + ' · 思考:' + (result.route.reasoningEffort === 'provider-default' ? '供应商默认' : result.route.reasoningEffort)),
1335
+ React.createElement('div', { className: 'iris-po-actions' },
1336
+ React.createElement('button', { className: 'iris-po-primary', onClick: function () { replaceDraft(result.optimized); } }, '写回输入框'),
1337
+ React.createElement('button', { className: 'iris-po-btn', onClick: copyResult }, '复制'),
1338
+ React.createElement('button', { className: 'iris-po-btn', onClick: optimize }, '再优化'),
1339
+ React.createElement('button', { className: 'iris-po-btn', onClick: function () { replaceDraft(result.original); } }, '恢复原文'))) : null,
1340
+ note ? React.createElement('div', { className: 'iris-po-note' }, note) : null,
1341
+ React.createElement('details', { className: 'iris-po-config' },
1342
+ React.createElement('summary', {}, 'JSON 配置'),
1343
+ React.createElement('div', { className: 'iris-po-muted' }, '可导出后修改 systemPrompt、targets、route 和 generation,再重新导入。'),
1344
+ React.createElement('div', { className: 'iris-po-actions' },
1345
+ React.createElement('button', { className: 'iris-po-btn', onClick: exportConfig }, '导出'),
1346
+ React.createElement('button', { className: 'iris-po-btn', onClick: function () { if (fileRef.current) fileRef.current.click(); } }, '导入'),
1347
+ React.createElement('button', { className: 'iris-po-btn danger', onClick: resetConfig }, '恢复默认'),
1348
+ React.createElement('input', { ref: fileRef, className: 'iris-po-file', type: 'file', accept: 'application/json,.json', onChange: importConfig })),
1349
+ React.createElement('div', { className: 'iris-po-muted' }, '当前:' + (configState && configState.source === 'custom' ? '用户 JSON' : 'Iris 默认'))),
1350
+ React.createElement('button', { className: 'iris-po-disable', disabled: busy, onClick: disableEntry }, '关闭此对话入口(工作台仍保留)')));
1351
+ }
1352
+
1353
+ return React.createElement('div', { className: 'iris-po-wrap' },
1354
+ React.createElement('button', {
1355
+ className: 'iris-po-trigger',
1356
+ type: 'button',
1357
+ title: refCount ? '含结构化引用的草稿暂不支持优化' : '优化当前输入框提示词',
1358
+ 'aria-expanded': open ? 'true' : 'false',
1359
+ onClick: function () { setOpen(!open); }
1360
+ }, '🫧'),
1361
+ panel);
1362
+ }
1363
+
1021
1364
  function ProgressDock() {
1022
1365
  var state = useIrisState(5000);
1023
1366
  var running = (state && state.tasks && state.tasks.running) || [];
@@ -1158,6 +1501,15 @@ window.__ModuleLoader__.load({ id: 'dsh-iris', factory: (require) => {
1158
1501
  }, WorkbenchPanel);
1159
1502
  });
1160
1503
 
1504
+ ctx.slots.inject('conversation.input.right', function () {
1505
+ return ctx.slots.register({
1506
+ name: 'conversation.input.right',
1507
+ id: 'iris-prompt-optimizer',
1508
+ order: 40,
1509
+ label: function () { return 'Iris 提示词优化'; }
1510
+ }, PromptOptimizerControl);
1511
+ });
1512
+
1161
1513
  ctx.slots.inject('conversation.input.dock', function () {
1162
1514
  return ctx.slots.register({
1163
1515
  name: 'conversation.input.dock',
@@ -1184,4 +1536,4 @@ window.__ModuleLoader__.load({ id: 'dsh-iris', factory: (require) => {
1184
1536
 
1185
1537
  module.exports = { inject: inject, apply: apply };
1186
1538
  return module.exports;
1187
- }});
1539
+ }});