@mokuyoaxis/dsh-iris 0.2.2 → 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.
- package/.dsh/skills/iris-compose-media/SKILL.md +23 -73
- package/.dsh/skills/iris-compose-media/agents/openai.yaml +1 -1
- package/.dsh/skills/iris-compose-media/references/task-v2-recovery.md +23 -22
- package/.dsh/skills/iris-compose-media/references/workflows.md +25 -30
- package/.dsh/skills/iris-use/SKILL.md +41 -0
- package/.dsh/skills/iris-use/agents/openai.yaml +4 -0
- package/.dsh/skills/iris-use/references/media-contract.md +73 -0
- package/.dsh/skills/iris-verify-ui/SKILL.md +9 -7
- package/.dsh/skills/iris-verify-ui/agents/openai.yaml +1 -1
- package/.dsh/skills/iris-verify-ui/references/verdict-and-degradation.md +3 -0
- package/CHANGELOG.md +26 -2
- package/README.en.md +39 -32
- package/README.md +37 -31
- package/bin/dsh-iris.js +27 -6
- package/docs/AGENT_QUICKSTART.md +9 -9
- package/docs/ARCHITECTURE.md +1 -1
- package/docs/CORE_RUNTIME_CONTRACT.md +3 -1
- package/docs/DSH_CORE_MIGRATION.md +6 -2
- package/docs/FAULT_INJECTION.md +1 -0
- package/docs/HEADLESS_CLI.md +8 -4
- package/docs/HOST_DOCTOR.md +3 -3
- package/docs/MODEL_PORT_CONTRACT.md +6 -2
- package/docs/MUSIC.md +77 -0
- package/docs/PROMPT_OPTIMIZER.md +48 -10
- package/docs/PROVIDER_ADAPTER_CONTRACT.md +2 -0
- package/docs/ROADMAP.md +14 -25
- package/docs/SECURITY.md +3 -2
- package/docs/SYNC_DELIVERY_RECOVERY.md +39 -0
- package/docs/TASK_SEMANTICS.md +4 -4
- package/docs/releases/0.2.3.md +54 -0
- package/lib/actions.js +40 -3
- package/lib/adapters.js +1 -0
- package/lib/api.js +14 -5
- package/lib/bundled-skills.js +1 -1
- package/lib/capability.js +2 -0
- package/lib/client.js +111 -26
- package/lib/command-service.js +12 -3
- package/lib/config.js +26 -2
- package/lib/core-artifact-store.js +44 -6
- package/lib/core-delivery-cache.js +145 -0
- package/lib/core-maintenance.js +12 -1
- package/lib/core-tasks.js +19 -5
- package/lib/core-user-projection.js +2 -2
- package/lib/dsh-core-adapter.js +14 -4
- package/lib/dsh-text-model-adapter.js +15 -2
- package/lib/generation-input.js +10 -0
- package/lib/host-runtime.js +4 -2
- package/lib/index.js +23 -4
- package/lib/models.js +4 -1
- package/lib/music-generation.js +21 -0
- package/lib/music-options.js +18 -0
- package/lib/music-transport.js +46 -0
- package/lib/prompt-optimizer-config.js +92 -21
- package/lib/prompt-optimizer-core.js +51 -6
- package/lib/prompt-optimizer.js +20 -14
- package/lib/provider-adapter.js +1 -0
- package/lib/provider-adapters.js +24 -1
- package/lib/provider-catalog.js +18 -7
- package/lib/provider-config-service.js +13 -1
- package/lib/provider-protocol.js +12 -0
- package/lib/provider-task-runner.js +80 -5
- package/package.json +4 -2
- package/user_guide.md +30 -16
package/lib/actions.js
CHANGED
|
@@ -23,7 +23,7 @@ import { registerMedia, mediaLinksOf, webBase } from './media.js';
|
|
|
23
23
|
import { RED_TEST_IMAGE } from './vision.js';
|
|
24
24
|
import { runVisionRequest, readVisionImageFile, probeVisionModel, visionImageFromDataUrl } from './vision-model-routing.js';
|
|
25
25
|
import { HOST_CONTRACT_VERSION, HOST_PORTS, defineHostAdapter, hasHostPort, requireHostPort } from './host-contract.js';
|
|
26
|
-
import { coreSnapshotForDsh, cropForDsh, diffForDsh, framesForDsh, summaryFramesForDsh, visionImageForDsh, htmlForDsh, inspectProviderTaskForDsh, submitProviderTaskForDsh, watchProviderTaskForDsh } from './dsh-core-adapter.js';
|
|
26
|
+
import { coreSnapshotForDsh, cropForDsh, diffForDsh, framesForDsh, summaryFramesForDsh, visionImageForDsh, htmlForDsh, inspectProviderTaskForDsh, submitMusicForDsh, submitProviderTaskForDsh, watchProviderTaskForDsh } from './dsh-core-adapter.js';
|
|
27
27
|
import { runLocateRequest, runSummaryRequest } from './composite-vision-routing.js';
|
|
28
28
|
import { normalizeSummarySource, summaryFrameRecords, summaryTimelineLabel } from './summary-input.js';
|
|
29
29
|
import { normalizeVisionImageSource } from './vision-input.js';
|
|
@@ -32,7 +32,7 @@ import { runOcrRequest } from './ocr-model-routing.js';
|
|
|
32
32
|
import { ffmpegAvailable, extractFrames, probeVideo } from './media-probe.js';
|
|
33
33
|
import { ensurePrivateDir, writePrivateFile } from './private-storage.js';
|
|
34
34
|
import { selectMediaProtocol, selectImageProtocol, providerForImageModel } from './provider-protocol.js';
|
|
35
|
-
import { providerTaskBinding } from './provider-catalog.js';
|
|
35
|
+
import { musicCandidatesFromCatalog, providerTaskBinding } from './provider-catalog.js';
|
|
36
36
|
import { invokeProviderOperation, providerPollTaskView, prepareProviderInput } from './provider-adapter.js';
|
|
37
37
|
import { createConfiguredProviderAdapter as createProviderAdapter } from './provider-adapters.js';
|
|
38
38
|
import { deriveUserState } from './task-semantics.js';
|
|
@@ -564,6 +564,33 @@ async function submitCoreTts(routes, normalizedInput, signal) {
|
|
|
564
564
|
}
|
|
565
565
|
|
|
566
566
|
/* ---------- 🔊 语音合成 ---------- */
|
|
567
|
+
register('music', async (host, args, { signal }) => {
|
|
568
|
+
const normalized = normalizeGenerationInput('music', {
|
|
569
|
+
prompt: args.prompt,
|
|
570
|
+
...(optionalActionText(args.timeout_ms) !== undefined ? { timeout_ms: args.timeout_ms } : {}),
|
|
571
|
+
...(optionalActionText(args.retry_502) !== undefined ? { retry_502: args.retry_502 } : {}),
|
|
572
|
+
...(optionalActionText(args.retry_delay_ms) !== undefined ? { retry_delay_ms: args.retry_delay_ms } : {})
|
|
573
|
+
});
|
|
574
|
+
const routes = musicCandidatesFromCatalog({ providers: store.allProviders(), assignments: store.assignments() }, args.model);
|
|
575
|
+
const submitted = await submitMusicForDsh({
|
|
576
|
+
candidates: routes.map(route => ({ adapter: createConfiguredProviderAdapter(route.provider), model: route.modelRef,
|
|
577
|
+
selectionReason: route.selectionReason, providerBinding: providerTaskBinding(route.provider) })),
|
|
578
|
+
providerInput: normalized.providerInput, musicOptions: normalized.musicOptions, signal
|
|
579
|
+
});
|
|
580
|
+
const task = submitted.task;
|
|
581
|
+
recordCoreTaskAttemptHealth(task, cap.CAPABILITIES.MUSIC);
|
|
582
|
+
if (submitted.localError) throw taskActionError(task.id, 'Music 结果未能可靠落盘', submitted.localError);
|
|
583
|
+
if (task.outcome !== 'succeeded' || task.deliveryState !== 'ready') {
|
|
584
|
+
throw taskActionError(task.id, task.outcome === 'succeeded'
|
|
585
|
+
? '音乐已返回,但交付失败;可从缓存重新交付'
|
|
586
|
+
: `音乐未取得产物(已重试${submitted.retries}次)`, task.lastError);
|
|
587
|
+
}
|
|
588
|
+
return { ok: true, storage: 'core', taskId: task.id, taskIds: submitted.taskIds, retries: submitted.retries,
|
|
589
|
+
model: task.modelRef, providerId: task.providerId, artifactIds: task.artifactIds,
|
|
590
|
+
audioUrl: '/iris/api/core/artifact/' + encodeURIComponent(task.artifactIds[0]) + '/media',
|
|
591
|
+
text: `[iris] 音乐已生成(${task.modelRef},重试${submitted.retries}次,Core task: ${task.id})` };
|
|
592
|
+
});
|
|
593
|
+
|
|
567
594
|
register('tts', async (host, args, { signal }) => {
|
|
568
595
|
const { providerInput } = normalizeGenerationInput('tts', {
|
|
569
596
|
text: args.text, voice: optionalActionText(args.voice)
|
|
@@ -1008,6 +1035,7 @@ register('providers_list', async (host, args) => {
|
|
|
1008
1035
|
id: m.id,
|
|
1009
1036
|
capabilities: m.capabilities,
|
|
1010
1037
|
...(m.imageProtocol !== undefined ? { imageProtocol: m.imageProtocol } : {}),
|
|
1038
|
+
...(m.musicProtocol !== undefined ? { musicProtocol: m.musicProtocol } : {}),
|
|
1011
1039
|
...(m.visionInput !== undefined ? { visionInput: m.visionInput } : {}),
|
|
1012
1040
|
...(m.capabilities.includes(cap.CAPABILITIES.VISION) ? { visionInputEffective: visionInputLimits(p, m.id) } : {}),
|
|
1013
1041
|
...(m.capabilities.includes(cap.CAPABILITIES.IMAGE) ? { imageRouting: selectImageProtocol(p, m.id) } : {}),
|
|
@@ -1100,6 +1128,9 @@ register('providers_test_vision', async (host, args, { signal }) => {
|
|
|
1100
1128
|
|
|
1101
1129
|
/** 按能力实测单个模型;返回 {ok, note}。真实调用仅在 UI 明确确认后执行。 */
|
|
1102
1130
|
async function probeModel(host, p, modelId, capability, signal) {
|
|
1131
|
+
if (capability === cap.CAPABILITIES.MUSIC) {
|
|
1132
|
+
return { skipped: true, ok: false, note: 'Music 不发起小样本探测;请通过音乐功能提交实际创作要求' };
|
|
1133
|
+
}
|
|
1103
1134
|
if (capability === cap.CAPABILITIES.VISION) {
|
|
1104
1135
|
const r = await probeVisionModel(p, modelId, visionImageFromDataUrl(RED_TEST_IMAGE), { timeoutMs: 15000, signal });
|
|
1105
1136
|
return {
|
|
@@ -1207,6 +1238,12 @@ register('providers_set_model_image_protocol', async (host, args) => {
|
|
|
1207
1238
|
return { ok: true, text: '[iris] 模型图片协议已更新' };
|
|
1208
1239
|
});
|
|
1209
1240
|
|
|
1241
|
+
register('providers_set_model_music_protocol', async (host, args) => {
|
|
1242
|
+
const ok = store.setModelMusicProtocol(String(args.id || ''), String(args.model_id || ''), args.musicProtocol);
|
|
1243
|
+
if (!ok) throw new Error('iris: 模型不在池中');
|
|
1244
|
+
return { ok: true, text: '[iris] 模型音乐协议已更新' };
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1210
1247
|
register('providers_set_model_vision_input', async (host, args) => {
|
|
1211
1248
|
if (!Object.hasOwn(args, 'visionInput')) throw new Error('iris: 需要 visionInput 对象或 null');
|
|
1212
1249
|
const ok = store.setModelVisionInput(String(args.id || ''), String(args.model_id || ''), args.visionInput);
|
|
@@ -1221,7 +1258,7 @@ register('providers_test_model', async (host, args, { signal }) => {
|
|
|
1221
1258
|
const modelId = String(args.model_id || '').trim();
|
|
1222
1259
|
const capability = String(args.capability || '').trim();
|
|
1223
1260
|
if (!modelId || !cap.isKnownCapability(capability)) throw new Error('iris: 需 model_id 与合法 capability');
|
|
1224
|
-
if (![cap.CAPABILITIES.VIDEO, cap.CAPABILITIES.TRANSCRIBE].includes(capability) && args.confirm_paid !== true) {
|
|
1261
|
+
if (![cap.CAPABILITIES.VIDEO, cap.CAPABILITIES.TRANSCRIBE, cap.CAPABILITIES.MUSIC].includes(capability) && args.confirm_paid !== true) {
|
|
1225
1262
|
throw new Error('iris: 模型实测会发起真实供应商请求,请先明确确认');
|
|
1226
1263
|
}
|
|
1227
1264
|
let result;
|
package/lib/adapters.js
CHANGED
|
@@ -15,6 +15,7 @@ import { dashscopeApiBase, imageSizeViolation, isDashScopeBaseUrl, normalizeImag
|
|
|
15
15
|
import { chmodPrivateFile, ensurePrivateDir, privateSibling } from './private-storage.js';
|
|
16
16
|
import { isFreeQuotaExhausted, retryAfterDelay } from './provider-health.js';
|
|
17
17
|
import { ProviderContractError } from './provider-contract.js';
|
|
18
|
+
export { generateMusic } from './music-transport.js';
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* 网络调用超时档位(2026-09-03 健康检查):挂死的连接绝不能冻住盯守 tick——
|
package/lib/api.js
CHANGED
|
@@ -33,9 +33,12 @@ import {
|
|
|
33
33
|
importPromptOptimizerConfig,
|
|
34
34
|
loadPromptOptimizerConfig,
|
|
35
35
|
resetPromptOptimizerConfig,
|
|
36
|
+
resetPromptOptimizerInstructions,
|
|
36
37
|
setPromptOptimizerEnabled
|
|
37
38
|
} from './prompt-optimizer-config.js';
|
|
38
39
|
|
|
40
|
+
const PROMPT_CONFIG_CAPABILITIES = Object.freeze({ ...PROMPT_OPTIMIZER_CAPABILITIES, resetInstructions: true });
|
|
41
|
+
|
|
39
42
|
function readJsonSafe(file) {
|
|
40
43
|
try {
|
|
41
44
|
const raw = fs.readFileSync(file, 'utf8');
|
|
@@ -369,7 +372,7 @@ export function serveApi(req, res, host) {
|
|
|
369
372
|
return serveSse(res);
|
|
370
373
|
}
|
|
371
374
|
if (urlPath === '/iris/api/prompt-optimizer/config') {
|
|
372
|
-
return sendJson(res, 200, method === 'HEAD' ? {} : { ...loadPromptOptimizerConfig(), capabilities:
|
|
375
|
+
return sendJson(res, 200, method === 'HEAD' ? {} : { ...loadPromptOptimizerConfig(), capabilities: PROMPT_CONFIG_CAPABILITIES });
|
|
373
376
|
}
|
|
374
377
|
if (urlPath === '/iris/api/doctor') {
|
|
375
378
|
if (!host || typeof host !== 'object') return sendJson(res, 503, { error: 'host unavailable' });
|
|
@@ -506,7 +509,9 @@ function handleJsonPost(req, res, execute, respond = sendJson) {
|
|
|
506
509
|
if (overflow) return finish(413, { error: 'request body too large (limit 1MB)' });
|
|
507
510
|
try {
|
|
508
511
|
const raw = Buffer.concat(chunks).toString('utf8');
|
|
509
|
-
|
|
512
|
+
let body;
|
|
513
|
+
try { body = raw ? JSON.parse(raw) : {}; }
|
|
514
|
+
catch (_) { return finish(controller.signal.aborted ? 499 : 400, { error: controller.signal.aborted ? 'request canceled' : 'invalid JSON' }); }
|
|
510
515
|
const result = await execute(body, controller.signal);
|
|
511
516
|
if (controller.signal.aborted) return finish(499, { error: 'request canceled' });
|
|
512
517
|
finish(200, result);
|
|
@@ -630,10 +635,14 @@ function handlePromptOptimizer(host, operation, req, res) {
|
|
|
630
635
|
if (operation === 'optimize') return optimizePrompt(host, body, { signal });
|
|
631
636
|
if (operation === 'import') {
|
|
632
637
|
const imported = body && body.config !== undefined ? body.config : body;
|
|
633
|
-
return { ok: true, ...importPromptOptimizerConfig(imported), capabilities:
|
|
638
|
+
return { ok: true, ...importPromptOptimizerConfig(imported), capabilities: PROMPT_CONFIG_CAPABILITIES };
|
|
639
|
+
}
|
|
640
|
+
if (operation === 'reset') {
|
|
641
|
+
const scope = body?.scope === undefined ? 'all' : body.scope;
|
|
642
|
+
if (!['all', 'instructions'].includes(scope)) throw new Error('scope 只能是 all 或 instructions');
|
|
643
|
+
return { ok: true, ...(scope === 'instructions' ? resetPromptOptimizerInstructions() : resetPromptOptimizerConfig()), capabilities: PROMPT_CONFIG_CAPABILITIES };
|
|
634
644
|
}
|
|
635
|
-
if (operation === '
|
|
636
|
-
if (operation === 'enabled') return { ok: true, ...setPromptOptimizerEnabled(body && body.enabled), capabilities: PROMPT_OPTIMIZER_CAPABILITIES };
|
|
645
|
+
if (operation === 'enabled') return { ok: true, ...setPromptOptimizerEnabled(body && body.enabled), capabilities: PROMPT_CONFIG_CAPABILITIES };
|
|
637
646
|
throw new Error('unknown prompt optimizer operation');
|
|
638
647
|
});
|
|
639
648
|
}
|
package/lib/bundled-skills.js
CHANGED
|
@@ -5,7 +5,7 @@ import path from 'node:path';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { requireHostPort } from './host-contract.js';
|
|
7
7
|
|
|
8
|
-
const SKILL_NAMES = ['iris-
|
|
8
|
+
const SKILL_NAMES = ['iris-use', 'iris-compose-media', 'iris-verify-ui'];
|
|
9
9
|
const SKILL_ROOT = fileURLToPath(new URL('../.dsh/skills/', import.meta.url));
|
|
10
10
|
|
|
11
11
|
function parseScalar(value, file) {
|
package/lib/capability.js
CHANGED
|
@@ -14,6 +14,7 @@ export const CAPABILITIES = Object.freeze({
|
|
|
14
14
|
IMAGE: 'image-gen',
|
|
15
15
|
VIDEO: 'video-gen',
|
|
16
16
|
TTS: 'tts',
|
|
17
|
+
MUSIC: 'music-gen',
|
|
17
18
|
TRANSCRIBE: 'transcribe',
|
|
18
19
|
VISION: 'vision'
|
|
19
20
|
});
|
|
@@ -26,6 +27,7 @@ const INFER_FIELD = {
|
|
|
26
27
|
[CAPABILITIES.IMAGE]: 'imageModel',
|
|
27
28
|
[CAPABILITIES.VIDEO]: 'videoModel',
|
|
28
29
|
[CAPABILITIES.TTS]: 'ttsModel',
|
|
30
|
+
[CAPABILITIES.MUSIC]: 'musicModel',
|
|
29
31
|
[CAPABILITIES.TRANSCRIBE]: 'transcribeModel',
|
|
30
32
|
[CAPABILITIES.VISION]: 'visionModel'
|
|
31
33
|
};
|
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.
|
|
24
|
+
var IRIS_CLIENT_VERSION = '0.2.3';
|
|
25
25
|
var HOST_CLIENT_PROTOCOL_VERSION = 0;
|
|
26
26
|
var reportedSeats = [];
|
|
27
27
|
|
|
@@ -810,7 +810,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
810
810
|
}
|
|
811
811
|
|
|
812
812
|
/* ---- 模型池(阶段 9 P4):发现/手动模型 + verified 标记 + 逐模型测试/移除 + 手动添加 ---- */
|
|
813
|
-
var CAP_SHORT = { 'image-gen': '图', 'video-gen': '视', 'tts': '音', 'transcribe': '转', 'vision': '视👁' };
|
|
813
|
+
var CAP_SHORT = { 'image-gen': '图', 'video-gen': '视', 'tts': '音', 'music-gen': '乐', 'transcribe': '转', 'vision': '视👁' };
|
|
814
814
|
function ModelPool(props) {
|
|
815
815
|
var p = props.provider;
|
|
816
816
|
var addPair = React.useState('');
|
|
@@ -821,6 +821,11 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
821
821
|
postAction('providers_set_model_image_protocol', { id: p.id, model_id: m.id, imageProtocol: value })
|
|
822
822
|
.then(function (r) { props.onNote(r.ok ? '模型图片协议已更新' : (r.d.error || '更新失败')); if (r.ok) props.onDone(); });
|
|
823
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
|
+
}
|
|
824
829
|
function verBadge(m, c) {
|
|
825
830
|
var health = m.health && m.health[c];
|
|
826
831
|
var status = health && HEALTH_META[health.status] ? health.status : 'configured';
|
|
@@ -849,6 +854,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
849
854
|
React.createElement('option', { value: 'openai-chat-images' }, '聊天生图(网关兼容)'),
|
|
850
855
|
React.createElement('option', { value: 'openai-responses-images' }, 'Responses 生图')),
|
|
851
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,
|
|
852
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,
|
|
853
862
|
onNote: props.onNote, onDone: props.onDone }) : null,
|
|
854
863
|
blocked ? React.createElement('span', { className: 'iris-pm-ver failed ' + healthClass(blocked), title: healthText(blocked) }, rateLimitLabel(blocked)) : null,
|
|
@@ -1000,7 +1009,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1000
1009
|
postAction('providers_discover', { id: p.id }).then(function (r) { setNote(r.d.text || r.d.error || '发现完成'); refresh(); });
|
|
1001
1010
|
}
|
|
1002
1011
|
function testModel(p, m, capability) {
|
|
1003
|
-
var noProbe = capability === 'video-gen' || capability === 'transcribe';
|
|
1012
|
+
var noProbe = capability === 'video-gen' || capability === 'transcribe' || capability === 'music-gen';
|
|
1004
1013
|
if (!noProbe && !window.confirm('将用模型「' + m.id + '」发起一次真实的 ' + capability + ' 请求,可能产生费用。继续吗?')) return;
|
|
1005
1014
|
setNote('验证 ' + m.id + ' · ' + capability + '…');
|
|
1006
1015
|
postAction('providers_test_model', { id: p.id, model_id: m.id, capability: capability, confirm_paid: !noProbe })
|
|
@@ -1166,7 +1175,8 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1166
1175
|
if (!r.ok || !r.d) return;
|
|
1167
1176
|
var opts = (r.d.poolByCapability && r.d.poolByCapability[capability]) || [];
|
|
1168
1177
|
var ord = (r.d.order && r.d.order[capability]) || [];
|
|
1169
|
-
|
|
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) || '';
|
|
1170
1180
|
var assignedOpt = opts.find(function (o) { return o.ref === assignedRef; });
|
|
1171
1181
|
// 不再自动填 vals.model:留空 = 用能力分配的解析结果(避免绕过分配)
|
|
1172
1182
|
setModelInfo({ assigned: assignedOpt ? assignedOpt.label : '', assignedRef: assignedRef, options: opts, loaded: true });
|
|
@@ -1183,7 +1193,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1183
1193
|
})
|
|
1184
1194
|
.then(function (r) { return r.json().then(function (d) { return { ok: r.ok, d: d }; }); })
|
|
1185
1195
|
.then(function (res) {
|
|
1186
|
-
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 || '执行失败' });
|
|
1187
1197
|
if (res.ok && res.d && res.d.storage === 'core') window.dispatchEvent(new CustomEvent('iris-core-artifacts-changed'));
|
|
1188
1198
|
})
|
|
1189
1199
|
.catch(function () { setRun({ ok: false, text: '网络错误' }); });
|
|
@@ -1193,6 +1203,14 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1193
1203
|
if (open) {
|
|
1194
1204
|
var inputs = fields.map(function (f) {
|
|
1195
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
|
+
}
|
|
1196
1214
|
if (f.type === 'file') {
|
|
1197
1215
|
node = React.createElement(FileField, { value: vals[f.key] || '', accept: f.accept || '', onChange: function (v) { setVal(f.key, v); } });
|
|
1198
1216
|
return React.createElement('div', { key: f.key, className: 'iris-act-field' },
|
|
@@ -1220,6 +1238,10 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1220
1238
|
result = React.createElement('div', { className: 'iris-act-result' },
|
|
1221
1239
|
React.createElement('div', {}, run.text),
|
|
1222
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: '' }, '下载音乐'));
|
|
1223
1245
|
} else {
|
|
1224
1246
|
result = React.createElement('div', { className: 'iris-act-result' + (run.ok ? '' : ' err') }, run.text);
|
|
1225
1247
|
}
|
|
@@ -1232,7 +1254,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1232
1254
|
modelBar = React.createElement('div', { className: 'iris-act-field' },
|
|
1233
1255
|
React.createElement('label', {},
|
|
1234
1256
|
React.createElement('span', { className: 'iris-pm-dot ' + healthClass(cardHealth) }, cardHealth.retryAt ? '◷' : HEALTH_META[cardHealth.status].symbol),
|
|
1235
|
-
' 模型:' + (shown || '自动') + (available ? '' : '(无可用模型)')),
|
|
1257
|
+
' 模型:' + (shown || (capability === 'music-gen' ? '未分配' : '自动')) + (available ? '' : '(无可用模型)')),
|
|
1236
1258
|
React.createElement('div', { className: 'iris-wb-muted' },
|
|
1237
1259
|
healthText(cardHealth) + ' · 改分配 → 上方「能力分配(failover 顺序)」'));
|
|
1238
1260
|
}
|
|
@@ -1272,6 +1294,12 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1272
1294
|
{ action: 'tts', title: '🔊 语音合成', capability: 'tts', fields: [
|
|
1273
1295
|
{ key: 'text', label: '文本', type: 'textarea', placeholder: '要合成的文字' },
|
|
1274
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' }] },
|
|
1275
1303
|
{ action: 'transcribe', title: '🎙️ 音频转写', capability: 'transcribe', fields: [
|
|
1276
1304
|
{ key: 'audio_path', label: '音频文件', type: 'file', accept: 'audio/*', placeholder: '/path/audio.wav' }] },
|
|
1277
1305
|
{ action: 'video_frames', title: '🎞️ 视频抽帧', fields: [
|
|
@@ -1325,6 +1353,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1325
1353
|
{ cap: 'image-gen', label: '🎨 画图' },
|
|
1326
1354
|
{ cap: 'video-gen', label: '🎬 视频' },
|
|
1327
1355
|
{ cap: 'tts', label: '🔊 语音' },
|
|
1356
|
+
{ cap: 'music-gen', label: '🎵 音乐' },
|
|
1328
1357
|
{ cap: 'transcribe', label: '🎙️ 转写' },
|
|
1329
1358
|
{ cap: 'vision', label: '👁 视觉' }
|
|
1330
1359
|
];
|
|
@@ -1370,7 +1399,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1370
1399
|
});
|
|
1371
1400
|
return React.createElement('div', { key: row.cap, className: 'iris-cap-row' },
|
|
1372
1401
|
React.createElement('span', { className: 'iris-cap-name' }, row.label),
|
|
1373
|
-
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' ? '尚未分配音乐模型' : '自动(按池顺序)'),
|
|
1374
1403
|
available.length ? React.createElement('select', {
|
|
1375
1404
|
className: 'iris-pm-model-sel', value: '',
|
|
1376
1405
|
onChange: function (e) { if (e.target.value) save(row.cap, list.concat([e.target.value])); }
|
|
@@ -1988,7 +2017,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
1988
2017
|
}
|
|
1989
2018
|
|
|
1990
2019
|
function coreCapabilityLabel(value) {
|
|
1991
|
-
return ({ image: '图片生成', video: '视频生成', tts: '语音合成', transcribe: '音频转写' })[value] || String(value || '媒体任务');
|
|
2020
|
+
return ({ image: '图片生成', video: '视频生成', tts: '语音合成', music: '音乐生成', transcribe: '音频转写' })[value] || String(value || '媒体任务');
|
|
1992
2021
|
}
|
|
1993
2022
|
|
|
1994
2023
|
/* 高级诊断与用户任务区共用五类用户状态投影;与服务端 lib/core-user-projection.js 保持同一映射,
|
|
@@ -2522,6 +2551,24 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2522
2551
|
note ? React.createElement('div', { className: 'iris-clean-note', style: { width: '100%' } }, note) : null);
|
|
2523
2552
|
}
|
|
2524
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
|
+
|
|
2525
2572
|
function PromptOptimizerSettings() {
|
|
2526
2573
|
var pair = React.useState(null);
|
|
2527
2574
|
var state = pair[0];
|
|
@@ -2566,6 +2613,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2566
2613
|
React.createElement('span', { style: { flex: 1 } }, enabled ? '对话框入口已启用' : '对话框入口已关闭'),
|
|
2567
2614
|
React.createElement('button', { className: 'iris-pm-btn', onClick: toggle }, enabled ? '关闭入口' : '重新启用')),
|
|
2568
2615
|
React.createElement('div', { className: 'iris-wb-muted' }, '这里只控制对话框提示词优化入口;Iris 工作台、Agent 工具和任务后台始终保持运行。'),
|
|
2616
|
+
promptConfigFeedback(state),
|
|
2569
2617
|
note ? React.createElement('div', { className: 'iris-clean-note' }, note) : null);
|
|
2570
2618
|
}
|
|
2571
2619
|
|
|
@@ -2757,6 +2805,18 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2757
2805
|
return { removed: before.slice(start, before.length - end).join(''), added: after.slice(start, after.length - end).join('') };
|
|
2758
2806
|
}
|
|
2759
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
|
+
|
|
2760
2820
|
function PromptOptimizerControl(props) {
|
|
2761
2821
|
var draft = props.useInput(function (value) { return value.draft; });
|
|
2762
2822
|
var phase = props.useInput(function (value) { return value.phase; });
|
|
@@ -2822,7 +2882,9 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2822
2882
|
var supportsAssembly = !!(configState && configState.capabilities && configState.capabilities.assemble === true);
|
|
2823
2883
|
var editingCapabilities = configState && configState.capabilities && configState.capabilities.editing;
|
|
2824
2884
|
var supportsFaithful = Array.isArray(editingCapabilities) && editingCapabilities.indexOf('faithful') >= 0 && editingCapabilities.indexOf('rewrite') >= 0;
|
|
2885
|
+
var supportsLocalFaithful = supportsFaithful && configState.capabilities.localFaithful === true;
|
|
2825
2886
|
var supportsSuggestions = Array.isArray(editingCapabilities) && editingCapabilities.indexOf('suggestions') >= 0;
|
|
2887
|
+
var supportsResetInstructions = !!(configState && configState.capabilities && configState.capabilities.resetInstructions === true);
|
|
2826
2888
|
function supportsTarget(value) {
|
|
2827
2889
|
var available = configState && configState.capabilities && configState.capabilities.targets;
|
|
2828
2890
|
return Array.isArray(available) ? available.indexOf(value) >= 0 : value !== 'image-edit';
|
|
@@ -2922,12 +2984,12 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2922
2984
|
if (abortRef.current) abortRef.current.abort();
|
|
2923
2985
|
var controller = new AbortController();
|
|
2924
2986
|
var requestSessionId = props.sessionId;
|
|
2987
|
+
var previousResult = result;
|
|
2988
|
+
var previewSnapshot = edited;
|
|
2925
2989
|
abortRef.current = controller;
|
|
2926
2990
|
setBusy(true);
|
|
2927
|
-
setNote(mode === 'assemble' ? '正在组装,不调用模型…' : '
|
|
2928
|
-
|
|
2929
|
-
setSuggestions(null); setSelectedSuggestions([]); setSuggestionUndo([]);
|
|
2930
|
-
lastWrittenRef.current = null;
|
|
2991
|
+
setNote(mode === 'assemble' ? '正在组装,不调用模型…' : supportsLocalFaithful && editing === 'faithful'
|
|
2992
|
+
? '正在忠实整理…需要分段时可能调用模型并产生费用' : '正在调用模型优化…这可能产生模型费用');
|
|
2931
2993
|
promptRequest('optimize', {
|
|
2932
2994
|
text: draft,
|
|
2933
2995
|
target: target,
|
|
@@ -2943,14 +3005,20 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2943
3005
|
if (data.original !== draft) throw new Error('结果原稿与本次请求不一致');
|
|
2944
3006
|
if (mode === 'optimize' && supportsFaithful && editing === 'faithful'
|
|
2945
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
|
+
}
|
|
2946
3011
|
setResult(data);
|
|
2947
3012
|
setEdited(data.optimized);
|
|
3013
|
+
setSuggestions(null); setSelectedSuggestions([]); setSuggestionUndo([]);
|
|
3014
|
+
lastWrittenRef.current = null;
|
|
2948
3015
|
resultSessionRef.current = requestSessionId;
|
|
2949
|
-
setNote('
|
|
3016
|
+
setNote(data.execution?.reason === 'single-part-faithful' && data.execution.modelInvocations === 0
|
|
3017
|
+
? '原文无需分段,未调用模型;尚未写回输入框' : '已生成预览,尚未写回输入框');
|
|
2950
3018
|
}).catch(function (err) {
|
|
2951
3019
|
if (abortRef.current !== controller) return;
|
|
2952
3020
|
if (controller.signal.aborted) setNote('已取消');
|
|
2953
|
-
else setNote(String(err && err.message || err));
|
|
3021
|
+
else setNote(String(err && err.message || err) + (previousResult ? ';原有预览已保留' : ''));
|
|
2954
3022
|
}).finally(function () {
|
|
2955
3023
|
if (abortRef.current === controller) { abortRef.current = null; setBusy(false); }
|
|
2956
3024
|
});
|
|
@@ -2980,7 +3048,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
2980
3048
|
|| typeof item.text !== 'string' || !item.text.trim() || item.text.length > 600) return false;
|
|
2981
3049
|
ids.push(item.id); return true;
|
|
2982
3050
|
}) || !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: [] });
|
|
3051
|
+
setSuggestions({ source: snapshot, items: data.suggestions, questions: data.questions, adopted: [], processing: data.processing });
|
|
2984
3052
|
setSelectedSuggestions([]); setSuggestionUndo([]);
|
|
2985
3053
|
setNote('建议尚未加入正文;只有采纳所选建议才会改变预览。');
|
|
2986
3054
|
}).catch(function (err) {
|
|
@@ -3060,7 +3128,8 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
3060
3128
|
event.target.value = '';
|
|
3061
3129
|
if (!file) return;
|
|
3062
3130
|
file.text().then(function (raw) {
|
|
3063
|
-
var parsed
|
|
3131
|
+
var parsed;
|
|
3132
|
+
try { parsed = JSON.parse(raw); } catch (_) { throw new Error('文件不是有效 JSON,请检查格式后重新导入'); }
|
|
3064
3133
|
return promptRequest('import', { config: parsed });
|
|
3065
3134
|
}).then(function (data) {
|
|
3066
3135
|
publishConfig(data);
|
|
@@ -3086,11 +3155,14 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
3086
3155
|
}
|
|
3087
3156
|
}
|
|
3088
3157
|
|
|
3089
|
-
function resetConfig() {
|
|
3090
|
-
if (
|
|
3091
|
-
|
|
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) {
|
|
3092
3164
|
publishConfig(data);
|
|
3093
|
-
setNote('
|
|
3165
|
+
setNote(scope === 'instructions' ? '已恢复默认指令;模型路由、生成参数与入口开关保持不变' : '已恢复全部 Iris 提示词设置');
|
|
3094
3166
|
}).catch(function (err) {
|
|
3095
3167
|
setNote('重置失败:' + String(err && err.message || err));
|
|
3096
3168
|
});
|
|
@@ -3120,6 +3192,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
3120
3192
|
var resultTarget = result && PROMPT_TARGET_OPTIONS.find(function (item) { return item.value === result.target; });
|
|
3121
3193
|
var config = configState && configState.config;
|
|
3122
3194
|
var targetTemplate = config && config.targets && config.targets[target];
|
|
3195
|
+
var requestConfigFeedback = result && promptConfigFeedback({ feedback: result.configFeedback });
|
|
3123
3196
|
function ruleField(label, value, update, placeholder) {
|
|
3124
3197
|
return React.createElement('label', { className: 'iris-po-field' }, React.createElement('span', {}, label),
|
|
3125
3198
|
React.createElement('textarea', { value: value, disabled: busy || !supportsRules, placeholder: placeholder,
|
|
@@ -3141,7 +3214,8 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
3141
3214
|
React.createElement('strong', { className: 'iris-po-title' }, React.createElement('span', { className: 'iris-po-mark' }, '🫧'), 'Iris 提示词优化'),
|
|
3142
3215
|
React.createElement('button', { className: 'iris-po-close', onClick: closePanel, title: '关闭', 'aria-label': '关闭提示词优化' }, '✕')),
|
|
3143
3216
|
React.createElement('div', { className: 'iris-po-intro' },
|
|
3144
|
-
|
|
3217
|
+
React.createElement('div', { className: 'iris-po-muted' }, '按用途整理草稿,先预览和编辑,再决定是否写回。'),
|
|
3218
|
+
promptConfigFeedback(configState),
|
|
3145
3219
|
React.createElement('div', { className: 'iris-po-principles' },
|
|
3146
3220
|
React.createElement('span', {}, '保留原意'), React.createElement('span', {}, '可查看改动'), React.createElement('span', {}, '确认后写回'))),
|
|
3147
3221
|
React.createElement('details', { className: 'iris-po-section', open: generalOpen,
|
|
@@ -3177,7 +3251,9 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
3177
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' ? '忠实整理' : '改写表达' : '智能优化'),
|
|
3178
3252
|
React.createElement('button', { className: 'iris-po-btn', disabled: busy || !supportsAssembly || !draft || !draft.trim() || phase !== 'plain' || refCount > 0, onClick: function () { optimize('assemble'); } }, '只组装(不调用模型)'),
|
|
3179
3253
|
busy ? React.createElement('button', { className: 'iris-po-btn', onClick: cancel }, '取消') : null),
|
|
3180
|
-
React.createElement('div', { className: 'iris-po-muted' },
|
|
3254
|
+
React.createElement('div', { className: 'iris-po-muted' }, supportsLocalFaithful
|
|
3255
|
+
? '单片段忠实整理与只组装不调用模型;其他优化及获取建议可能产生模型费用。'
|
|
3256
|
+
: '智能优化调用文本模型,可能产生费用;只组装原稿与前后缀。'),
|
|
3181
3257
|
result ? React.createElement('div', { className: 'iris-po-result' },
|
|
3182
3258
|
React.createElement('div', { className: 'iris-po-result-label' }, '结果预览 · ' + (resultTarget ? resultTarget.label + ' · ' : '') + '可编辑 · ' + edited.length + ' 字符'),
|
|
3183
3259
|
React.createElement('textarea', { 'aria-label': '结果预览', value: edited, onChange: function (event) { editPreview(event.target.value); } }),
|
|
@@ -3197,6 +3273,7 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
3197
3273
|
!suggestions.items.length ? React.createElement('div', { className: 'iris-po-muted' }, '没有额外补充建议。') : null,
|
|
3198
3274
|
suggestions.questions.length ? React.createElement('div', { className: 'iris-po-suggestion' }, React.createElement('strong', {}, '待确认 · 尚未作为要求'),
|
|
3199
3275
|
suggestions.questions.map(function (question, index) { return React.createElement('p', { key: index }, question); })) : null,
|
|
3276
|
+
promptRequestSnapshot(suggestions.processing, '本次建议完整指令'),
|
|
3200
3277
|
React.createElement('div', { className: 'iris-po-actions' },
|
|
3201
3278
|
React.createElement('button', { className: 'iris-po-btn', disabled: busy || !selectedSuggestions.length, onClick: adoptSuggestions }, '采纳所选建议'),
|
|
3202
3279
|
React.createElement('button', { className: 'iris-po-btn', disabled: busy || !suggestionUndo.length, onClick: undoSuggestions }, '撤回上次采纳'))) : null) : null,
|
|
@@ -3212,7 +3289,13 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
3212
3289
|
return React.createElement('div', { className: 'iris-po-diff', key: rule.id }, rule.label + '(' + (rule.kind === 'optimization' ? '指导改写' : rule.position === 'prefix' ? '输出前缀' : '输出后缀') + ')\n' + rule.text);
|
|
3213
3290
|
}) : React.createElement('div', { className: 'iris-po-muted' }, '未选择额外规则'),
|
|
3214
3291
|
result.processing ? React.createElement('div', { className: 'iris-po-diff' }, '优化处理规则:\n' + result.processing.systemPrompt + '\n\n目标模板:\n' + result.processing.targetTemplate) : null),
|
|
3215
|
-
|
|
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,
|
|
3216
3299
|
React.createElement('div', { className: 'iris-po-actions' },
|
|
3217
3300
|
React.createElement('button', { className: 'iris-po-primary', onClick: function () { replaceDraft(edited); } }, '写回输入框'),
|
|
3218
3301
|
React.createElement('button', { className: 'iris-po-btn', onClick: copyResult }, '复制'),
|
|
@@ -3226,11 +3309,13 @@ window.__ModuleLoader__.load({ id: '@mokuyoaxis/dsh-iris', factory: (require) =>
|
|
|
3226
3309
|
React.createElement('div', { className: 'iris-po-template' }, config && config.systemPrompt || '配置尚未载入'),
|
|
3227
3310
|
React.createElement('div', { className: 'iris-po-template' }, targetTemplate || '当前后端未提供此目标模板')),
|
|
3228
3311
|
React.createElement('div', { className: 'iris-po-result-label' }, 'JSON 配置'),
|
|
3229
|
-
React.createElement('div', { className: 'iris-po-muted' }, '
|
|
3312
|
+
React.createElement('div', { className: 'iris-po-muted' }, '导入会替换当前配置;局部 JSON 缺少的字段使用内置默认值。可导出后修改 systemPrompt、targets、route 和 generation,再重新导入。'),
|
|
3230
3313
|
React.createElement('div', { className: 'iris-po-actions' },
|
|
3231
3314
|
React.createElement('button', { className: 'iris-po-btn', onClick: exportConfig }, '导出'),
|
|
3232
|
-
React.createElement('button', { className: 'iris-po-btn', onClick: function () { if (fileRef.current) fileRef.current.click(); } }, '导入'),
|
|
3233
|
-
React.createElement('button', { className: 'iris-po-btn
|
|
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'); } }, '恢复全部设置'),
|
|
3234
3319
|
React.createElement('input', { ref: fileRef, className: 'iris-po-file', type: 'file', accept: 'application/json,.json', onChange: importConfig })),
|
|
3235
3320
|
React.createElement('div', { className: 'iris-po-muted' }, '当前:' + (configState && configState.source === 'custom' ? '用户 JSON' : 'Iris 默认'))),
|
|
3236
3321
|
React.createElement('button', { className: 'iris-po-disable', disabled: busy, onClick: disableEntry }, '关闭此对话入口(工作台仍保留)')));
|
package/lib/command-service.js
CHANGED
|
@@ -20,6 +20,7 @@ import { GenerationInputError, normalizeGenerationInput } from './generation-inp
|
|
|
20
20
|
import { videoTaskCandidates } from './video-input.js';
|
|
21
21
|
import { waitForCoreTask } from './task-wait.js';
|
|
22
22
|
import { planCoreDeletion, planCoreCleanup, quarantineCoreFiles, inspectCoreTransactions, restoreCoreTransaction } from './core-maintenance.js';
|
|
23
|
+
import { deliveryCacheNeedsProvider } from './core-delivery-cache.js';
|
|
23
24
|
|
|
24
25
|
export const COMMAND_CONTRACT_VERSION = 0;
|
|
25
26
|
export const CORE_COMMANDS = Object.freeze([
|
|
@@ -366,16 +367,20 @@ export function createCommandService(runtime, ports = {}) {
|
|
|
366
367
|
});
|
|
367
368
|
}
|
|
368
369
|
// task.redeliver:只对 outcome=succeeded / deliveryState=failed 的任务重新取回
|
|
369
|
-
//
|
|
370
|
+
// 已生成产物:同步结果读私有缓存,异步结果 re-poll;绝不 submit。
|
|
370
371
|
if (name === 'task.redeliver') {
|
|
371
372
|
exactInput(input, ['task_id']);
|
|
372
373
|
return runtime.run('execute', async ({ dataRoot }) => {
|
|
373
374
|
const before = inspectCoreTask(dataRoot, input.task_id);
|
|
374
|
-
if (before.outcome !== 'succeeded' || before.deliveryState !== 'failed' || !before.remoteTaskId) throw commandError('IRIS_TASK_NOT_REDELIVERABLE', 'Task 不处于可重新交付状态;不会重新生成');
|
|
375
|
+
if (before.outcome !== 'succeeded' || before.deliveryState !== 'failed' || (!before.remoteTaskId && !before.deliveryCache)) throw commandError('IRIS_TASK_NOT_REDELIVERABLE', 'Task 不处于可重新交付状态;不会重新生成');
|
|
376
|
+
if (before.deliveryCache && !deliveryCacheNeedsProvider(dataRoot, before)) {
|
|
377
|
+
const task = await createProviderTaskRunner(runtime).redeliver(input.task_id);
|
|
378
|
+
return Object.freeze({ contractVersion: COMMAND_CONTRACT_VERSION, command: name, taskId: task.id, task });
|
|
379
|
+
}
|
|
375
380
|
if (typeof ports.resolveTaskAdapter !== 'function') throw commandError('IRIS_COMMAND_PROVIDER_REQUIRED', 'task.redeliver 需要宿主显式提供 Task Provider resolver');
|
|
376
381
|
const adapter = await ports.resolveTaskAdapter(before);
|
|
377
382
|
if (!adapter || adapter.id !== before.providerId || !adapter.capabilities?.includes(before.capability)) throw commandError('IRIS_PROVIDER_TASK_IDENTITY_MISMATCH', 'Task Provider resolver 返回了不匹配的 Adapter');
|
|
378
|
-
if (!hasProviderOperation(adapter, 'poll')) throw commandError('IRIS_PROVIDER_TASK_OBSERVE_UNSUPPORTED', 'Task 原 Provider 不支持 poll;不会重新生成');
|
|
383
|
+
if (!before.deliveryCache && !hasProviderOperation(adapter, 'poll')) throw commandError('IRIS_PROVIDER_TASK_OBSERVE_UNSUPPORTED', 'Task 原 Provider 不支持 poll;不会重新生成');
|
|
379
384
|
const task = await createProviderTaskRunner(runtime).redeliver(input.task_id, adapter);
|
|
380
385
|
return Object.freeze({ contractVersion: COMMAND_CONTRACT_VERSION, command: name, taskId: task.id, task });
|
|
381
386
|
});
|
|
@@ -415,6 +420,10 @@ export function createCommandService(runtime, ports = {}) {
|
|
|
415
420
|
throw commandError('IRIS_TASK_NOT_RETRYABLE', '只有终态且未成功交付的 Task 可以重试为新任务');
|
|
416
421
|
}
|
|
417
422
|
let normalized;
|
|
423
|
+
if (before.capability === 'music' && input.provider_input &&
|
|
424
|
+
['retry_502', 'retry_delay_ms'].some(field => Object.hasOwn(input.provider_input, field))) {
|
|
425
|
+
throw commandError('IRIS_COMMAND_INPUT_INVALID', 'task.retry 只执行一次重新生成;502重试配置请使用Music生成入口');
|
|
426
|
+
}
|
|
418
427
|
try { normalized = normalizeGenerationInput(before.capability, input.provider_input, { allowVideoPaths: before.capability === 'video' }); }
|
|
419
428
|
catch (error) {
|
|
420
429
|
if (error instanceof GenerationInputError) throw commandError('IRIS_COMMAND_INPUT_INVALID', error.message);
|