@nac3/forge-cli 0.2.1-alpha.60 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/agents/bridge.d.ts.map +1 -1
- package/dist/agents/bridge.js +27 -0
- package/dist/agents/bridge.js.map +1 -1
- package/dist/agents/config_resolver.d.ts.map +1 -1
- package/dist/agents/config_resolver.js +15 -15
- package/dist/agents/config_resolver.js.map +1 -1
- package/dist/agents/runner.d.ts +8 -0
- package/dist/agents/runner.d.ts.map +1 -1
- package/dist/agents/runner.js +58 -11
- package/dist/agents/runner.js.map +1 -1
- package/dist/agents/system_prompt.d.ts.map +1 -1
- package/dist/agents/system_prompt.js +2 -1
- package/dist/agents/system_prompt.js.map +1 -1
- package/dist/bin/yf.d.ts.map +1 -1
- package/dist/bin/yf.js +18 -0
- package/dist/bin/yf.js.map +1 -1
- package/dist/chat/claude.d.ts +13 -0
- package/dist/chat/claude.d.ts.map +1 -1
- package/dist/chat/claude.js +95 -10
- package/dist/chat/claude.js.map +1 -1
- package/dist/chat/claude_cli.d.ts +10 -0
- package/dist/chat/claude_cli.d.ts.map +1 -1
- package/dist/chat/claude_cli.js +97 -16
- package/dist/chat/claude_cli.js.map +1 -1
- package/dist/chat/completion_hallucination_detector.d.ts +11 -0
- package/dist/chat/completion_hallucination_detector.d.ts.map +1 -1
- package/dist/chat/completion_hallucination_detector.js +37 -2
- package/dist/chat/completion_hallucination_detector.js.map +1 -1
- package/dist/chat/doctrine_digest.d.ts.map +1 -1
- package/dist/chat/doctrine_digest.js +10 -6
- package/dist/chat/doctrine_digest.js.map +1 -1
- package/dist/chat/panel.d.ts.map +1 -1
- package/dist/chat/panel.js +923 -50
- package/dist/chat/panel.js.map +1 -1
- package/dist/chat/refusal_detector.d.ts.map +1 -1
- package/dist/chat/refusal_detector.js +2 -1
- package/dist/chat/refusal_detector.js.map +1 -1
- package/dist/chat/server.d.ts +1 -0
- package/dist/chat/server.d.ts.map +1 -1
- package/dist/chat/server.js +960 -73
- package/dist/chat/server.js.map +1 -1
- package/dist/chat/session_projects.d.ts +31 -0
- package/dist/chat/session_projects.d.ts.map +1 -0
- package/dist/chat/session_projects.js +36 -0
- package/dist/chat/session_projects.js.map +1 -0
- package/dist/chat/session_store.d.ts +32 -0
- package/dist/chat/session_store.d.ts.map +1 -0
- package/dist/chat/session_store.js +58 -0
- package/dist/chat/session_store.js.map +1 -0
- package/dist/chat/tooling_section.d.ts +11 -0
- package/dist/chat/tooling_section.d.ts.map +1 -0
- package/dist/chat/tooling_section.js +70 -0
- package/dist/chat/tooling_section.js.map +1 -0
- package/dist/chat/tools/app_backend_dispatch.d.ts.map +1 -1
- package/dist/chat/tools/app_backend_dispatch.js +44 -1
- package/dist/chat/tools/app_backend_dispatch.js.map +1 -1
- package/dist/chat/tools/fs_editor.d.ts.map +1 -1
- package/dist/chat/tools/fs_editor.js +53 -3
- package/dist/chat/tools/fs_editor.js.map +1 -1
- package/dist/chat/tools/graph.js +2 -2
- package/dist/chat/tools/graph.js.map +1 -1
- package/dist/chat/tools/keys.d.ts.map +1 -1
- package/dist/chat/tools/keys.js +5 -18
- package/dist/chat/tools/keys.js.map +1 -1
- package/dist/chat/tools/nac3_invoke.d.ts.map +1 -1
- package/dist/chat/tools/nac3_invoke.js +16 -7
- package/dist/chat/tools/nac3_invoke.js.map +1 -1
- package/dist/chat/tools/shell.d.ts +6 -0
- package/dist/chat/tools/shell.d.ts.map +1 -1
- package/dist/chat/tools/shell.js +39 -0
- package/dist/chat/tools/shell.js.map +1 -1
- package/dist/chat/tools/workflow.d.ts +20 -0
- package/dist/chat/tools/workflow.d.ts.map +1 -1
- package/dist/chat/tools/workflow.js +98 -0
- package/dist/chat/tools/workflow.js.map +1 -1
- package/dist/chat/tools.d.ts.map +1 -1
- package/dist/chat/tools.js +79 -1
- package/dist/chat/tools.js.map +1 -1
- package/dist/chat/turn_control.d.ts +42 -0
- package/dist/chat/turn_control.d.ts.map +1 -0
- package/dist/chat/turn_control.js +53 -0
- package/dist/chat/turn_control.js.map +1 -0
- package/dist/commands/brain.js +1 -1
- package/dist/commands/chat.d.ts.map +1 -1
- package/dist/commands/chat.js +61 -10
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/graph.js +9 -9
- package/dist/commands/graph.js.map +1 -1
- package/dist/commands/keys_setup.d.ts +10 -28
- package/dist/commands/keys_setup.d.ts.map +1 -1
- package/dist/commands/keys_setup.js +54 -228
- package/dist/commands/keys_setup.js.map +1 -1
- package/dist/commands/license.d.ts.map +1 -1
- package/dist/commands/license.js +52 -28
- package/dist/commands/license.js.map +1 -1
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +62 -6
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/mobile.d.ts +1 -1
- package/dist/commands/mobile.d.ts.map +1 -1
- package/dist/commands/mobile.js +32 -28
- package/dist/commands/mobile.js.map +1 -1
- package/dist/commands/onboard.d.ts +19 -0
- package/dist/commands/onboard.d.ts.map +1 -0
- package/dist/commands/onboard.js +126 -0
- package/dist/commands/onboard.js.map +1 -0
- package/dist/commands/pending.d.ts.map +1 -1
- package/dist/commands/pending.js +25 -1
- package/dist/commands/pending.js.map +1 -1
- package/dist/commands/projects.js +25 -1
- package/dist/commands/projects.js.map +1 -1
- package/dist/commands/review-screens.js +28 -1
- package/dist/commands/review-screens.js.map +1 -1
- package/dist/commands/workflow.d.ts +18 -0
- package/dist/commands/workflow.d.ts.map +1 -0
- package/dist/commands/workflow.js +253 -0
- package/dist/commands/workflow.js.map +1 -0
- package/dist/core/brain_config.d.ts +2 -0
- package/dist/core/brain_config.d.ts.map +1 -1
- package/dist/core/brain_config.js +18 -3
- package/dist/core/brain_config.js.map +1 -1
- package/dist/core/cf_tunnel.d.ts.map +1 -1
- package/dist/core/cf_tunnel.js +8 -2
- package/dist/core/cf_tunnel.js.map +1 -1
- package/dist/core/cost_router.d.ts.map +1 -1
- package/dist/core/cost_router.js +11 -2
- package/dist/core/cost_router.js.map +1 -1
- package/dist/core/desktop_open.d.ts +16 -0
- package/dist/core/desktop_open.d.ts.map +1 -0
- package/dist/core/desktop_open.js +114 -0
- package/dist/core/desktop_open.js.map +1 -0
- package/dist/core/keys_envelope.d.ts.map +1 -1
- package/dist/core/keys_envelope.js +5 -1
- package/dist/core/keys_envelope.js.map +1 -1
- package/dist/core/limits_config.d.ts +5 -0
- package/dist/core/limits_config.d.ts.map +1 -1
- package/dist/core/limits_config.js +5 -0
- package/dist/core/limits_config.js.map +1 -1
- package/dist/core/logger.d.ts.map +1 -1
- package/dist/core/logger.js +3 -0
- package/dist/core/logger.js.map +1 -1
- package/dist/core/pairing.d.ts +6 -0
- package/dist/core/pairing.d.ts.map +1 -1
- package/dist/core/pairing.js +41 -2
- package/dist/core/pairing.js.map +1 -1
- package/dist/core/provider_keys.d.ts.map +1 -1
- package/dist/core/provider_keys.js +25 -12
- package/dist/core/provider_keys.js.map +1 -1
- package/dist/core/provider_models.js +1 -1
- package/dist/deploy/cloudflare.d.ts.map +1 -1
- package/dist/deploy/cloudflare.js +13 -2
- package/dist/deploy/cloudflare.js.map +1 -1
- package/dist/design/yujin_design_resolver.d.ts +16 -0
- package/dist/design/yujin_design_resolver.d.ts.map +1 -1
- package/dist/design/yujin_design_resolver.js +103 -20
- package/dist/design/yujin_design_resolver.js.map +1 -1
- package/dist/docs/doctrine/default-design-system.md +32 -0
- package/dist/docs/doctrine/office-tools.md +20 -0
- package/dist/docs/doctrine/pizarron.md +40 -0
- package/dist/docs/doctrine/verb-composition.md +16 -0
- package/dist/docs/doctrine/workflow-kinds.md +39 -22
- package/dist/docs/doctrine/workflow.md +12 -4
- package/dist/license/auto_sync.d.ts +22 -0
- package/dist/license/auto_sync.d.ts.map +1 -0
- package/dist/license/auto_sync.js +91 -0
- package/dist/license/auto_sync.js.map +1 -0
- package/dist/license/handle_key.d.ts +32 -0
- package/dist/license/handle_key.d.ts.map +1 -0
- package/dist/license/handle_key.js +56 -0
- package/dist/license/handle_key.js.map +1 -0
- package/dist/license/hito4_client.d.ts.map +1 -1
- package/dist/license/hito4_client.js +63 -25
- package/dist/license/hito4_client.js.map +1 -1
- package/dist/llm/gemini_client.d.ts.map +1 -1
- package/dist/llm/gemini_client.js +5 -2
- package/dist/llm/gemini_client.js.map +1 -1
- package/dist/llm/managed_client.d.ts.map +1 -1
- package/dist/llm/managed_client.js +14 -9
- package/dist/llm/managed_client.js.map +1 -1
- package/dist/mcp/plan_bridges.d.ts +17 -0
- package/dist/mcp/plan_bridges.d.ts.map +1 -0
- package/dist/mcp/plan_bridges.js +119 -0
- package/dist/mcp/plan_bridges.js.map +1 -0
- package/dist/migrate/ai-apply.d.ts.map +1 -1
- package/dist/migrate/ai-apply.js +25 -2
- package/dist/migrate/ai-apply.js.map +1 -1
- package/dist/nac3/internal_manifest.d.ts.map +1 -1
- package/dist/nac3/internal_manifest.js +200 -10
- package/dist/nac3/internal_manifest.js.map +1 -1
- package/dist/nac3/verb_tier_map.d.ts.map +1 -1
- package/dist/nac3/verb_tier_map.js +3 -0
- package/dist/nac3/verb_tier_map.js.map +1 -1
- package/dist/onboarding/concierge_client.d.ts +34 -0
- package/dist/onboarding/concierge_client.d.ts.map +1 -0
- package/dist/onboarding/concierge_client.js +80 -0
- package/dist/onboarding/concierge_client.js.map +1 -0
- package/dist/onboarding/next_step.d.ts +32 -0
- package/dist/onboarding/next_step.d.ts.map +1 -0
- package/dist/onboarding/next_step.js +60 -0
- package/dist/onboarding/next_step.js.map +1 -0
- package/dist/pending/store.d.ts +7 -0
- package/dist/pending/store.d.ts.map +1 -1
- package/dist/pending/store.js +19 -0
- package/dist/pending/store.js.map +1 -1
- package/dist/pending/types.d.ts +20 -0
- package/dist/pending/types.d.ts.map +1 -1
- package/dist/pending/types.js.map +1 -1
- package/dist/reader/parsers/epub.d.ts.map +1 -1
- package/dist/reader/parsers/epub.js +6 -1
- package/dist/reader/parsers/epub.js.map +1 -1
- package/dist/relay/board_snapshot.d.ts +69 -0
- package/dist/relay/board_snapshot.d.ts.map +1 -0
- package/dist/relay/board_snapshot.js +149 -0
- package/dist/relay/board_snapshot.js.map +1 -0
- package/dist/relay/command_consumer.d.ts +97 -0
- package/dist/relay/command_consumer.d.ts.map +1 -0
- package/dist/relay/command_consumer.js +135 -0
- package/dist/relay/command_consumer.js.map +1 -0
- package/dist/relay/mock_relay.d.ts +34 -0
- package/dist/relay/mock_relay.d.ts.map +1 -0
- package/dist/relay/mock_relay.js +93 -0
- package/dist/relay/mock_relay.js.map +1 -0
- package/dist/relay/relay_client.d.ts +28 -0
- package/dist/relay/relay_client.d.ts.map +1 -0
- package/dist/relay/relay_client.js +80 -0
- package/dist/relay/relay_client.js.map +1 -0
- package/dist/relay/relay_publisher.d.ts +59 -0
- package/dist/relay/relay_publisher.d.ts.map +1 -0
- package/dist/relay/relay_publisher.js +101 -0
- package/dist/relay/relay_publisher.js.map +1 -0
- package/dist/relay/types.d.ts +54 -0
- package/dist/relay/types.d.ts.map +1 -0
- package/dist/relay/types.js +8 -0
- package/dist/relay/types.js.map +1 -0
- package/dist/semantic_graph/ast_extract.d.ts +5 -0
- package/dist/semantic_graph/ast_extract.d.ts.map +1 -1
- package/dist/semantic_graph/ast_extract.js +46 -0
- package/dist/semantic_graph/ast_extract.js.map +1 -1
- package/dist/semantic_graph/derive.d.ts +12 -0
- package/dist/semantic_graph/derive.d.ts.map +1 -1
- package/dist/semantic_graph/derive.js +325 -6
- package/dist/semantic_graph/derive.js.map +1 -1
- package/dist/semantic_graph/store.d.ts +9 -0
- package/dist/semantic_graph/store.d.ts.map +1 -1
- package/dist/semantic_graph/store.js +23 -1
- package/dist/semantic_graph/store.js.map +1 -1
- package/dist/semantic_graph/types.d.ts +1 -1
- package/dist/semantic_graph/types.d.ts.map +1 -1
- package/dist/support/github_dispatcher.d.ts.map +1 -1
- package/dist/support/github_dispatcher.js +30 -2
- package/dist/support/github_dispatcher.js.map +1 -1
- package/dist/tasks/supervisor.d.ts +88 -0
- package/dist/tasks/supervisor.d.ts.map +1 -0
- package/dist/tasks/supervisor.js +308 -0
- package/dist/tasks/supervisor.js.map +1 -0
- package/dist/tasks/task_type.d.ts +11 -0
- package/dist/tasks/task_type.d.ts.map +1 -0
- package/dist/tasks/task_type.js +21 -0
- package/dist/tasks/task_type.js.map +1 -0
- package/dist/vault/store.d.ts.map +1 -1
- package/dist/vault/store.js +6 -1
- package/dist/vault/store.js.map +1 -1
- package/dist/version.d.ts +3 -5
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +3 -5
- package/dist/version.js.map +1 -1
- package/dist/voice/providers/google.d.ts.map +1 -1
- package/dist/voice/providers/google.js +6 -4
- package/dist/voice/providers/google.js.map +1 -1
- package/dist/workflow/config.d.ts +43 -0
- package/dist/workflow/config.d.ts.map +1 -0
- package/dist/workflow/config.js +199 -0
- package/dist/workflow/config.js.map +1 -0
- package/dist/workflow/instances.d.ts +40 -0
- package/dist/workflow/instances.d.ts.map +1 -0
- package/dist/workflow/instances.js +122 -0
- package/dist/workflow/instances.js.map +1 -0
- package/dist/workflow/multi_view.d.ts +27 -0
- package/dist/workflow/multi_view.d.ts.map +1 -0
- package/dist/workflow/multi_view.js +76 -0
- package/dist/workflow/multi_view.js.map +1 -0
- package/dist/workflow/orchestrator.d.ts +111 -0
- package/dist/workflow/orchestrator.d.ts.map +1 -0
- package/dist/workflow/orchestrator.js +241 -0
- package/dist/workflow/orchestrator.js.map +1 -0
- package/docs/doctrine/default-design-system.md +32 -0
- package/docs/doctrine/office-tools.md +20 -0
- package/docs/doctrine/pizarron.md +40 -0
- package/docs/doctrine/verb-composition.md +16 -0
- package/docs/doctrine/workflow-kinds.md +39 -22
- package/docs/doctrine/workflow.md +12 -4
- package/package.json +2 -1
- package/src/i18n/catalogs/ar.json +223 -210
- package/src/i18n/catalogs/de.json +223 -210
- package/src/i18n/catalogs/en.json +223 -210
- package/src/i18n/catalogs/es.json +223 -210
- package/src/i18n/catalogs/fr.json +223 -210
- package/src/i18n/catalogs/hi.json +223 -210
- package/src/i18n/catalogs/it.json +223 -210
- package/src/i18n/catalogs/ja.json +223 -210
- package/src/i18n/catalogs/pt.json +223 -210
- package/src/i18n/catalogs/zh.json +223 -210
package/dist/chat/panel.js
CHANGED
|
@@ -34,6 +34,19 @@ export function renderPanelHtml(cfg) {
|
|
|
34
34
|
const tr = {
|
|
35
35
|
inputPlaceholder: tForLang(lang, 'panel.input.placeholder'),
|
|
36
36
|
sendButton: tForLang(lang, 'panel.button.send'),
|
|
37
|
+
stopButton: tForLang(lang, 'panel.button.stop'),
|
|
38
|
+
statusStopping: tForLang(lang, 'panel.status.stopping'),
|
|
39
|
+
statusNoActiveTurn: tForLang(lang, 'panel.status.noActiveTurn'),
|
|
40
|
+
statusInterruptFailed: tForLang(lang, 'panel.status.interruptFailed'),
|
|
41
|
+
statusTurnInterrupted: tForLang(lang, 'panel.status.turnInterrupted'),
|
|
42
|
+
labelInterrupted: tForLang(lang, 'panel.label.interrupted'),
|
|
43
|
+
queueLabel: tForLang(lang, 'panel.queue.label'),
|
|
44
|
+
queueRemove: tForLang(lang, 'panel.queue.remove'),
|
|
45
|
+
projectSwitchHint: tForLang(lang, 'panel.project.switchHint'),
|
|
46
|
+
projectTitle: tForLang(lang, 'panel.project.title'),
|
|
47
|
+
projectAdd: tForLang(lang, 'panel.project.add'),
|
|
48
|
+
projectAddPrompt: tForLang(lang, 'panel.project.addPrompt'),
|
|
49
|
+
projectSwitching: tForLang(lang, 'panel.project.switching'),
|
|
37
50
|
micButton: tForLang(lang, 'panel.button.mic'),
|
|
38
51
|
micButtonRec: tForLang(lang, 'panel.button.mic.recording'),
|
|
39
52
|
ttsButton: tForLang(lang, 'panel.button.tts'),
|
|
@@ -308,6 +321,26 @@ body {
|
|
|
308
321
|
/* alpha.59i -- shell kill button on active shell.exec tabs */
|
|
309
322
|
#yf-full .pz-tab-kill { margin-left: 4px; width: 18px; height: 18px; border-radius: 50%; border: 1px solid rgba(208,64,64,0.5); background: rgba(208,64,64,0.15); color: #ff8a8a; cursor: pointer; font-size: 10px; line-height: 1; padding: 0; display: inline-flex; align-items: center; justify-content: center; }
|
|
310
323
|
#yf-full .pz-tab-kill:hover { background: rgba(208,64,64,0.35); border-color: rgba(208,64,64,0.8); }
|
|
324
|
+
/* PND-007 -- Tareas tab */
|
|
325
|
+
#yf-full .pz-tasks-wrap { flex: 1; overflow-y: auto; padding: 14px 16px; }
|
|
326
|
+
#yf-full .pz-tasks-new { display: flex; gap: 8px; margin-bottom: 14px; }
|
|
327
|
+
#yf-full .pz-tasks-input { flex: 1; font: inherit; font-size: 13px; padding: 7px 11px; border: 1px solid var(--line, rgba(0,0,0,0.14)); border-radius: 8px; background: var(--bg-1, #fafafa); color: var(--ink, #1a1a1a); }
|
|
328
|
+
#yf-full .pz-tasks-run { font: inherit; font-size: 12px; font-weight: 600; padding: 7px 14px; border: none; border-radius: 8px; background: #4f5b87; color: white; cursor: pointer; }
|
|
329
|
+
#yf-full .pz-tasks-run:hover { background: #3f4a73; }
|
|
330
|
+
#yf-full .pz-tasks-empty { color: var(--ink-subtle, #9a9a9a); font-size: 13px; padding: 20px 4px; text-align: center; }
|
|
331
|
+
#yf-full .pz-task { display: flex; align-items: center; gap: 12px; padding: 11px 12px; border: 1px solid var(--line, rgba(0,0,0,0.10)); border-radius: 10px; margin-bottom: 8px; background: var(--bg-1, #fcfcfc); }
|
|
332
|
+
#yf-full .pz-task-ic { font-size: 20px; width: 26px; text-align: center; flex-shrink: 0; }
|
|
333
|
+
#yf-full .pz-task-main { flex: 1; min-width: 0; }
|
|
334
|
+
#yf-full .pz-task-label { font-size: 13px; font-weight: 600; color: var(--ink, #1a1a1a); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
335
|
+
#yf-full .pz-task-cmd { font-size: 11px; color: var(--ink-subtle, #9a9a9a); font-family: ui-monospace, monospace; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 2px; }
|
|
336
|
+
#yf-full .pz-task-side { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
|
|
337
|
+
#yf-full .pz-task-badge { font-size: 10px; text-transform: uppercase; letter-spacing: 0.5px; border: 1px solid; border-radius: 6px; padding: 2px 7px; }
|
|
338
|
+
#yf-full .pz-task-kill, #yf-full .pz-task-log { font: inherit; font-size: 11px; padding: 3px 9px; border-radius: 6px; border: 1px solid var(--line, rgba(0,0,0,0.16)); background: transparent; color: var(--ink-muted, #6a6a6a); cursor: pointer; }
|
|
339
|
+
#yf-full .pz-task-kill { border-color: rgba(208,64,64,0.5); color: #c8472e; }
|
|
340
|
+
#yf-full .pz-task-del { font: inherit; font-size: 14px; line-height: 1; padding: 2px 8px; border-radius: 6px; border: 1px solid var(--line, rgba(0,0,0,0.16)); background: transparent; color: var(--ink-subtle, #9a9a9a); cursor: pointer; }
|
|
341
|
+
#yf-full .pz-task-del:hover { border-color: rgba(208,64,64,0.5); color: #c8472e; }
|
|
342
|
+
#yf-full .pz-task-logbox:not(:empty) { margin: -2px 0 10px 38px; }
|
|
343
|
+
#yf-full .pz-task-logpre { font-family: ui-monospace, monospace; font-size: 11px; line-height: 1.5; color: var(--ink-muted, #555); background: var(--bg-2, #f4f4f4); border-radius: 8px; padding: 10px 12px; overflow-x: auto; white-space: pre-wrap; max-height: 220px; }
|
|
311
344
|
#yf-full .pz-tab-body {
|
|
312
345
|
flex: 1; overflow: hidden; min-height: 0;
|
|
313
346
|
display: flex; flex-direction: column;
|
|
@@ -569,6 +602,7 @@ body {
|
|
|
569
602
|
#yf-full .pz-cw-row.k-bash { border-left-color: #6f8b5e; }
|
|
570
603
|
#yf-full .pz-cw-row.k-read, #yf-full .pz-cw-row.k-search { border-left-color: var(--indigo, #4f5b87); }
|
|
571
604
|
#yf-full .pz-cw-head { display: flex; align-items: baseline; gap: 7px; }
|
|
605
|
+
#yf-full .pz-cw-ts { font-family: ui-monospace, Menlo, monospace; font-size: 10.5px; color: var(--ink-subtle, #a89f88); flex: 0 0 auto; font-variant-numeric: tabular-nums; }
|
|
572
606
|
#yf-full .pz-cw-ic { color: var(--ink-muted, #8a7f68); font-size: 12px; width: 14px; flex: 0 0 auto; text-align: center; }
|
|
573
607
|
#yf-full .pz-cw-target { font-family: ui-monospace, Menlo, monospace; font-size: 13px; color: var(--ink, #1a1a1a); font-weight: 600; word-break: break-all; }
|
|
574
608
|
#yf-full .pz-cw-path { font-size: 10px; color: var(--ink-subtle, #a89f88); margin-left: auto; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 45%; }
|
|
@@ -588,6 +622,22 @@ body {
|
|
|
588
622
|
background: var(--bg-1, #f5f0e6); border: 1px solid var(--border, #d4cbb8); border-radius: 8px;
|
|
589
623
|
padding: 14px 16px; margin-bottom: 14px;
|
|
590
624
|
}
|
|
625
|
+
/* PND-094 ext -- multi-workflow: cada workflow es una card separada. */
|
|
626
|
+
#yf-full .pz-wfl-card { margin-bottom: 26px; padding-bottom: 6px; border-bottom: 1px dashed var(--border, #d4cbb8); }
|
|
627
|
+
#yf-full .pz-wfl-card:last-child { border-bottom: none; }
|
|
628
|
+
/* selector entre workflows abiertos (solo con >1). */
|
|
629
|
+
#yf-full .pz-wfl-sel { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 16px; }
|
|
630
|
+
#yf-full .pz-wfl-chip {
|
|
631
|
+
font-size: 12px; color: var(--ink-muted, #6f6650); cursor: pointer;
|
|
632
|
+
background: var(--bg-1, #f5f0e6); border: 1px solid var(--border, #d4cbb8);
|
|
633
|
+
border-radius: 14px; padding: 4px 12px; font-family: inherit;
|
|
634
|
+
}
|
|
635
|
+
#yf-full .pz-wfl-chip:hover { border-color: var(--indigo, #4f5b87); }
|
|
636
|
+
#yf-full .pz-wfl-chip.active {
|
|
637
|
+
color: #fff; background: var(--indigo, #4f5b87); border-color: var(--indigo, #4f5b87); font-weight: 600;
|
|
638
|
+
}
|
|
639
|
+
#yf-full .pz-wfl-adhoc { color: var(--amber, #c8a04d); font-weight: 700; }
|
|
640
|
+
#yf-full .pz-wfl-empty-stage { opacity: 0.5; font-style: italic; }
|
|
591
641
|
#yf-full .pz-wfl-title { font-family: var(--font-kanji, "Noto Serif JP", serif); font-size: 18px; color: var(--ink, #1a1a1a); }
|
|
592
642
|
#yf-full .pz-wfl-kind {
|
|
593
643
|
display: inline-block; font-size: 11px; color: var(--indigo, #4f5b87);
|
|
@@ -624,7 +674,7 @@ body {
|
|
|
624
674
|
#yf-full .pz-wfl-hang { position: relative; margin: 0 0 0 14px; padding-top: 12px; }
|
|
625
675
|
#yf-full .pz-wfl-hang::before { content: ''; position: absolute; left: 0; top: -2px; bottom: 12px; width: 2px; background: #d4cbb8; }
|
|
626
676
|
#yf-full .pz-wfl-task {
|
|
627
|
-
position: relative; display: flex; align-items:
|
|
677
|
+
position: relative; display: flex; align-items: flex-start; gap: 6px;
|
|
628
678
|
background: var(--bg-1, #f5f0e6); border: 1px solid var(--border, #d4cbb8); border-radius: 6px;
|
|
629
679
|
padding: 5px 8px; margin-bottom: 7px; font-size: 12px; color: var(--ink-muted, #6f6650);
|
|
630
680
|
}
|
|
@@ -634,7 +684,14 @@ body {
|
|
|
634
684
|
#yf-full .pz-wfl-check { width: 14px; text-align: center; color: #c8b89a; flex: 0 0 auto; }
|
|
635
685
|
#yf-full .pz-wfl-task.wfl-done .pz-wfl-check { color: #6f8b5e; }
|
|
636
686
|
#yf-full .pz-wfl-task.wfl-active .pz-wfl-check { color: var(--amber, #c8a04d); }
|
|
637
|
-
|
|
687
|
+
/* el titulo es el item flexible: min-width:0 + word-break para que una
|
|
688
|
+
* palabra larga (p.ej. MobileManagedBrainClient) corte dentro de la card
|
|
689
|
+
* y no se desborde por la derecha. */
|
|
690
|
+
#yf-full .pz-wfl-task-title { flex: 1 1 auto; min-width: 0; line-height: 1.2; overflow-wrap: anywhere; word-break: break-word; }
|
|
691
|
+
/* Pestana BPMN 2.0 (flujos de proceso). */
|
|
692
|
+
#yf-full .pz-body.pz-bpmn-wrap { background: var(--bg-1, #f5f0e6); padding: 12px; overflow: auto; }
|
|
693
|
+
#yf-full .pz-bpmn-svg { display: block; width: 100%; height: auto; }
|
|
694
|
+
#yf-full .pz-bpmn-empty { color: var(--ink-muted, #8a7f68); font-style: italic; padding: 28px; text-align: center; }
|
|
638
695
|
#yf-full .pz-body a {
|
|
639
696
|
color: #2563eb; text-decoration: underline;
|
|
640
697
|
}
|
|
@@ -813,6 +870,26 @@ body {
|
|
|
813
870
|
.header .brand .t { display: flex; flex-direction: column; line-height: 1.1; }
|
|
814
871
|
.header .brand .t .name { font-size: 14px; font-weight: 600; }
|
|
815
872
|
.header .brand .t .proj { font-size: 11px; opacity: 0.8; }
|
|
873
|
+
.header .brand .t .proj-switch { cursor: pointer; display: inline-flex; align-items: center;
|
|
874
|
+
align-self: flex-start; gap: 4px; padding: 1px 6px; margin-left: -4px; border-radius: 6px;
|
|
875
|
+
border: 1px solid transparent; transition: background 0.12s, border-color 0.12s; }
|
|
876
|
+
.header .brand .t .proj-switch:hover,
|
|
877
|
+
.header .brand .t .proj-switch:focus-visible {
|
|
878
|
+
opacity: 1; background: rgba(255,255,255,0.16); border-color: rgba(255,255,255,0.28); outline: none; }
|
|
879
|
+
.proj-menu { position: fixed; z-index: 9999; min-width: 240px; max-width: 420px;
|
|
880
|
+
max-height: 60vh; overflow-y: auto; background: white; color: var(--ink);
|
|
881
|
+
border: 1px solid var(--border); border-radius: 8px;
|
|
882
|
+
box-shadow: 0 6px 24px rgba(0,0,0,0.18); padding: 4px; }
|
|
883
|
+
.proj-menu .proj-menu-title { font-size: 10px; text-transform: uppercase; letter-spacing: .06em;
|
|
884
|
+
color: var(--ink-muted); padding: 6px 8px 2px; }
|
|
885
|
+
.proj-menu .proj-row { display: flex; flex-direction: column; gap: 1px;
|
|
886
|
+
padding: 6px 8px; border-radius: 6px; cursor: pointer; }
|
|
887
|
+
.proj-menu .proj-row:hover { background: var(--bg2, #f1efe7); }
|
|
888
|
+
.proj-menu .proj-row.on { background: rgba(80,90,160,0.08); }
|
|
889
|
+
.proj-menu .proj-row .proj-row-name { font-size: 13px; font-weight: 600; }
|
|
890
|
+
.proj-menu .proj-row .proj-row-path { font-size: 10px; color: var(--ink-muted);
|
|
891
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
892
|
+
.proj-menu .proj-add { color: var(--indigo); font-weight: 600; border-top: 1px solid var(--border-subtle); margin-top: 2px; }
|
|
816
893
|
.header .actions { display: flex; gap: 4px; }
|
|
817
894
|
.header .actions button {
|
|
818
895
|
background: rgba(255,255,255,0.1); color: white;
|
|
@@ -1262,10 +1339,17 @@ body {
|
|
|
1262
1339
|
|
|
1263
1340
|
/* ---- input bar ---- */
|
|
1264
1341
|
.bar { border-top: 1px solid var(--border-subtle); background: white;
|
|
1265
|
-
padding: 8px; display: flex; gap: 6px; align-items:
|
|
1266
|
-
.bar input { flex: 1; padding: 8px 10px;
|
|
1342
|
+
padding: 8px; display: flex; gap: 6px; align-items: flex-end; flex-shrink: 0; }
|
|
1343
|
+
.bar input, .bar .composer-input { flex: 1; min-width: 0; padding: 8px 10px;
|
|
1267
1344
|
border: 1px solid var(--border); border-radius: 6px; font-size: 14px; font-family: inherit; }
|
|
1268
|
-
.bar input:focus { outline: none; border-color: var(--indigo); box-shadow: 0 0 0 3px rgba(79,91,135,0.12); }
|
|
1345
|
+
.bar input:focus, .bar .composer-input:focus { outline: none; border-color: var(--indigo); box-shadow: 0 0 0 3px rgba(79,91,135,0.12); }
|
|
1346
|
+
/* Auto-growing composer: one row by default, expands as you type
|
|
1347
|
+
(bubble-like, capped at ~40vh) then scrolls; Enter sends and the
|
|
1348
|
+
submit handler resets the height. Shift+Enter inserts a newline. */
|
|
1349
|
+
.bar .composer-input { resize: none; overflow-y: auto; line-height: 1.4;
|
|
1350
|
+
min-height: 36px; max-height: 40vh; }
|
|
1351
|
+
.bar .composer-input.expanded { box-shadow: 0 6px 24px rgba(26,24,21,0.16);
|
|
1352
|
+
border-color: var(--indigo); }
|
|
1269
1353
|
.bar .mic, .bar .send {
|
|
1270
1354
|
border: 0; border-radius: 50%; width: 36px; height: 36px;
|
|
1271
1355
|
display: flex; align-items: center; justify-content: center;
|
|
@@ -1284,9 +1368,25 @@ body {
|
|
|
1284
1368
|
cursor: pointer; font-size: 14px;
|
|
1285
1369
|
}
|
|
1286
1370
|
.bar .tts-toggle.on { background: var(--indigo); color: white; }
|
|
1287
|
-
.bar .send { background: var(--indigo); padding: 0
|
|
1288
|
-
font-size:
|
|
1371
|
+
.bar .send { background: var(--indigo); padding: 0 9px; width: auto; min-width: 0;
|
|
1372
|
+
flex-shrink: 0; white-space: nowrap; border-radius: 6px; font-size: 12px; font-weight: 600; }
|
|
1289
1373
|
.bar .send:disabled { background: #aaa; cursor: not-allowed; }
|
|
1374
|
+
.bar .stop { background: var(--err, #c8472e); color: white; padding: 0 9px; width: auto;
|
|
1375
|
+
min-width: 0; flex-shrink: 0; white-space: nowrap; border-radius: 6px; font-size: 12px;
|
|
1376
|
+
font-weight: 600; border: none; cursor: pointer; height: 36px; }
|
|
1377
|
+
|
|
1378
|
+
/* ---- message queue (task #28) ---- */
|
|
1379
|
+
.queue { display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
|
|
1380
|
+
padding: 6px 12px 0; }
|
|
1381
|
+
.queue .queue-label { font-size: 10px; text-transform: uppercase; letter-spacing: .06em;
|
|
1382
|
+
color: var(--ink-muted); margin-right: 2px; }
|
|
1383
|
+
.queue .queue-chip { display: inline-flex; align-items: center; gap: 5px;
|
|
1384
|
+
background: var(--bg2, #f1efe7); border: 1px solid var(--border); border-radius: 12px;
|
|
1385
|
+
padding: 2px 9px; font-size: 12px; color: var(--ink); cursor: pointer; max-width: 240px;
|
|
1386
|
+
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1387
|
+
.queue .queue-chip:hover { border-color: var(--err, #c8472e); }
|
|
1388
|
+
.queue .queue-x { color: var(--ink-muted); font-weight: 700; }
|
|
1389
|
+
.queue .queue-chip:hover .queue-x { color: var(--err, #c8472e); }
|
|
1290
1390
|
|
|
1291
1391
|
/* ---- status row ---- */
|
|
1292
1392
|
.status { font-size: 11px; color: var(--ink-muted); padding: 4px 12px; text-align: center; flex-shrink: 0; }
|
|
@@ -1713,7 +1813,7 @@ ${renderYujinSplash()}
|
|
|
1713
1813
|
<div class="header">
|
|
1714
1814
|
<div class="brand">
|
|
1715
1815
|
<span class="k">${escapeHtml(tr.brandKanji)}</span>
|
|
1716
|
-
<div class="t"><span class="name">Yujin Forge</span><span class="proj" id="proj-mini" data-nac-id="yujin.panel.project-mini"></span></div>
|
|
1816
|
+
<div class="t"><span class="name">Yujin Forge</span><span class="proj proj-switch" id="proj-mini" data-proj-switch role="button" tabindex="0" title="${escapeHtml(tr.projectSwitchHint)}" data-nac-id="yujin.panel.project-mini"></span></div>
|
|
1717
1817
|
</div>
|
|
1718
1818
|
<div class="actions">
|
|
1719
1819
|
<button class="keys-btn" data-vault-open aria-label="${escapeHtml(tr.keysButton)}" title="${escapeHtml(tr.keysButton)}">${escapeHtml(tr.keysButton)}</button>
|
|
@@ -1723,12 +1823,14 @@ ${renderYujinSplash()}
|
|
|
1723
1823
|
</div>
|
|
1724
1824
|
<div id="stream-mini" data-nac-id="yujin.chat.stream-mini" class="stream"></div>
|
|
1725
1825
|
<div class="status" id="status-mini" data-nac-id="yujin.chat.status-mini"></div>
|
|
1826
|
+
<div id="queue-mini" class="queue" style="display:none"></div>
|
|
1726
1827
|
<form id="bar-mini" data-nac-id="yujin.chat.bar-mini" class="bar" onsubmit="return false">
|
|
1727
1828
|
<button type="button" class="mic" data-mic data-mic-btn aria-label="${escapeHtml(tr.micButton)}" title="${escapeHtml(tr.micButton)}">🎙</button>
|
|
1728
1829
|
<button type="button" class="tts-toggle" data-tts-toggle aria-label="${escapeHtml(tr.ttsButton)}" title="${escapeHtml(tr.ttsButton)}">🔊</button>
|
|
1729
1830
|
<button type="button" class="clip" data-attach-clip aria-label="Adjuntar archivo" title="Adjuntar archivo">📎</button>
|
|
1730
|
-
<input
|
|
1831
|
+
<textarea class="composer-input" rows="1" placeholder="${escapeHtml(tr.inputPlaceholder)}" aria-label="${escapeHtml(tr.inputPlaceholder)}"></textarea>
|
|
1731
1832
|
<button type="submit" class="send">${escapeHtml(tr.sendButton)}</button>
|
|
1833
|
+
<button type="button" class="stop" data-stop aria-label="${escapeHtml(tr.stopButton)}" title="${escapeHtml(tr.stopButton)}" style="display:none">■ ${escapeHtml(tr.stopButton)}</button>
|
|
1732
1834
|
</form>
|
|
1733
1835
|
<input type="file" id="attach-file-input" multiple style="display:none" data-nac-id="yujin.chat.attach-input">
|
|
1734
1836
|
</div>
|
|
@@ -1883,7 +1985,7 @@ ${renderYujinSplash()}
|
|
|
1883
1985
|
<div class="header">
|
|
1884
1986
|
<div class="brand">
|
|
1885
1987
|
<span class="k">${escapeHtml(tr.brandKanji)}</span>
|
|
1886
|
-
<div class="t"><span class="name">Yujin Forge</span><span class="proj" id="proj-full" data-nac-id="yujin.panel.project-full"></span></div>
|
|
1988
|
+
<div class="t"><span class="name">Yujin Forge</span><span class="proj proj-switch" id="proj-full" data-proj-switch role="button" tabindex="0" title="${escapeHtml(tr.projectSwitchHint)}" data-nac-id="yujin.panel.project-full"></span></div>
|
|
1887
1989
|
</div>
|
|
1888
1990
|
<div class="actions">
|
|
1889
1991
|
<button class="agents-btn"
|
|
@@ -2343,18 +2445,17 @@ ${renderYujinSplash()}
|
|
|
2343
2445
|
</div>
|
|
2344
2446
|
</div>
|
|
2345
2447
|
|
|
2346
|
-
<!-- Vincular telefono (dogfood 2026-06-10
|
|
2347
|
-
|
|
2448
|
+
<!-- Vincular telefono (dogfood 2026-06-10; Option 3 2026-06-16 --
|
|
2449
|
+
code-less: el QR es autocontenido, se escanea y listo). -->
|
|
2348
2450
|
<div class="set-section">
|
|
2349
2451
|
<div class="set-section-title">Vincular telefono</div>
|
|
2350
2452
|
<div class="set-row">
|
|
2351
|
-
<span class="set-lbl" style="opacity:0.7;font-size:11px;">Abri Forge Mobile, escanea este QR
|
|
2453
|
+
<span class="set-lbl" style="opacity:0.7;font-size:11px;">Abri Forge Mobile, toca "Vincular" y escanea este QR. Listo.</span>
|
|
2352
2454
|
<button type="button" class="set-btn" id="set-pair-generate">Generar QR</button>
|
|
2353
2455
|
</div>
|
|
2354
2456
|
<div id="set-pair-result" class="hidden" style="text-align:center;padding:8px 0;">
|
|
2355
|
-
<div id="set-pair-qr" style="display:inline-block;background:#
|
|
2356
|
-
<div
|
|
2357
|
-
<div id="set-pair-expiry" style="font-size:11px;opacity:0.6;margin-top:2px;"></div>
|
|
2457
|
+
<div id="set-pair-qr" style="display:inline-block;background:#ffffff;padding:10px;border-radius:8px;width:220px;height:220px;"></div>
|
|
2458
|
+
<div id="set-pair-expiry" style="font-size:11px;opacity:0.6;margin-top:6px;"></div>
|
|
2358
2459
|
</div>
|
|
2359
2460
|
<div id="set-pair-error" class="hidden" style="font-size:12px;color:var(--err,#c0392b);padding:6px 0;"></div>
|
|
2360
2461
|
</div>
|
|
@@ -2472,12 +2573,14 @@ ${renderYujinSplash()}
|
|
|
2472
2573
|
<div class="chat-col">
|
|
2473
2574
|
<div id="stream-full" data-nac-id="yujin.chat.stream-full" class="stream"></div>
|
|
2474
2575
|
<div class="status" id="status-full" data-nac-id="yujin.chat.status-full"></div>
|
|
2576
|
+
<div id="queue-full" class="queue" style="display:none"></div>
|
|
2475
2577
|
<form id="bar-full" data-nac-id="yujin.chat.bar-full" class="bar" onsubmit="return false">
|
|
2476
2578
|
<button type="button" class="mic" data-mic data-mic-btn aria-label="${escapeHtml(tr.micButton)}" title="${escapeHtml(tr.micButton)}">🎙</button>
|
|
2477
2579
|
<button type="button" class="tts-toggle" data-tts-toggle aria-label="${escapeHtml(tr.ttsButton)}" title="${escapeHtml(tr.ttsButton)}">🔊</button>
|
|
2478
2580
|
<button type="button" class="clip" data-attach-clip aria-label="Adjuntar archivo" title="Adjuntar archivo">📎</button>
|
|
2479
|
-
<input
|
|
2581
|
+
<textarea class="composer-input" rows="1" placeholder="${escapeHtml(tr.inputPlaceholder)}" aria-label="${escapeHtml(tr.inputPlaceholder)}"></textarea>
|
|
2480
2582
|
<button type="submit" class="send">${escapeHtml(tr.sendButton)}</button>
|
|
2583
|
+
<button type="button" class="stop" data-stop aria-label="${escapeHtml(tr.stopButton)}" title="${escapeHtml(tr.stopButton)}" style="display:none">■ ${escapeHtml(tr.stopButton)}</button>
|
|
2481
2584
|
</form>
|
|
2482
2585
|
</div>
|
|
2483
2586
|
<div class="pizarra" data-nac-id="yujin.pizarron" data-nac-role="region">
|
|
@@ -2596,14 +2699,114 @@ function showMode(mode) {
|
|
|
2596
2699
|
$('#yf-mini').classList.toggle('hidden', mode !== 'mini');
|
|
2597
2700
|
$('#yf-full').classList.toggle('hidden', mode !== 'full');
|
|
2598
2701
|
if (mode !== 'globito') {
|
|
2599
|
-
setTimeout(() => $('#bar-' + (mode === 'full' ? 'full' : 'mini') + '
|
|
2702
|
+
setTimeout(() => $('#bar-' + (mode === 'full' ? 'full' : 'mini') + ' textarea').focus(), 30);
|
|
2600
2703
|
}
|
|
2601
2704
|
}
|
|
2602
2705
|
|
|
2603
2706
|
function setProj() {
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2707
|
+
/* Project selector (task #33) -- render the name + a caret so it reads
|
|
2708
|
+
* as a dropdown (not a passive version stamp). The version moves to the
|
|
2709
|
+
* tooltip. */
|
|
2710
|
+
const label = CONFIG.projectName + ' ▾';
|
|
2711
|
+
const title = CONFIG.projectName + ' v' + CONFIG.version + ' — ' + (CONFIG.i18n.projectSwitchHint || '');
|
|
2712
|
+
for (const id of ['#proj-mini', '#proj-full']) {
|
|
2713
|
+
const el = $(id);
|
|
2714
|
+
if (!el) continue;
|
|
2715
|
+
el.textContent = label;
|
|
2716
|
+
el.setAttribute('title', title);
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
/* Project selector (task #33) -- the project label in the header is a
|
|
2721
|
+
* switcher. Clicking it lists every known project (~/.yujin-forge/
|
|
2722
|
+
* projects.json) and switching HOT-reanchors the live session: the
|
|
2723
|
+
* pizarron, graph + startup tabs follow immediately. */
|
|
2724
|
+
function closeProjectMenu() {
|
|
2725
|
+
const m = document.getElementById('proj-menu');
|
|
2726
|
+
if (m && m.parentNode) m.parentNode.removeChild(m);
|
|
2727
|
+
document.removeEventListener('click', onProjMenuOutside, true);
|
|
2728
|
+
}
|
|
2729
|
+
function onProjMenuOutside(e) {
|
|
2730
|
+
const m = document.getElementById('proj-menu');
|
|
2731
|
+
if (m && !m.contains(e.target) && !(e.target.closest && e.target.closest('[data-proj-switch]'))) {
|
|
2732
|
+
closeProjectMenu();
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
async function openProjectMenu(anchor) {
|
|
2736
|
+
if (document.getElementById('proj-menu')) { closeProjectMenu(); return; }
|
|
2737
|
+
let reg = { projects: [], active_slug: null };
|
|
2738
|
+
try {
|
|
2739
|
+
const r = await fetch('/api/forge/projects');
|
|
2740
|
+
const j = await r.json();
|
|
2741
|
+
if (j && j.ok && j.registry) reg = j.registry;
|
|
2742
|
+
else if (j && Array.isArray(j.projects)) reg = j;
|
|
2743
|
+
} catch (e) { /* show just the add option */ }
|
|
2744
|
+
const menu = document.createElement('div');
|
|
2745
|
+
menu.id = 'proj-menu';
|
|
2746
|
+
menu.className = 'proj-menu';
|
|
2747
|
+
const rows = (reg.projects || []).slice().sort(function (a, b) {
|
|
2748
|
+
return String(b.last_active_at || '').localeCompare(String(a.last_active_at || ''));
|
|
2749
|
+
}).map(function (p) {
|
|
2750
|
+
const active = p.slug === reg.active_slug;
|
|
2751
|
+
return '<div class="proj-row' + (active ? ' on' : '') + '" data-proj-slug="' + escapeHtml(p.slug) + '" role="button" tabindex="0">'
|
|
2752
|
+
+ '<span class="proj-row-name">' + (active ? '● ' : '') + escapeHtml(p.slug) + '</span>'
|
|
2753
|
+
+ '<span class="proj-row-path">' + escapeHtml(String(p.path || '')) + '</span>'
|
|
2754
|
+
+ '</div>';
|
|
2755
|
+
}).join('');
|
|
2756
|
+
menu.innerHTML = '<div class="proj-menu-title">' + escapeHtml(CONFIG.i18n.projectTitle) + '</div>'
|
|
2757
|
+
+ (rows || '')
|
|
2758
|
+
+ '<div class="proj-row proj-add" data-proj-add role="button" tabindex="0">' + escapeHtml(CONFIG.i18n.projectAdd) + '</div>';
|
|
2759
|
+
document.body.appendChild(menu);
|
|
2760
|
+
/* position under the anchor */
|
|
2761
|
+
try {
|
|
2762
|
+
const rect = anchor.getBoundingClientRect();
|
|
2763
|
+
menu.style.top = (rect.bottom + 4) + 'px';
|
|
2764
|
+
menu.style.left = Math.max(8, rect.left) + 'px';
|
|
2765
|
+
} catch (e) { /* default top-left */ }
|
|
2766
|
+
menu.querySelectorAll('[data-proj-slug]').forEach(function (row) {
|
|
2767
|
+
row.addEventListener('click', function () { switchProject(row.getAttribute('data-proj-slug')); });
|
|
2768
|
+
});
|
|
2769
|
+
const addRow = menu.querySelector('[data-proj-add]');
|
|
2770
|
+
if (addRow) addRow.addEventListener('click', addProjectViaPrompt);
|
|
2771
|
+
setTimeout(function () { document.addEventListener('click', onProjMenuOutside, true); }, 0);
|
|
2772
|
+
}
|
|
2773
|
+
async function switchProject(slug) {
|
|
2774
|
+
if (!slug) return;
|
|
2775
|
+
closeProjectMenu();
|
|
2776
|
+
setStatus(CONFIG.i18n.projectSwitching);
|
|
2777
|
+
try {
|
|
2778
|
+
const r = await fetch('/api/forge/projects/active', {
|
|
2779
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
2780
|
+
body: JSON.stringify({ slug: slug }),
|
|
2781
|
+
});
|
|
2782
|
+
const j = await r.json();
|
|
2783
|
+
if (j && j.ok) {
|
|
2784
|
+
/* optimistic label update; the system.reanchor event reloads the
|
|
2785
|
+
* startup tabs + the graph follows server-side. */
|
|
2786
|
+
CONFIG.projectName = slug;
|
|
2787
|
+
setProj();
|
|
2788
|
+
setStatus('');
|
|
2789
|
+
} else {
|
|
2790
|
+
setStatus((j && j.error) || 'No se pudo cambiar de proyecto.', true);
|
|
2791
|
+
}
|
|
2792
|
+
} catch (e) {
|
|
2793
|
+
setStatus('No se pudo cambiar de proyecto.', true);
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
async function addProjectViaPrompt() {
|
|
2797
|
+
closeProjectMenu();
|
|
2798
|
+
const path = prompt(CONFIG.i18n.projectAddPrompt, '');
|
|
2799
|
+
if (!path || !path.trim()) return;
|
|
2800
|
+
try {
|
|
2801
|
+
const r = await fetch('/api/forge/projects/add', {
|
|
2802
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
2803
|
+
body: JSON.stringify({ path: path.trim() }),
|
|
2804
|
+
});
|
|
2805
|
+
const j = await r.json();
|
|
2806
|
+
if (!j || !j.ok) { setStatus((j && j.error) || 'No se pudo anadir el proyecto.', true); return; }
|
|
2807
|
+
/* reopen the menu so the user can pick the freshly added project */
|
|
2808
|
+
openProjectMenu($('#proj-full') || $('#proj-mini'));
|
|
2809
|
+
} catch (e) { setStatus('No se pudo anadir el proyecto.', true); }
|
|
2607
2810
|
}
|
|
2608
2811
|
|
|
2609
2812
|
function escapeHtml(s) {
|
|
@@ -3524,10 +3727,8 @@ async function pairGenerate() {
|
|
|
3524
3727
|
const j = await r.json();
|
|
3525
3728
|
if (j && j.ok) {
|
|
3526
3729
|
const qr = document.getElementById('set-pair-qr');
|
|
3527
|
-
const code = document.getElementById('set-pair-code');
|
|
3528
3730
|
const exp = document.getElementById('set-pair-expiry');
|
|
3529
3731
|
if (qr) qr.innerHTML = j.qr_svg || '';
|
|
3530
|
-
if (code) code.textContent = j.code || '';
|
|
3531
3732
|
if (exp && j.expires_at) {
|
|
3532
3733
|
const mins = Math.max(0, Math.round((new Date(j.expires_at).getTime() - Date.now()) / 60000));
|
|
3533
3734
|
exp.textContent = 'Expira en ~' + mins + ' min. Generalo de nuevo si caduca.';
|
|
@@ -4677,8 +4878,23 @@ function renderPizarronCardBody(it) {
|
|
|
4677
4878
|
* estetica sumi-e. El modelo manda markdown (seguro: no se
|
|
4678
4879
|
* ejecuta HTML/JS), el panel le da el look de documento. */
|
|
4679
4880
|
if (it.kind === 'doc') {
|
|
4881
|
+
/* Default de documentos = HTML (pedido de Pablo). El brain genera
|
|
4882
|
+
* HTML por defecto; se renderiza sanitizado DENTRO de la hoja Yujin.
|
|
4883
|
+
* Backward-compat: si el content es markdown (no empieza con un tag
|
|
4884
|
+
* de bloque), cae a renderMdRich, asi los docs viejos siguen bien. */
|
|
4885
|
+
const dc = String(it.content || '');
|
|
4886
|
+
/* Pablo 2026-06-15 -- el heuristico viejo solo miraba si EMPEZABA
|
|
4887
|
+
* con un tag de bloque, asi que un doc HTML con un \n o una linea
|
|
4888
|
+
* de texto antes del primer tag caia a markdown y se veia "feo"
|
|
4889
|
+
* (tags como texto). Ahora: es HTML si empieza con un tag de bloque
|
|
4890
|
+
* O si contiene un tag de CIERRE de bloque en cualquier parte (senal
|
|
4891
|
+
* fuerte que el markdown no tiene). */
|
|
4892
|
+
const looksHtml =
|
|
4893
|
+
/^\\s*<(?:!doctype|html|head|body|div|section|article|main|header|h[1-6]|p|ul|ol|table|figure|blockquote|pre)\\b/i.test(dc)
|
|
4894
|
+
|| /<\\/(?:h[1-6]|div|section|article|main|header|body|html|p|ul|ol|li|table|tr|td|figure|blockquote|pre)\\s*>/i.test(dc);
|
|
4895
|
+
const inner = looksHtml ? sanitizeHtml(dc) : renderMdRich(dc);
|
|
4680
4896
|
return '<div class="pz-body pz-doc-wrap" data-nac-id="yujin.pizarron.item.' + it.id + '.body" data-nac-role="region" data-nac-action="scroll" tabindex="0">'
|
|
4681
|
-
+ '<article class="pz-doc-page pz-sumi">' +
|
|
4897
|
+
+ '<article class="pz-doc-page pz-sumi">' + inner + '</article>'
|
|
4682
4898
|
+ '</div>';
|
|
4683
4899
|
}
|
|
4684
4900
|
/* Lienzo Coding (dogfood 2026-06-10) -- mientras el brain codea
|
|
@@ -4697,6 +4913,17 @@ function renderPizarronCardBody(it) {
|
|
|
4697
4913
|
: '<div class="pz-coding-detail" data-pz-coding-detail>' + renderCodingDetail() + '</div>')
|
|
4698
4914
|
+ '</div>';
|
|
4699
4915
|
}
|
|
4916
|
+
/* PND-007 -- Tareas: tareas de fondo que sobreviven al turno. Se
|
|
4917
|
+
* llena en vivo en initTasksTab() (poll a /api/forge/task). */
|
|
4918
|
+
if (it.kind === 'tasks') {
|
|
4919
|
+
return '<div class="pz-body pz-tasks-wrap pz-sumi" data-nac-id="yujin.pizarron.item.' + it.id + '.body" data-nac-role="region" tabindex="0">'
|
|
4920
|
+
+ '<div class="pz-tasks-new">'
|
|
4921
|
+
+ '<input type="text" class="pz-tasks-input" data-pz-tasks-cmd placeholder="comando (ej: pnpm install)..." />'
|
|
4922
|
+
+ '<button type="button" class="pz-tasks-run" data-pz-tasks-run>Lanzar</button>'
|
|
4923
|
+
+ '</div>'
|
|
4924
|
+
+ '<div class="pz-tasks-list" data-pz-tasks-list><div class="pz-tasks-empty">cargando...</div></div>'
|
|
4925
|
+
+ '</div>';
|
|
4926
|
+
}
|
|
4700
4927
|
/* PND-094 -- vista de workflow: fases con nombre (nivel 1) + sus
|
|
4701
4928
|
* tareas debajo (nivel 2), con avance por tarea y por fase. */
|
|
4702
4929
|
if (it.kind === 'workflow') {
|
|
@@ -4728,9 +4955,15 @@ function renderPizarronCardBody(it) {
|
|
|
4728
4955
|
}
|
|
4729
4956
|
if (it.kind === 'bpmn' || it.kind === 'flow') {
|
|
4730
4957
|
const trimmed = it.content.trim();
|
|
4958
|
+
/* SVG crudo -> passthrough (compat). */
|
|
4731
4959
|
if (trimmed.startsWith('<svg')) {
|
|
4732
4960
|
return bodyOpen + sanitizeHtml(trimmed) + '</div>';
|
|
4733
4961
|
}
|
|
4962
|
+
/* Modelo JSON {nodes,flows,lanes?} -> renderer BPMN 2.0 propio. */
|
|
4963
|
+
if (trimmed.charAt(0) === '{') {
|
|
4964
|
+
return '<div class="pz-body pz-bpmn-wrap" data-nac-id="yujin.pizarron.item.' + it.id + '.body" data-nac-role="region" data-nac-action="scroll" tabindex="0">'
|
|
4965
|
+
+ renderBpmnView(trimmed) + '</div>';
|
|
4966
|
+
}
|
|
4734
4967
|
return bodyOpen + '<pre class="pz-code"><code>' + escapeHtml(it.content) + '</code></pre></div>';
|
|
4735
4968
|
}
|
|
4736
4969
|
return bodyOpen + '<pre><code>' + escapeHtml(it.content) + '</code></pre></div>';
|
|
@@ -4821,6 +5054,188 @@ function renderPizarron() {
|
|
|
4821
5054
|
if (it.kind === 'coding') {
|
|
4822
5055
|
try { initCodingTab(it); } catch (e) { /* render best-effort */ }
|
|
4823
5056
|
}
|
|
5057
|
+
if (it.kind === 'tasks') {
|
|
5058
|
+
try { initTasksTab(it); } catch (e) { /* render best-effort */ }
|
|
5059
|
+
}
|
|
5060
|
+
if (it.kind === 'workflow') {
|
|
5061
|
+
try { initWorkflowTab(it); } catch (e) { /* render best-effort */ }
|
|
5062
|
+
}
|
|
5063
|
+
}
|
|
5064
|
+
|
|
5065
|
+
/* Selector multi-workflow: persiste el indice elegido por tab id (igual
|
|
5066
|
+
* que el filtro del grafo) para que un refresh del board no resetee la
|
|
5067
|
+
* seleccion. Con un solo workflow no hay barra de chips: no-op. */
|
|
5068
|
+
var _wflSelByTab = {};
|
|
5069
|
+
function initWorkflowTab(it) {
|
|
5070
|
+
var body = document.querySelector('[data-nac-id="yujin.pizarron.item.' + it.id + '.body"]');
|
|
5071
|
+
if (!body) return;
|
|
5072
|
+
var root = body.querySelector('[data-pz-wfl-root]');
|
|
5073
|
+
if (!root) return;
|
|
5074
|
+
var chips = root.querySelectorAll('[data-pz-wfl-chip]');
|
|
5075
|
+
var cards = root.querySelectorAll('[data-pz-wfl-card]');
|
|
5076
|
+
if (chips.length <= 1) return; /* un solo workflow: sin selector */
|
|
5077
|
+
var show = function (idx) {
|
|
5078
|
+
for (var i = 0; i < cards.length; i++) {
|
|
5079
|
+
cards[i].style.display = (String(cards[i].getAttribute('data-wfl-idx')) === String(idx)) ? '' : 'none';
|
|
5080
|
+
}
|
|
5081
|
+
for (var j = 0; j < chips.length; j++) {
|
|
5082
|
+
var on = String(chips[j].getAttribute('data-wfl-idx')) === String(idx);
|
|
5083
|
+
if (on) chips[j].classList.add('active'); else chips[j].classList.remove('active');
|
|
5084
|
+
}
|
|
5085
|
+
_wflSelByTab[it.id] = String(idx);
|
|
5086
|
+
};
|
|
5087
|
+
for (var k = 0; k < chips.length; k++) {
|
|
5088
|
+
chips[k].addEventListener('click', function () { show(this.getAttribute('data-wfl-idx')); });
|
|
5089
|
+
}
|
|
5090
|
+
var saved = _wflSelByTab[it.id];
|
|
5091
|
+
show(saved != null ? saved : '0');
|
|
5092
|
+
}
|
|
5093
|
+
|
|
5094
|
+
/* ====================================================================
|
|
5095
|
+
* PND-007 -- Tareas tab. Polls /api/forge/task; renders survivable
|
|
5096
|
+
* background tasks with a per-type icon, status, a log peek + kill, and
|
|
5097
|
+
* a small launcher. Each task TYPE (install/build/server/...) gets its
|
|
5098
|
+
* own sumi-e glyph.
|
|
5099
|
+
* ================================================================== */
|
|
5100
|
+
var yfTasksTimer = null;
|
|
5101
|
+
|
|
5102
|
+
function taskTypeIcon(type) {
|
|
5103
|
+
switch (String(type || 'other')) {
|
|
5104
|
+
case 'install': return { g: '↓', c: '#4f5b87' }; /* down arrow */
|
|
5105
|
+
case 'build': return { g: '⚒', c: '#c8a04d' }; /* hammer+pick */
|
|
5106
|
+
case 'server': return { g: '◉', c: '#3fa85e' }; /* fisheye */
|
|
5107
|
+
case 'test': return { g: '✓', c: '#6f8b5e' }; /* check */
|
|
5108
|
+
case 'deploy': return { g: '☁', c: '#8a7bb0' }; /* cloud */
|
|
5109
|
+
case 'git': return { g: '⎇', c: '#b07b4f' }; /* alternation */
|
|
5110
|
+
case 'script': return { g: '›', c: '#7a8aa0' }; /* chevron */
|
|
5111
|
+
default: return { g: '●', c: '#9a9a9a' }; /* disc */
|
|
5112
|
+
}
|
|
5113
|
+
}
|
|
5114
|
+
|
|
5115
|
+
function taskStatusBadge(s) {
|
|
5116
|
+
if (s === 'running') return { t: 'corriendo', c: '#c8a04d' };
|
|
5117
|
+
if (s === 'done') return { t: 'terminada', c: '#6f8b5e' };
|
|
5118
|
+
if (s === 'failed') return { t: 'fallo', c: '#c8472e' };
|
|
5119
|
+
if (s === 'killed') return { t: 'detenida', c: '#9a9a9a' };
|
|
5120
|
+
if (s === 'exited') return { t: 'salio', c: '#9a9a9a' };
|
|
5121
|
+
return { t: String(s || ''), c: '#9a9a9a' };
|
|
5122
|
+
}
|
|
5123
|
+
|
|
5124
|
+
function renderTasksList(tasks) {
|
|
5125
|
+
if (!tasks || tasks.length === 0) {
|
|
5126
|
+
return '<div class="pz-tasks-empty">Sin tareas de fondo. Lo que lances aca sobrevive al turno.</div>';
|
|
5127
|
+
}
|
|
5128
|
+
var html = '';
|
|
5129
|
+
for (var i = 0; i < tasks.length; i++) {
|
|
5130
|
+
var t = tasks[i];
|
|
5131
|
+
var ic = taskTypeIcon(t.type);
|
|
5132
|
+
var sb = taskStatusBadge(t.status);
|
|
5133
|
+
var cmdline = escapeHtml(String(t.cmd || '') + (t.args && t.args.length ? ' ' + t.args.join(' ') : '')).slice(0, 200);
|
|
5134
|
+
html += '<div class="pz-task" data-pz-task="' + escapeHtml(t.id) + '">'
|
|
5135
|
+
+ '<div class="pz-task-ic" style="color:' + ic.c + '">' + ic.g + '</div>'
|
|
5136
|
+
+ '<div class="pz-task-main">'
|
|
5137
|
+
+ '<div class="pz-task-label">' + escapeHtml(String(t.label || t.cmd || 'tarea')) + '</div>'
|
|
5138
|
+
+ '<div class="pz-task-cmd">' + cmdline + '</div>'
|
|
5139
|
+
+ '</div>'
|
|
5140
|
+
+ '<div class="pz-task-side">'
|
|
5141
|
+
+ '<span class="pz-task-badge" style="color:' + sb.c + ';border-color:' + sb.c + '">' + sb.t + '</span>'
|
|
5142
|
+
+ (t.status === 'running'
|
|
5143
|
+
? '<button type="button" class="pz-task-kill" data-pz-task-kill="' + escapeHtml(t.id) + '">stop</button>'
|
|
5144
|
+
: '<button type="button" class="pz-task-log" data-pz-task-log="' + escapeHtml(t.id) + '">log</button>')
|
|
5145
|
+
+ '<button type="button" class="pz-task-del" data-pz-task-del="' + escapeHtml(t.id) + '" title="Eliminar tarea" aria-label="Eliminar tarea">×</button>'
|
|
5146
|
+
+ '</div>'
|
|
5147
|
+
+ '</div>'
|
|
5148
|
+
+ '<div class="pz-task-logbox" data-pz-task-logbox="' + escapeHtml(t.id) + '"></div>';
|
|
5149
|
+
}
|
|
5150
|
+
return html;
|
|
5151
|
+
}
|
|
5152
|
+
|
|
5153
|
+
async function refreshTasksTab() {
|
|
5154
|
+
var list = document.querySelector('[data-pz-tasks-list]');
|
|
5155
|
+
if (!list) return; /* tab closed -> stop touching the DOM */
|
|
5156
|
+
try {
|
|
5157
|
+
var r = await fetch('/api/forge/task');
|
|
5158
|
+
var data = await r.json();
|
|
5159
|
+
if (data && data.ok) list.innerHTML = renderTasksList(data.tasks || []);
|
|
5160
|
+
} catch (e) { /* best-effort */ }
|
|
5161
|
+
}
|
|
5162
|
+
|
|
5163
|
+
async function tasksTabLaunch() {
|
|
5164
|
+
var inp = document.querySelector('[data-pz-tasks-cmd]');
|
|
5165
|
+
if (!inp) return;
|
|
5166
|
+
var raw = String(inp.value || '').trim();
|
|
5167
|
+
if (!raw) return;
|
|
5168
|
+
/* split a simple "cmd arg arg" string (no shell parsing).
|
|
5169
|
+
* NOTE: this code rides inside the panel's outer template literal, so
|
|
5170
|
+
* a regex backslash must be DOUBLED -- /\s+/ would be mangled to /s+/
|
|
5171
|
+
* (split on the letter 's': "pnpm install" -> "pnpm in"+"tall"). */
|
|
5172
|
+
var parts = raw.split(/\\s+/);
|
|
5173
|
+
var cmd = parts.shift();
|
|
5174
|
+
try {
|
|
5175
|
+
await fetch('/api/forge/task', {
|
|
5176
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
5177
|
+
body: JSON.stringify({ cmd: cmd, args: parts, label: raw.slice(0, 60) }),
|
|
5178
|
+
});
|
|
5179
|
+
inp.value = '';
|
|
5180
|
+
await refreshTasksTab();
|
|
5181
|
+
} catch (e) { /* best-effort */ }
|
|
5182
|
+
}
|
|
5183
|
+
|
|
5184
|
+
async function tasksTabShowLog(id) {
|
|
5185
|
+
var box = document.querySelector('[data-pz-task-logbox="' + id + '"]');
|
|
5186
|
+
if (!box) return;
|
|
5187
|
+
if (box.getAttribute('data-open') === '1') { box.innerHTML = ''; box.removeAttribute('data-open'); return; }
|
|
5188
|
+
try {
|
|
5189
|
+
var r = await fetch('/api/forge/task/log?id=' + encodeURIComponent(id) + '&lines=40');
|
|
5190
|
+
var data = await r.json();
|
|
5191
|
+
box.innerHTML = '<pre class="pz-task-logpre">' + escapeHtml(String((data && data.tail) || '(sin salida)')) + '</pre>';
|
|
5192
|
+
box.setAttribute('data-open', '1');
|
|
5193
|
+
} catch (e) { /* best-effort */ }
|
|
5194
|
+
}
|
|
5195
|
+
|
|
5196
|
+
async function tasksTabKill(id) {
|
|
5197
|
+
try {
|
|
5198
|
+
await fetch('/api/forge/task/kill', {
|
|
5199
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
5200
|
+
body: JSON.stringify({ id: id }),
|
|
5201
|
+
});
|
|
5202
|
+
await refreshTasksTab();
|
|
5203
|
+
} catch (e) { /* best-effort */ }
|
|
5204
|
+
}
|
|
5205
|
+
|
|
5206
|
+
async function tasksTabDelete(id) {
|
|
5207
|
+
/* Remove the task from the list (kills it first if still running). */
|
|
5208
|
+
try {
|
|
5209
|
+
await fetch('/api/forge/task/delete', {
|
|
5210
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
5211
|
+
body: JSON.stringify({ id: id }),
|
|
5212
|
+
});
|
|
5213
|
+
await refreshTasksTab();
|
|
5214
|
+
} catch (e) { /* best-effort */ }
|
|
5215
|
+
}
|
|
5216
|
+
|
|
5217
|
+
function initTasksTab(it) {
|
|
5218
|
+
var wrap = document.querySelector('.pz-tasks-wrap');
|
|
5219
|
+
if (!wrap) return;
|
|
5220
|
+
var runBtn = wrap.querySelector('[data-pz-tasks-run]');
|
|
5221
|
+
if (runBtn) runBtn.addEventListener('click', function () { tasksTabLaunch().catch(function () {}); });
|
|
5222
|
+
var inp = wrap.querySelector('[data-pz-tasks-cmd]');
|
|
5223
|
+
if (inp) inp.addEventListener('keydown', function (e) { if (e.key === 'Enter') tasksTabLaunch().catch(function () {}); });
|
|
5224
|
+
/* delegated clicks for per-task log/kill */
|
|
5225
|
+
wrap.addEventListener('click', function (e) {
|
|
5226
|
+
var tgt = e.target;
|
|
5227
|
+
if (!tgt || !tgt.getAttribute) return;
|
|
5228
|
+
var killId = tgt.getAttribute('data-pz-task-kill');
|
|
5229
|
+
if (killId) { tasksTabKill(killId).catch(function () {}); return; }
|
|
5230
|
+
var delId = tgt.getAttribute('data-pz-task-del');
|
|
5231
|
+
if (delId) { tasksTabDelete(delId).catch(function () {}); return; }
|
|
5232
|
+
var logId = tgt.getAttribute('data-pz-task-log');
|
|
5233
|
+
if (logId) { tasksTabShowLog(logId).catch(function () {}); return; }
|
|
5234
|
+
});
|
|
5235
|
+
refreshTasksTab().catch(function () {});
|
|
5236
|
+
if (!yfTasksTimer) {
|
|
5237
|
+
yfTasksTimer = setInterval(function () { refreshTasksTab().catch(function () {}); }, 4000);
|
|
5238
|
+
}
|
|
4824
5239
|
}
|
|
4825
5240
|
|
|
4826
5241
|
/* ====================================================================
|
|
@@ -4842,6 +5257,68 @@ function renderPizarron() {
|
|
|
4842
5257
|
function renderWorkflowView(content) {
|
|
4843
5258
|
let v;
|
|
4844
5259
|
try { v = JSON.parse(content || '{}'); } catch (e) { v = null; }
|
|
5260
|
+
/* PND-094 ext -- multi-workflow: content { workflows: [card...] }. Each
|
|
5261
|
+
* card = one coexisting WorkflowInstance with its stages -> ordered
|
|
5262
|
+
* task pendings. Falls through to the legacy single-workflow render
|
|
5263
|
+
* below when content is the old { stages, ... } shape. */
|
|
5264
|
+
if (v && Array.isArray(v.workflows)) {
|
|
5265
|
+
if (v.workflows.length === 0) return '<div class="pz-wfl-empty">Sin workflows abiertos.</div>';
|
|
5266
|
+
var kindLbl = { greenfield: 'App nueva', corrective: 'Correctivo', evolutive: 'Evolutivo' };
|
|
5267
|
+
var ic2 = function (closed) { return closed ? '✓' : '○'; };
|
|
5268
|
+
var multi = v.workflows.length > 1;
|
|
5269
|
+
/* Selector entre los workflows abiertos: con >1 se muestra una barra
|
|
5270
|
+
* de chips (uno por workflow) y SOLO se ve el seleccionado. El estado
|
|
5271
|
+
* lo persiste initWorkflowTab por tab (sobrevive a los refreshes). */
|
|
5272
|
+
var sel = '';
|
|
5273
|
+
if (multi) {
|
|
5274
|
+
sel = '<div class="pz-wfl-sel" data-pz-wfl-sel>';
|
|
5275
|
+
for (var ci = 0; ci < v.workflows.length; ci++) {
|
|
5276
|
+
var cw = v.workflows[ci];
|
|
5277
|
+
sel += '<button type="button" class="pz-wfl-chip' + (ci === 0 ? ' active' : '') + '"'
|
|
5278
|
+
+ ' data-pz-wfl-chip data-wfl-idx="' + ci + '">'
|
|
5279
|
+
+ escapeHtml(cw.code) + ' · ' + escapeHtml(cw.name || '') + '</button>';
|
|
5280
|
+
}
|
|
5281
|
+
sel += '</div>';
|
|
5282
|
+
}
|
|
5283
|
+
var out = '<div data-pz-wfl-root>' + sel;
|
|
5284
|
+
for (var wi = 0; wi < v.workflows.length; wi++) {
|
|
5285
|
+
var wf = v.workflows[wi];
|
|
5286
|
+
var wpct = wf.total > 0 ? Math.round((wf.done / wf.total) * 100) : 0;
|
|
5287
|
+
out += '<div class="pz-wfl-card" data-pz-wfl-card data-wfl-idx="' + wi + '"'
|
|
5288
|
+
+ (multi && wi !== 0 ? ' style="display:none"' : '') + '>'
|
|
5289
|
+
+ '<div class="pz-wfl-head">'
|
|
5290
|
+
+ '<div class="pz-wfl-title">' + escapeHtml(wf.code + ' -- ' + (wf.name || '')) + '</div>'
|
|
5291
|
+
+ '<div class="pz-wfl-kind">' + escapeHtml(kindLbl[wf.kind] || wf.kind || '') + (wf.status === 'closed' ? ' (cerrado)' : '') + '</div>'
|
|
5292
|
+
+ '<div class="pz-wfl-overall">' + (wf.done || 0) + '/' + (wf.total || 0) + ' tareas · ' + wpct + '%</div>'
|
|
5293
|
+
+ '<div class="pz-wfl-bar"><div class="pz-wfl-fill" style="width:' + wpct + '%"></div></div>'
|
|
5294
|
+
+ '</div>';
|
|
5295
|
+
out += '<div class="pz-wfl-flow">';
|
|
5296
|
+
for (var si2 = 0; si2 < wf.stages.length; si2++) {
|
|
5297
|
+
var st2 = wf.stages[si2];
|
|
5298
|
+
var tasks2 = st2.tasks || [];
|
|
5299
|
+
var sdone = 0; for (var ti = 0; ti < tasks2.length; ti++) if (tasks2[ti].closed) sdone++;
|
|
5300
|
+
var sst = tasks2.length === 0 ? 'pending' : (sdone === tasks2.length ? 'done' : (sdone > 0 ? 'active' : 'pending'));
|
|
5301
|
+
var last2 = si2 === wf.stages.length - 1;
|
|
5302
|
+
out += '<div class="pz-wfl-col">'
|
|
5303
|
+
+ '<div class="pz-wfl-phase wfl-' + sst + (last2 ? ' wfl-last' : '') + '">'
|
|
5304
|
+
+ '<div class="pz-wfl-phase-title">' + escapeHtml(st2.title || st2.id || '') + '</div>'
|
|
5305
|
+
+ '<div class="pz-wfl-phase-count">' + sdone + '/' + tasks2.length + '</div>'
|
|
5306
|
+
+ '</div><div class="pz-wfl-hang">';
|
|
5307
|
+
for (var k2 = 0; k2 < tasks2.length; k2++) {
|
|
5308
|
+
var tk = tasks2[k2];
|
|
5309
|
+
out += '<div class="pz-wfl-task wfl-' + (tk.closed ? 'done' : 'pending') + '" title="' + escapeHtml(tk.id) + '">'
|
|
5310
|
+
+ '<span class="pz-wfl-check">' + ic2(tk.closed) + '</span>'
|
|
5311
|
+
+ '<span class="pz-wfl-task-title">' + escapeHtml((tk.id ? tk.id + ' ' : '') + (tk.title || '')) + (tk.ad_hoc ? ' <span class="pz-wfl-adhoc">+</span>' : '') + '</span>'
|
|
5312
|
+
+ '</div>';
|
|
5313
|
+
}
|
|
5314
|
+
if (tasks2.length === 0) out += '<div class="pz-wfl-task wfl-pending pz-wfl-empty-stage">sin tareas</div>';
|
|
5315
|
+
out += '</div></div>';
|
|
5316
|
+
}
|
|
5317
|
+
out += '</div></div>';
|
|
5318
|
+
}
|
|
5319
|
+
out += '</div>';
|
|
5320
|
+
return out;
|
|
5321
|
+
}
|
|
4845
5322
|
if (!v || !Array.isArray(v.stages)) {
|
|
4846
5323
|
return '<div class="pz-wfl-empty">Sin estado de workflow todavia.</div>';
|
|
4847
5324
|
}
|
|
@@ -4882,6 +5359,161 @@ function renderWorkflowView(content) {
|
|
|
4882
5359
|
return h;
|
|
4883
5360
|
}
|
|
4884
5361
|
|
|
5362
|
+
/* Renderer BPMN 2.0 (pestana canonica de flujos). El content es un
|
|
5363
|
+
* modelo JSON { lanes?:[{id,label}], nodes:[{id,type,label,lane?,
|
|
5364
|
+
* gateway?}], flows:[{from,to,label?}] }. Layout en capas izq->der
|
|
5365
|
+
* (longest-path), shapes BPMN (eventos=circulos, tareas=rect
|
|
5366
|
+
* redondeado, gateways=rombo con simbolo), flujos ortogonales con
|
|
5367
|
+
* flecha + label, swimlanes opcionales. Estatico, estetica sumi-e. */
|
|
5368
|
+
function bpmnWrap(s, maxChars, maxLines) {
|
|
5369
|
+
var words = String(s == null ? '' : s).split(/\s+/);
|
|
5370
|
+
var lines = []; var cur = '';
|
|
5371
|
+
for (var i = 0; i < words.length; i++) {
|
|
5372
|
+
var w = words[i];
|
|
5373
|
+
if (!cur) { cur = w; }
|
|
5374
|
+
else if ((cur + ' ' + w).length <= maxChars) { cur += ' ' + w; }
|
|
5375
|
+
else { lines.push(cur); cur = w; if (lines.length === maxLines - 1) break; }
|
|
5376
|
+
}
|
|
5377
|
+
if (cur && lines.length < maxLines) lines.push(cur);
|
|
5378
|
+
if (lines.length === maxLines && (cur || words.length > 0)) {
|
|
5379
|
+
var last = lines[maxLines - 1];
|
|
5380
|
+
if (last && last.length > maxChars) lines[maxLines - 1] = last.slice(0, maxChars - 1) + '…';
|
|
5381
|
+
}
|
|
5382
|
+
return lines;
|
|
5383
|
+
}
|
|
5384
|
+
|
|
5385
|
+
function renderBpmnView(content) {
|
|
5386
|
+
var m;
|
|
5387
|
+
try { m = JSON.parse(content); } catch (e) {
|
|
5388
|
+
return '<div class="pz-bpmn-empty">El flujo BPMN espera JSON { nodes, flows, lanes? }. No pude parsear el content.</div>';
|
|
5389
|
+
}
|
|
5390
|
+
if (!m || !Array.isArray(m.nodes) || m.nodes.length === 0) {
|
|
5391
|
+
return '<div class="pz-bpmn-empty">Sin nodos en el flujo.</div>';
|
|
5392
|
+
}
|
|
5393
|
+
var nodes = m.nodes.filter(function (n) { return n && n.id; });
|
|
5394
|
+
var flows = Array.isArray(m.flows) ? m.flows.filter(function (f) { return f && f.from && f.to; }) : [];
|
|
5395
|
+
var lanes = Array.isArray(m.lanes) ? m.lanes.filter(function (l) { return l && l.id; }) : [];
|
|
5396
|
+
var byId = {}; nodes.forEach(function (n) { byId[n.id] = n; });
|
|
5397
|
+
/* capas: longest-path desde las fuentes (DAG; cap por #nodos si hay ciclos). */
|
|
5398
|
+
var layer = {}; nodes.forEach(function (n) { layer[n.id] = 0; });
|
|
5399
|
+
for (var it = 0; it < nodes.length; it++) {
|
|
5400
|
+
var changed = false;
|
|
5401
|
+
for (var k = 0; k < flows.length; k++) {
|
|
5402
|
+
var f = flows[k];
|
|
5403
|
+
if (byId[f.from] && byId[f.to] && layer[f.to] < layer[f.from] + 1) { layer[f.to] = layer[f.from] + 1; changed = true; }
|
|
5404
|
+
}
|
|
5405
|
+
if (!changed) break;
|
|
5406
|
+
}
|
|
5407
|
+
var maxCol = 0; nodes.forEach(function (n) { if (layer[n.id] > maxCol) maxCol = layer[n.id]; });
|
|
5408
|
+
var hasLanes = lanes.length > 0;
|
|
5409
|
+
var laneIdx = {}; lanes.forEach(function (l, i) { laneIdx[l.id] = i; });
|
|
5410
|
+
/* dimensiones */
|
|
5411
|
+
var COL_W = 178, LANE_H = 116, ROW_H = 96, PAD = 30, LANE_LBL = hasLanes ? 30 : 0, TOPPAD = hasLanes ? 8 : 26;
|
|
5412
|
+
/* posiciones (centro de cada nodo) */
|
|
5413
|
+
var pos = {};
|
|
5414
|
+
if (hasLanes) {
|
|
5415
|
+
var subCount = {}; /* nodos por (lane,col) para apilar */
|
|
5416
|
+
nodes.forEach(function (n) {
|
|
5417
|
+
var li = (n.lane && laneIdx[n.lane] != null) ? laneIdx[n.lane] : 0;
|
|
5418
|
+
var key = li + ':' + layer[n.id];
|
|
5419
|
+
var sub = subCount[key] || 0; subCount[key] = sub + 1;
|
|
5420
|
+
pos[n.id] = { li: li, col: layer[n.id], sub: sub };
|
|
5421
|
+
});
|
|
5422
|
+
nodes.forEach(function (n) {
|
|
5423
|
+
var p = pos[n.id]; var key = p.li + ':' + p.col; var cnt = subCount[key];
|
|
5424
|
+
var bandTop = TOPPAD + p.li * LANE_H;
|
|
5425
|
+
n._x = PAD + LANE_LBL + p.col * COL_W + COL_W / 2;
|
|
5426
|
+
n._y = bandTop + (p.sub + 1) * (LANE_H / (cnt + 1));
|
|
5427
|
+
});
|
|
5428
|
+
} else {
|
|
5429
|
+
var cols = {}; nodes.forEach(function (n) { (cols[layer[n.id]] = cols[layer[n.id]] || []).push(n); });
|
|
5430
|
+
var maxRows = 0; for (var c in cols) if (cols[c].length > maxRows) maxRows = cols[c].length;
|
|
5431
|
+
var diagramH = maxRows * ROW_H;
|
|
5432
|
+
for (var cc = 0; cc <= maxCol; cc++) {
|
|
5433
|
+
var arr = cols[cc] || [];
|
|
5434
|
+
var startY = TOPPAD + (diagramH - arr.length * ROW_H) / 2;
|
|
5435
|
+
arr.forEach(function (n, i) {
|
|
5436
|
+
n._x = PAD + cc * COL_W + COL_W / 2;
|
|
5437
|
+
n._y = startY + i * ROW_H + ROW_H / 2;
|
|
5438
|
+
});
|
|
5439
|
+
}
|
|
5440
|
+
}
|
|
5441
|
+
var W = PAD + LANE_LBL + (maxCol + 1) * COL_W + PAD;
|
|
5442
|
+
var H = hasLanes ? (TOPPAD + lanes.length * LANE_H + PAD) : (TOPPAD + (function () { var mx = 0; nodes.forEach(function (n) { if (n._y > mx) mx = n._y; }); return mx + ROW_H / 2; })());
|
|
5443
|
+
var P = { ink: '#1a1a1a', indigo: '#4f5b87', amber: '#c8a04d', tea: '#6f8b5e', vermilion: '#c8472e', line: '#b8ad95', washi: '#f5f0e6', bg2: '#ece6d8' };
|
|
5444
|
+
/* Auto-resize fix (Pablo 2026-06-16): a wide-flat flow (W >> H) used to
|
|
5445
|
+
* be scaled DOWN to the tab width (width:100%), shrinking the nodes to an
|
|
5446
|
+
* unreadable band with dead space below. min-width = the intrinsic width
|
|
5447
|
+
* keeps nodes at their designed size: a narrow flow still fills the width
|
|
5448
|
+
* (scaled up), a wide one renders 1:1 and the container scrolls. */
|
|
5449
|
+
var svg = '<svg class="pz-bpmn-svg" viewBox="0 0 ' + Math.round(W) + ' ' + Math.round(H) + '"'
|
|
5450
|
+
+ ' style="min-width:' + Math.round(W) + 'px"'
|
|
5451
|
+
+ ' preserveAspectRatio="xMinYMin meet" xmlns="http://www.w3.org/2000/svg">';
|
|
5452
|
+
svg += '<defs><marker id="bpmn-arrow" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto" markerUnits="strokeWidth">'
|
|
5453
|
+
+ '<path d="M0,0 L7,3 L0,6 z" fill="' + P.ink + '"/></marker></defs>';
|
|
5454
|
+
/* swimlanes */
|
|
5455
|
+
if (hasLanes) {
|
|
5456
|
+
for (var li2 = 0; li2 < lanes.length; li2++) {
|
|
5457
|
+
var ly = TOPPAD + li2 * LANE_H;
|
|
5458
|
+
svg += '<rect x="' + PAD + '" y="' + ly + '" width="' + (W - 2 * PAD) + '" height="' + LANE_H + '" fill="' + (li2 % 2 ? 'rgba(0,0,0,0.025)' : 'transparent') + '" stroke="' + P.line + '" stroke-width="1"/>';
|
|
5459
|
+
svg += '<text x="' + (PAD + 14) + '" y="' + (ly + LANE_H / 2) + '" transform="rotate(-90 ' + (PAD + 14) + ' ' + (ly + LANE_H / 2) + ')" text-anchor="middle" font-size="11" fill="' + P.indigo + '" font-weight="600">' + escapeHtml(String(lanes[li2].label || lanes[li2].id)) + '</text>';
|
|
5460
|
+
}
|
|
5461
|
+
}
|
|
5462
|
+
/* flujos (debajo de los nodos) */
|
|
5463
|
+
flows.forEach(function (f) {
|
|
5464
|
+
var a = byId[f.from], b = byId[f.to]; if (!a || !b) return;
|
|
5465
|
+
var ar = bpmnHalf(a), br = bpmnHalf(b);
|
|
5466
|
+
var sx, sy = a._y, tx, ty = b._y;
|
|
5467
|
+
var forward = (b._x >= a._x);
|
|
5468
|
+
sx = a._x + (forward ? ar : -ar); tx = b._x - (forward ? br : -br);
|
|
5469
|
+
var midx = (sx + tx) / 2;
|
|
5470
|
+
var d;
|
|
5471
|
+
if (Math.abs(sy - ty) < 2) { d = 'M' + sx + ',' + sy + ' L' + tx + ',' + ty; }
|
|
5472
|
+
else if (forward) { d = 'M' + sx + ',' + sy + ' L' + midx + ',' + sy + ' L' + midx + ',' + ty + ' L' + tx + ',' + ty; }
|
|
5473
|
+
else { /* loop hacia atras: por abajo */ var down = Math.max(a._y, b._y) + 46; d = 'M' + (a._x) + ',' + (a._y + ar) + ' L' + a._x + ',' + down + ' L' + b._x + ',' + down + ' L' + b._x + ',' + (b._y + br); }
|
|
5474
|
+
svg += '<path d="' + d + '" fill="none" stroke="' + P.ink + '" stroke-width="1.4" marker-end="url(#bpmn-arrow)" opacity="0.8"/>';
|
|
5475
|
+
if (f.label) {
|
|
5476
|
+
var lx = forward ? midx : ((a._x + b._x) / 2), lyy = forward ? (sy + ty) / 2 - 4 : (Math.max(a._y, b._y) + 42);
|
|
5477
|
+
svg += '<rect x="' + (lx - String(f.label).length * 3.1 - 3) + '" y="' + (lyy - 9) + '" width="' + (String(f.label).length * 6.2 + 6) + '" height="13" fill="' + P.washi + '" opacity="0.9"/>';
|
|
5478
|
+
svg += '<text x="' + lx + '" y="' + (lyy + 1) + '" text-anchor="middle" font-size="10" fill="' + P.ink + '">' + escapeHtml(String(f.label)) + '</text>';
|
|
5479
|
+
}
|
|
5480
|
+
});
|
|
5481
|
+
/* nodos */
|
|
5482
|
+
nodes.forEach(function (n) {
|
|
5483
|
+
var x = n._x, y = n._y, type = String(n.type || 'task');
|
|
5484
|
+
if (/start|^event$|end|intermediate/.test(type)) {
|
|
5485
|
+
var r = 18, sw = type.indexOf('end') >= 0 ? 3.4 : 1.6;
|
|
5486
|
+
var stroke = type.indexOf('start') >= 0 ? P.tea : (type.indexOf('end') >= 0 ? P.vermilion : P.indigo);
|
|
5487
|
+
svg += '<circle cx="' + x + '" cy="' + y + '" r="' + r + '" fill="' + P.washi + '" stroke="' + stroke + '" stroke-width="' + sw + '"/>';
|
|
5488
|
+
if (type.indexOf('intermediate') >= 0) svg += '<circle cx="' + x + '" cy="' + y + '" r="' + (r - 3.5) + '" fill="none" stroke="' + stroke + '" stroke-width="1.2"/>';
|
|
5489
|
+
if (n.label) svg += '<text x="' + x + '" y="' + (y + r + 13) + '" text-anchor="middle" font-size="10.5" fill="' + P.ink + '">' + escapeHtml(String(n.label).slice(0, 22)) + '</text>';
|
|
5490
|
+
} else if (/gateway|decision|fork|join|merge/.test(type)) {
|
|
5491
|
+
var g = 23, gk = String(n.gateway || (/parallel|fork|join/.test(type) ? 'parallel' : 'exclusive'));
|
|
5492
|
+
svg += '<path d="M' + x + ',' + (y - g) + ' L' + (x + g) + ',' + y + ' L' + x + ',' + (y + g) + ' L' + (x - g) + ',' + y + ' z" fill="' + P.washi + '" stroke="' + P.amber + '" stroke-width="1.8"/>';
|
|
5493
|
+
var sym = gk.charAt(0) === 'p' ? '+' : (gk.charAt(0) === 'i' ? 'O' : '\\u00d7');
|
|
5494
|
+
svg += '<text x="' + x + '" y="' + (y + 5) + '" text-anchor="middle" font-size="16" fill="' + P.amber + '" font-weight="700">' + sym + '</text>';
|
|
5495
|
+
if (n.label) svg += '<text x="' + x + '" y="' + (y + g + 13) + '" text-anchor="middle" font-size="10.5" fill="' + P.ink + '">' + escapeHtml(String(n.label).slice(0, 22)) + '</text>';
|
|
5496
|
+
} else {
|
|
5497
|
+
var tw = 118, th = 54;
|
|
5498
|
+
svg += '<rect x="' + (x - tw / 2) + '" y="' + (y - th / 2) + '" width="' + tw + '" height="' + th + '" rx="9" fill="' + P.bg2 + '" stroke="' + P.indigo + '" stroke-width="1.4"/>';
|
|
5499
|
+
var ic = { userTask: '\\u263a', serviceTask: '\\u2699', scriptTask: '\\u25b8', manualTask: '\\u270b', sendTask: '\\u2709', receiveTask: '\\u2709' }[type];
|
|
5500
|
+
if (ic) svg += '<text x="' + (x - tw / 2 + 9) + '" y="' + (y - th / 2 + 14) + '" font-size="11" fill="' + P.indigo + '">' + ic + '</text>';
|
|
5501
|
+
var ls = bpmnWrap(n.label || n.id, 17, 3);
|
|
5502
|
+
var ty0 = y - (ls.length - 1) * 7;
|
|
5503
|
+
for (var li3 = 0; li3 < ls.length; li3++) svg += '<text x="' + x + '" y="' + (ty0 + li3 * 14 + 4) + '" text-anchor="middle" font-size="11.5" fill="' + P.ink + '">' + escapeHtml(ls[li3]) + '</text>';
|
|
5504
|
+
}
|
|
5505
|
+
});
|
|
5506
|
+
svg += '</svg>';
|
|
5507
|
+
return svg;
|
|
5508
|
+
}
|
|
5509
|
+
/* mitad del ancho/alto del nodo para anclar los flujos al borde. */
|
|
5510
|
+
function bpmnHalf(n) {
|
|
5511
|
+
var type = String(n.type || 'task');
|
|
5512
|
+
if (/start|^event$|end|intermediate/.test(type)) return 18;
|
|
5513
|
+
if (/gateway|decision|fork|join|merge/.test(type)) return 23;
|
|
5514
|
+
return 59;
|
|
5515
|
+
}
|
|
5516
|
+
|
|
4885
5517
|
const CODING_TAB_ID = 'coding_canvas';
|
|
4886
5518
|
const yfCoding = {
|
|
4887
5519
|
strokes: [], texts: [], fade: 0, phase: 'draw', /* draw | fadeout */
|
|
@@ -4934,8 +5566,12 @@ function renderCodingDetail() {
|
|
|
4934
5566
|
var item = feed[i];
|
|
4935
5567
|
var d = item.detail || { kind: 'other', target: item.text, path: null, diff: null, body: null };
|
|
4936
5568
|
var icon = CODING_ICON[d.kind] || '·';
|
|
5569
|
+
var ts = '';
|
|
5570
|
+
try { ts = new Date(item.t).toTimeString().slice(0, 8); } catch (e) { ts = ''; }
|
|
4937
5571
|
h += '<div class="pz-cw-row k-' + escapeHtml(d.kind || 'other') + '">'
|
|
4938
|
-
+ '<div class="pz-cw-head"
|
|
5572
|
+
+ '<div class="pz-cw-head">'
|
|
5573
|
+
+ '<span class="pz-cw-ts">' + ts + '</span>'
|
|
5574
|
+
+ '<span class="pz-cw-ic">' + icon + '</span>'
|
|
4939
5575
|
+ '<span class="pz-cw-target">' + escapeHtml(d.target || item.text || '') + '</span>'
|
|
4940
5576
|
+ (d.path ? '<span class="pz-cw-path">' + escapeHtml(d.path) + '</span>' : '')
|
|
4941
5577
|
+ '</div>';
|
|
@@ -5276,10 +5912,11 @@ function unmarkStartupTabClosed(key) {
|
|
|
5276
5912
|
* titulo real lo recordamos del ultimo fetch; el fallback cubre las que
|
|
5277
5913
|
* nunca se vieron. */
|
|
5278
5914
|
var yfStartupTitles = {};
|
|
5279
|
-
var NATIVE_TAB_KEYS = ['startup_graph', 'startup_graph_sem', 'startup_pendings', 'startup_workflow', 'coding_canvas'];
|
|
5915
|
+
var NATIVE_TAB_KEYS = ['startup_graph', 'startup_graph_sem', 'startup_pendings', 'startup_workflow', 'startup_tasks', 'coding_canvas'];
|
|
5280
5916
|
var NATIVE_TAB_FALLBACK = {
|
|
5281
5917
|
startup_graph: 'Grafo', startup_graph_sem: 'Grafo semantico',
|
|
5282
|
-
startup_pendings: 'Pendientes', startup_workflow: 'Workflow',
|
|
5918
|
+
startup_pendings: 'Pendientes', startup_workflow: 'Workflow',
|
|
5919
|
+
startup_tasks: 'Tareas', coding_canvas: 'Coding',
|
|
5283
5920
|
};
|
|
5284
5921
|
function nativeTabLabel(key) { return yfStartupTitles[key] || NATIVE_TAB_FALLBACK[key] || key; }
|
|
5285
5922
|
|
|
@@ -5305,8 +5942,15 @@ function pizarronUpsertById(item) {
|
|
|
5305
5942
|
const wasEditing = existing.isEditing;
|
|
5306
5943
|
/* No pisar si el usuario esta editando esa tab. */
|
|
5307
5944
|
if (!wasEditing) {
|
|
5945
|
+
/* Solo re-renderizar si el contenido/tipo/titulo CAMBIO. Antes se
|
|
5946
|
+
* re-renderizaba en cada refresh (cada 12s), lo que reiniciaba el
|
|
5947
|
+
* grafo: reseteaba los filtros de grupo + reposicionaba el layout
|
|
5948
|
+
* aunque nada hubiera cambiado. */
|
|
5949
|
+
const changed = existing.content !== item.content
|
|
5950
|
+
|| existing.kind !== item.kind
|
|
5951
|
+
|| existing.title !== item.title;
|
|
5308
5952
|
Object.assign(existing, item, { id: existing.id, isEditing: false });
|
|
5309
|
-
if (wasActive) renderPizarron();
|
|
5953
|
+
if (wasActive && changed) renderPizarron();
|
|
5310
5954
|
}
|
|
5311
5955
|
return existing.id;
|
|
5312
5956
|
}
|
|
@@ -5391,6 +6035,33 @@ function graphNodeColor(n) {
|
|
|
5391
6035
|
return '#8a7bb0'; /* violeta tenue */
|
|
5392
6036
|
}
|
|
5393
6037
|
|
|
6038
|
+
/* High-level group of a node, for the filter legend -- SAME meaning as
|
|
6039
|
+
* the server's nodeGroup (board_snapshot.ts) so desktop + mobile agree.
|
|
6040
|
+
* indexOf-only (NO regex: this lives inside the panel template literal,
|
|
6041
|
+
* where a regex backslash would be mangled). */
|
|
6042
|
+
const PZ_GROUP_COLORS = {
|
|
6043
|
+
backend: '#5b6aa6', frontend: '#6f8b5e', mobile: '#c2914a', tests: '#b06a6a',
|
|
6044
|
+
scripts: '#8a7bb0', libs: '#9a9a9a', tools: '#4a8a8a', protocol: '#b0894a',
|
|
6045
|
+
};
|
|
6046
|
+
/* Filtro de grupos por tab de grafo (id -> {group:boolean}). Se conserva
|
|
6047
|
+
* entre re-renders para que un refresh con datos nuevos NO reinicie la
|
|
6048
|
+
* seleccion del usuario. */
|
|
6049
|
+
const _graphFilterByTab = {};
|
|
6050
|
+
function graphNodeGroup(n) {
|
|
6051
|
+
const id = String((n && n.id) || '');
|
|
6052
|
+
const s = (id + ' ' + String((n && (n.label || n.name)) || '')).toLowerCase();
|
|
6053
|
+
if (id.indexOf('tool.ci.') === 0) return 'tools';
|
|
6054
|
+
if (id.indexOf('tool.npm.') === 0 || id.indexOf('tool.script.') === 0) return 'scripts';
|
|
6055
|
+
if ((n && n.kind === 'library') || id.indexOf('lib.') === 0) return 'libs';
|
|
6056
|
+
const has = (w) => s.indexOf(w) >= 0;
|
|
6057
|
+
if (has('test') || has('spec') || has('vitest') || has('playwright') || has('.qa') || has('qa.')) return 'tests';
|
|
6058
|
+
if (has('mobile') || has('forge-rn') || has('nac3-rn') || has('expo') || has('metro')) return 'mobile';
|
|
6059
|
+
if (has('panel') || has('pizarron') || has('chat') || has('reader') || has('design')
|
|
6060
|
+
|| has('figma') || has('webview') || has('component') || has('frontend')) return 'frontend';
|
|
6061
|
+
if (n && n.kind === 'protocol') return 'protocol';
|
|
6062
|
+
return 'backend';
|
|
6063
|
+
}
|
|
6064
|
+
|
|
5394
6065
|
function initGraphTab(it) {
|
|
5395
6066
|
const canvas = document.querySelector('[data-pz-graph-canvas]');
|
|
5396
6067
|
const detail = document.querySelector('[data-pz-graph-detail]');
|
|
@@ -5414,8 +6085,10 @@ function initGraphTab(it) {
|
|
|
5414
6085
|
label: String(n.label != null ? n.label : n.id != null ? n.id : i),
|
|
5415
6086
|
weight: typeof n.weight === 'number' ? n.weight : 0.5,
|
|
5416
6087
|
kind: n.kind || '',
|
|
6088
|
+
group: graphNodeGroup(n),
|
|
5417
6089
|
detail: n.detail || '',
|
|
5418
|
-
|
|
6090
|
+
confidence: n.confidence || 'auto',
|
|
6091
|
+
color: PZ_GROUP_COLORS[graphNodeGroup(n)] || graphNodeColor(n),
|
|
5419
6092
|
x: W / 2 + Math.cos(ang) * Math.min(W, H) * 0.32,
|
|
5420
6093
|
y: H / 2 + Math.sin(ang) * Math.min(W, H) * 0.32,
|
|
5421
6094
|
vx: 0, vy: 0, fixed: false,
|
|
@@ -5470,6 +6143,15 @@ function initGraphTab(it) {
|
|
|
5470
6143
|
const circle = document.createElementNS(SVGNS, 'circle');
|
|
5471
6144
|
circle.setAttribute('r', String(r));
|
|
5472
6145
|
circle.setAttribute('fill', n.color);
|
|
6146
|
+
/* PND graph -- nodos 'claimed' (declarados en chat, sin verificacion
|
|
6147
|
+
* automatica) se dibujan atenuados + con borde punteado para
|
|
6148
|
+
* distinguirlos de lo derivado del codigo (auto). Antes se ocultaban. */
|
|
6149
|
+
if (n.confidence === 'claimed') {
|
|
6150
|
+
circle.setAttribute('fill-opacity', '0.45');
|
|
6151
|
+
circle.setAttribute('stroke', n.color);
|
|
6152
|
+
circle.setAttribute('stroke-width', '1.5');
|
|
6153
|
+
circle.setAttribute('stroke-dasharray', '3 2');
|
|
6154
|
+
}
|
|
5473
6155
|
g.appendChild(circle);
|
|
5474
6156
|
const label = document.createElementNS(SVGNS, 'text');
|
|
5475
6157
|
label.setAttribute('class', 'pz-node-label');
|
|
@@ -5482,6 +6164,48 @@ function initGraphTab(it) {
|
|
|
5482
6164
|
return g;
|
|
5483
6165
|
});
|
|
5484
6166
|
|
|
6167
|
+
/* ---- Filtro por grupo (legend toggleable) -- desktop a la par del
|
|
6168
|
+
* mobile. Colorea por backend/frontend/mobile/tests/tools/scripts/
|
|
6169
|
+
* libs/protocol y permite mostrar/ocultar cada grupo. ---- */
|
|
6170
|
+
/* Restaurar la seleccion previa de este tab (si existe) para que un
|
|
6171
|
+
* refresh no la pierda; grupos nuevos arrancan visibles. */
|
|
6172
|
+
const savedVis = _graphFilterByTab[it.id] || {};
|
|
6173
|
+
const vis = {};
|
|
6174
|
+
const counts = {};
|
|
6175
|
+
nodes.forEach((n) => {
|
|
6176
|
+
vis[n.group] = Object.prototype.hasOwnProperty.call(savedVis, n.group) ? !!savedVis[n.group] : true;
|
|
6177
|
+
counts[n.group] = (counts[n.group] || 0) + 1;
|
|
6178
|
+
});
|
|
6179
|
+
_graphFilterByTab[it.id] = vis; /* el toggle muta este mismo objeto */
|
|
6180
|
+
const legend = document.createElement('div');
|
|
6181
|
+
legend.setAttribute('style', 'position:absolute;top:6px;left:6px;right:6px;display:flex;flex-wrap:wrap;gap:5px;z-index:6;pointer-events:auto;');
|
|
6182
|
+
Object.keys(counts).sort().forEach((k) => {
|
|
6183
|
+
const chip = document.createElement('button');
|
|
6184
|
+
chip.type = 'button';
|
|
6185
|
+
chip.setAttribute('data-g', k);
|
|
6186
|
+
chip.setAttribute('style', 'display:inline-flex;align-items:center;gap:5px;font:inherit;font-size:11px;color:#3a3a3a;'
|
|
6187
|
+
+ 'border:1px solid #d9d2c4;border-radius:14px;padding:3px 9px;background:#fff;cursor:pointer;opacity:' + (vis[k] ? '1' : '0.4') + ';');
|
|
6188
|
+
chip.innerHTML = '<span style="width:9px;height:9px;border-radius:50%;background:' + (PZ_GROUP_COLORS[k] || '#999') + '"></span>'
|
|
6189
|
+
+ escapeHtml(k) + ' ' + counts[k];
|
|
6190
|
+
chip.addEventListener('click', () => {
|
|
6191
|
+
vis[k] = !vis[k];
|
|
6192
|
+
chip.style.opacity = vis[k] ? '1' : '0.4';
|
|
6193
|
+
applyGraphFilter();
|
|
6194
|
+
});
|
|
6195
|
+
legend.appendChild(chip);
|
|
6196
|
+
});
|
|
6197
|
+
/* el canvas es position:relative en CSS; si no, el absolute cuelga del svg */
|
|
6198
|
+
canvas.style.position = canvas.style.position || 'relative';
|
|
6199
|
+
canvas.appendChild(legend);
|
|
6200
|
+
function applyGraphFilter() {
|
|
6201
|
+
for (let i = 0; i < nodeEls.length; i++) nodeEls[i].style.display = vis[nodes[i].group] ? '' : 'none';
|
|
6202
|
+
for (let k = 0; k < edges.length; k++) {
|
|
6203
|
+
const show = vis[nodes[edges[k].from].group] && vis[nodes[edges[k].to].group];
|
|
6204
|
+
edgeEls[k].style.display = show ? '' : 'none';
|
|
6205
|
+
}
|
|
6206
|
+
}
|
|
6207
|
+
applyGraphFilter();
|
|
6208
|
+
|
|
5485
6209
|
function showDetail(n) {
|
|
5486
6210
|
if (!detail) return;
|
|
5487
6211
|
nodeEls.forEach((g) => g.classList.toggle('selected', g._node === n));
|
|
@@ -5681,13 +6405,67 @@ function setStatus(text, isErr) {
|
|
|
5681
6405
|
|
|
5682
6406
|
function setBusy(b) {
|
|
5683
6407
|
state.busy = b;
|
|
5684
|
-
|
|
6408
|
+
/* Queue (task #28) -- the input + Send stay LIVE during a turn so the
|
|
6409
|
+
* user can type-while-working (like Claude Code): a send mid-turn is
|
|
6410
|
+
* queued and runs automatically when the turn finishes. Only the clip
|
|
6411
|
+
* (attachments mid-turn) is gated. */
|
|
6412
|
+
for (const sel of ['#bar-mini .clip', '#bar-full .clip']) {
|
|
6413
|
+
const el = $(sel); if (el) el.disabled = b;
|
|
6414
|
+
}
|
|
6415
|
+
for (const sel of ['#bar-mini textarea', '#bar-full textarea', '#bar-mini .send', '#bar-full .send']) {
|
|
6416
|
+
const el = $(sel); if (el) el.disabled = false;
|
|
6417
|
+
}
|
|
6418
|
+
/* Interrupt (task #27) -- a Detener button appears alongside Send
|
|
6419
|
+
* while a turn is in flight, so the user can stop a long / runaway
|
|
6420
|
+
* turn (a controlled fall: applied tool calls stay, the partial
|
|
6421
|
+
* answer is kept). */
|
|
6422
|
+
for (const sel of ['#bar-mini .stop', '#bar-full .stop']) {
|
|
5685
6423
|
const el = $(sel);
|
|
5686
|
-
if (el) el.
|
|
6424
|
+
if (el) { el.style.display = b ? '' : 'none'; el.disabled = false; }
|
|
5687
6425
|
}
|
|
6426
|
+
renderQueue();
|
|
5688
6427
|
renderStream();
|
|
5689
6428
|
}
|
|
5690
6429
|
|
|
6430
|
+
/* Queue (task #28) -- render the pending-message chips above the
|
|
6431
|
+
* composer. Each chip is removable before it runs. */
|
|
6432
|
+
function renderQueue() {
|
|
6433
|
+
const q = state.queue || [];
|
|
6434
|
+
for (const id of ['#queue-mini', '#queue-full']) {
|
|
6435
|
+
const el = $(id);
|
|
6436
|
+
if (!el) continue;
|
|
6437
|
+
if (!q.length) { el.style.display = 'none'; el.innerHTML = ''; continue; }
|
|
6438
|
+
el.style.display = '';
|
|
6439
|
+
el.innerHTML = '<span class="queue-label">' + escapeHtml(CONFIG.i18n.queueLabel) + '</span>'
|
|
6440
|
+
+ q.map(function (txt, i) {
|
|
6441
|
+
const short = txt.length > 42 ? txt.slice(0, 42) + '...' : txt;
|
|
6442
|
+
return '<span class="queue-chip" data-qrm="' + i + '" role="button" tabindex="0"'
|
|
6443
|
+
+ ' title="' + escapeHtml(CONFIG.i18n.queueRemove) + ': ' + escapeHtml(txt) + '">'
|
|
6444
|
+
+ escapeHtml(short) + ' <span class="queue-x">×</span></span>';
|
|
6445
|
+
}).join('');
|
|
6446
|
+
el.querySelectorAll('[data-qrm]').forEach(function (chip) {
|
|
6447
|
+
chip.addEventListener('click', function () {
|
|
6448
|
+
const idx = parseInt(chip.getAttribute('data-qrm'), 10);
|
|
6449
|
+
if (!isNaN(idx) && state.queue) { state.queue.splice(idx, 1); renderQueue(); }
|
|
6450
|
+
});
|
|
6451
|
+
});
|
|
6452
|
+
}
|
|
6453
|
+
}
|
|
6454
|
+
|
|
6455
|
+
/* Interrupt (task #27) -- POST the stop request. The server aborts the
|
|
6456
|
+
* active turn's signal; the in-flight send() unwinds via the SSE
|
|
6457
|
+
* turn_complete (interrupted:true) or the abort path. */
|
|
6458
|
+
async function interruptTurn() {
|
|
6459
|
+
setStatus(CONFIG.i18n.statusStopping);
|
|
6460
|
+
try {
|
|
6461
|
+
const r = await fetch('/api/chat/interrupt', { method: 'POST' });
|
|
6462
|
+
const j = await r.json().catch(function () { return {}; });
|
|
6463
|
+
if (!j || j.active === false) setStatus(CONFIG.i18n.statusNoActiveTurn);
|
|
6464
|
+
} catch (e) {
|
|
6465
|
+
setStatus(CONFIG.i18n.statusInterruptFailed, true);
|
|
6466
|
+
}
|
|
6467
|
+
}
|
|
6468
|
+
|
|
5691
6469
|
/* alpha.59e -- consume an SSE response stream from /api/chat.
|
|
5692
6470
|
* The body is text/event-stream with frames separated by an
|
|
5693
6471
|
* empty line. We parse each event as it arrives + update the
|
|
@@ -5878,7 +6656,18 @@ function handleSseEvent(event, data) {
|
|
|
5878
6656
|
|
|
5879
6657
|
async function send(text, opts) {
|
|
5880
6658
|
text = (text || '').trim();
|
|
5881
|
-
if (!text
|
|
6659
|
+
if (!text) return;
|
|
6660
|
+
/* Queue (task #28) -- a turn is already running: enqueue this message
|
|
6661
|
+
* instead of dropping it. It runs automatically when the current turn
|
|
6662
|
+
* finishes (drained in the finally below). Synthetic 'hidden' turns
|
|
6663
|
+
* (NAC3 approval continuations) and the drain itself bypass the queue.
|
|
6664
|
+
* 'Send now' steering = press Detener, then the queued message runs. */
|
|
6665
|
+
if (state.busy && !(opts && (opts.hidden || opts.fromQueue))) {
|
|
6666
|
+
if (!state.queue) state.queue = [];
|
|
6667
|
+
state.queue.push(text);
|
|
6668
|
+
renderQueue();
|
|
6669
|
+
return;
|
|
6670
|
+
}
|
|
5882
6671
|
/* alpha.49 -- opts.hidden=true pushes a user message that the
|
|
5883
6672
|
* LLM sees in context but the UI does NOT render in the bubble
|
|
5884
6673
|
* stream. Used by the NAC3 approval modal post-action to inject
|
|
@@ -5963,7 +6752,12 @@ async function send(text, opts) {
|
|
|
5963
6752
|
'yf limits set max_tool_rounds.',
|
|
5964
6753
|
};
|
|
5965
6754
|
var friendly = FRIENDLY_ERRORS[data.error];
|
|
5966
|
-
|
|
6755
|
+
/* Surface the DETAILED message (stderr del subproceso, motivo real
|
|
6756
|
+
* del fallo) en vez del codigo generico -- p.ej. modo Plan
|
|
6757
|
+
* claude_api_error traia el stderr del subproceso escondido.
|
|
6758
|
+
* (Pablo 2026-06-15) */
|
|
6759
|
+
var detail = (data.message && data.message !== data.error) ? data.message : '';
|
|
6760
|
+
setStatus(friendly || detail || data.error || ('HTTP ' + res.status), true);
|
|
5967
6761
|
// Drop the unanswered user message so the user can retry.
|
|
5968
6762
|
state.messages.pop();
|
|
5969
6763
|
return;
|
|
@@ -5971,6 +6765,13 @@ async function send(text, opts) {
|
|
|
5971
6765
|
/* alpha.58 -- attach brain_routing metadata to the bubble so
|
|
5972
6766
|
* renderStream can paint a tier badge + escalation arrow. */
|
|
5973
6767
|
const assistantMsg = { role: 'assistant', content: data.message.text };
|
|
6768
|
+
/* Interrupt (task #27) -- the turn was stopped mid-flight; mark the
|
|
6769
|
+
* partial answer so the user knows it is not the full reply. */
|
|
6770
|
+
if (data.interrupted === true) {
|
|
6771
|
+
assistantMsg.interrupted = true;
|
|
6772
|
+
assistantMsg.content = (assistantMsg.content || '') + '\\n\\n_' + CONFIG.i18n.labelInterrupted + '_';
|
|
6773
|
+
setStatus(CONFIG.i18n.statusTurnInterrupted);
|
|
6774
|
+
}
|
|
5974
6775
|
if (data.brain_routing && typeof data.brain_routing === 'object') {
|
|
5975
6776
|
const br = data.brain_routing;
|
|
5976
6777
|
assistantMsg.brain_tier = br.tier;
|
|
@@ -6143,15 +6944,50 @@ async function send(text, opts) {
|
|
|
6143
6944
|
* goes back to default state on the next turn. */
|
|
6144
6945
|
state.streamProgress = null;
|
|
6145
6946
|
setBusy(false);
|
|
6947
|
+
/* Queue (task #28) -- drain the next queued message, if any. A small
|
|
6948
|
+
* delay lets the DOM settle (busy flag cleared) before the next turn
|
|
6949
|
+
* starts; fromQueue bypasses the re-enqueue guard. */
|
|
6950
|
+
if (state.queue && state.queue.length) {
|
|
6951
|
+
const next = state.queue.shift();
|
|
6952
|
+
renderQueue();
|
|
6953
|
+
setTimeout(function () { send(next, { fromQueue: true }); }, 60);
|
|
6954
|
+
}
|
|
6146
6955
|
}
|
|
6147
6956
|
}
|
|
6148
6957
|
|
|
6149
6958
|
function attachBar(form) {
|
|
6150
|
-
const input = form.querySelector('
|
|
6959
|
+
const input = form.querySelector('textarea');
|
|
6960
|
+
/* Interrupt (task #27) -- the Detener button shares the composer
|
|
6961
|
+
* row; wire it to the stop endpoint. */
|
|
6962
|
+
const stopBtn = form.querySelector('[data-stop]');
|
|
6963
|
+
if (stopBtn) stopBtn.addEventListener('click', function () { interruptTurn(); });
|
|
6964
|
+
/* Auto-grow composer: grows with the content (bubble-like) up to
|
|
6965
|
+
~40vh (CSS cap), then scrolls. Reset to one row after sending. */
|
|
6966
|
+
function autoGrow() {
|
|
6967
|
+
input.style.height = 'auto';
|
|
6968
|
+
const cap = Math.round(window.innerHeight * 0.4);
|
|
6969
|
+
input.style.height = Math.min(input.scrollHeight, cap) + 'px';
|
|
6970
|
+
input.classList.toggle('expanded', input.scrollHeight > 48);
|
|
6971
|
+
}
|
|
6972
|
+
function resetGrow() {
|
|
6973
|
+
input.style.height = 'auto';
|
|
6974
|
+
input.classList.remove('expanded');
|
|
6975
|
+
}
|
|
6976
|
+
input.addEventListener('input', autoGrow);
|
|
6977
|
+
/* Enter sends; Shift/Alt/Ctrl/Cmd+Enter inserts a newline. Skip
|
|
6978
|
+
while an IME is composing (e.g. CJK) so Enter commits, not sends. */
|
|
6979
|
+
input.addEventListener('keydown', function (e) {
|
|
6980
|
+
if (e.key === 'Enter' && !e.shiftKey && !e.altKey && !e.ctrlKey && !e.metaKey && !e.isComposing) {
|
|
6981
|
+
e.preventDefault();
|
|
6982
|
+
if (typeof form.requestSubmit === 'function') form.requestSubmit();
|
|
6983
|
+
else form.dispatchEvent(new Event('submit', { cancelable: true }));
|
|
6984
|
+
}
|
|
6985
|
+
});
|
|
6151
6986
|
form.addEventListener('submit', (e) => {
|
|
6152
6987
|
e.preventDefault();
|
|
6153
6988
|
const v = input.value;
|
|
6154
6989
|
input.value = '';
|
|
6990
|
+
resetGrow();
|
|
6155
6991
|
/* SQ 0.8 slash command: /modo didactico|tecnico (alias EN
|
|
6156
6992
|
/mode). Handled client-side -- no LLM round-trip.
|
|
6157
6993
|
isSlashCommand is SYNCHRONOUS; handleSlashCommand runs
|
|
@@ -6604,15 +7440,7 @@ async function pizarronPlanHintCheck() {
|
|
|
6604
7440
|
try {
|
|
6605
7441
|
const r = await fetch('/api/forge/brain-config');
|
|
6606
7442
|
const data = await r.json();
|
|
6607
|
-
|
|
6608
|
-
if (isCli) {
|
|
6609
|
-
/* El bridge esta activo: arrancar el polling de la cola.
|
|
6610
|
-
* El aviso queda oculto porque el pizarron SI funciona. */
|
|
6611
|
-
if (hint) hint.classList.add('hidden');
|
|
6612
|
-
startPanelQueuePolling();
|
|
6613
|
-
} else {
|
|
6614
|
-
if (hint) hint.classList.add('hidden');
|
|
6615
|
-
}
|
|
7443
|
+
if (hint) hint.classList.add('hidden');
|
|
6616
7444
|
} catch { /* dejar como esta */ }
|
|
6617
7445
|
}
|
|
6618
7446
|
|
|
@@ -6655,9 +7483,26 @@ function materializePanelDispatch(d) {
|
|
|
6655
7483
|
/* proyecto nuevo: re-mostrar las 4 pestanas canonicas (respeta
|
|
6656
7484
|
* el switch en el endpoint). Se limpia el set de cerradas. */
|
|
6657
7485
|
try { localStorage.removeItem(YF_CLOSED_STARTUP_KEY); } catch (e) { /* */ }
|
|
7486
|
+
/* Project selector (task #33) -- update the header label to the new
|
|
7487
|
+
* project name the server reported. */
|
|
7488
|
+
try {
|
|
7489
|
+
if (d.args && d.args.name) { CONFIG.projectName = String(d.args.name); setProj(); }
|
|
7490
|
+
} catch (e) { /* */ }
|
|
6658
7491
|
loadStartupTabs(false).catch(function () {});
|
|
6659
7492
|
return;
|
|
6660
7493
|
}
|
|
7494
|
+
if (d.verb === 'system.remote-turn') {
|
|
7495
|
+
/* UI sync (task #32) -- a turn issued from the phone in the unified
|
|
7496
|
+
* 'notebook' thread ran on the desktop; show it here so the laptop
|
|
7497
|
+
* reflects what was asked + answered from the mobile. */
|
|
7498
|
+
const ra = d.args || {};
|
|
7499
|
+
if (ra.user) state.messages.push({ role: 'user', content: String(ra.user), remote: true });
|
|
7500
|
+
if (typeof ra.reply === 'string' && ra.reply) {
|
|
7501
|
+
state.messages.push({ role: 'assistant', content: ra.reply, remote: true });
|
|
7502
|
+
}
|
|
7503
|
+
renderStream();
|
|
7504
|
+
return;
|
|
7505
|
+
}
|
|
6661
7506
|
if (!d.args) return;
|
|
6662
7507
|
const a = d.args;
|
|
6663
7508
|
if (d.verb === 'pizarron.create-tab') {
|
|
@@ -6697,10 +7542,14 @@ function materializePanelDispatch(d) {
|
|
|
6697
7542
|
/* ---- PND-069: seccion Brain del panel de settings ----------- */
|
|
6698
7543
|
const yfBrain = { cfg: null, loading: false };
|
|
6699
7544
|
|
|
7545
|
+
/* Strict BYOK: the everyday brain is always the user own key
|
|
7546
|
+
* (Console / Plan / other providers). The yujin_managed virtual
|
|
7547
|
+
* provider is intentionally NOT offered here -- managed access is
|
|
7548
|
+
* reserved for the bounded onboarding concierge, never the daily brain. */
|
|
6700
7549
|
const BRAIN_PROVIDER_LABELS = {
|
|
6701
7550
|
anthropic: 'Anthropic', openai: 'OpenAI', google_ai: 'Google AI',
|
|
6702
7551
|
deepseek: 'DeepSeek', xai: 'xAI', mistral: 'Mistral',
|
|
6703
|
-
cohere: 'Cohere',
|
|
7552
|
+
cohere: 'Cohere',
|
|
6704
7553
|
};
|
|
6705
7554
|
|
|
6706
7555
|
function brainStatus(msg) {
|
|
@@ -6805,22 +7654,34 @@ async function brainConfigSave(patch, statusMsg) {
|
|
|
6805
7654
|
body: JSON.stringify(patch),
|
|
6806
7655
|
});
|
|
6807
7656
|
const data = await r.json();
|
|
6808
|
-
if (!r.ok || !data.ok) { brainStatus('error guardando'); return; }
|
|
7657
|
+
if (!r.ok || !data.ok) { brainStatus('error guardando'); return null; }
|
|
6809
7658
|
yfBrain.cfg = data.config;
|
|
6810
7659
|
brainStatus(statusMsg || 'guardado');
|
|
6811
7660
|
setTimeout(() => brainStatus(''), 2500);
|
|
7661
|
+
return data;
|
|
6812
7662
|
} catch (err) {
|
|
6813
7663
|
brainStatus('error: ' + (err && err.message ? err.message : String(err)));
|
|
7664
|
+
return null;
|
|
6814
7665
|
}
|
|
6815
7666
|
}
|
|
6816
7667
|
|
|
6817
7668
|
function brainConfigBind() {
|
|
6818
7669
|
document.querySelectorAll('input[name="brain-anthropic-mode"]').forEach((radio) => {
|
|
6819
|
-
radio.addEventListener('change', () => {
|
|
7670
|
+
radio.addEventListener('change', async () => {
|
|
6820
7671
|
if (!radio.checked) return;
|
|
6821
7672
|
const note = document.querySelector('[data-brain-mode-note]');
|
|
6822
|
-
|
|
6823
|
-
brainConfigSave({ anthropic_mode: radio.value }, '
|
|
7673
|
+
const modeLabel = radio.value === 'plan' ? 'Plan (suscripcion)' : 'Console (API key)';
|
|
7674
|
+
const data = await brainConfigSave({ anthropic_mode: radio.value }, 'aplicando ' + modeLabel + '...');
|
|
7675
|
+
/* Hot-swap (server applies live now). Only show the restart hint if
|
|
7676
|
+
* the swap did NOT apply live (e.g. test client). */
|
|
7677
|
+
const live = data && data.applied_live;
|
|
7678
|
+
if (note) {
|
|
7679
|
+
note.hidden = false;
|
|
7680
|
+
note.textContent = live
|
|
7681
|
+
? modeLabel + ' aplicado. El proximo turno ya usa este modo.'
|
|
7682
|
+
: 'Se aplica al reabrir el panel.';
|
|
7683
|
+
}
|
|
7684
|
+
if (live) brainStatus(modeLabel + ' activo');
|
|
6824
7685
|
});
|
|
6825
7686
|
});
|
|
6826
7687
|
const ecoToggle = document.querySelector('[data-brain-eco-enabled]');
|
|
@@ -7511,7 +8372,7 @@ async function micToggle(button) {
|
|
|
7511
8372
|
return;
|
|
7512
8373
|
}
|
|
7513
8374
|
/* Inject the transcript into the active input + submit. */
|
|
7514
|
-
const inputSel = state.mode === 'full' ? '#bar-full
|
|
8375
|
+
const inputSel = state.mode === 'full' ? '#bar-full textarea' : '#bar-mini textarea';
|
|
7515
8376
|
const input = $(inputSel);
|
|
7516
8377
|
if (input) {
|
|
7517
8378
|
input.value = text;
|
|
@@ -8099,7 +8960,7 @@ async function _micToggleWebSpeech(button) {
|
|
|
8099
8960
|
_lastInputWasVoice = true;
|
|
8100
8961
|
_lastVoiceButton = recordingPanelButton;
|
|
8101
8962
|
/* Inject + submit, same as the Google-STT path. */
|
|
8102
|
-
const inputSel = state.mode === 'full' ? '#bar-full
|
|
8963
|
+
const inputSel = state.mode === 'full' ? '#bar-full textarea' : '#bar-mini textarea';
|
|
8103
8964
|
const input = $(inputSel);
|
|
8104
8965
|
if (input) {
|
|
8105
8966
|
input.value = text;
|
|
@@ -8356,6 +9217,11 @@ function installSupportCollector() {
|
|
|
8356
9217
|
document.addEventListener('DOMContentLoaded', () => {
|
|
8357
9218
|
installSupportCollector();
|
|
8358
9219
|
setProj();
|
|
9220
|
+
/* Project selector (task #33) -- the header project label opens the
|
|
9221
|
+
* switcher. */
|
|
9222
|
+
$$('[data-proj-switch]').forEach(function (el) {
|
|
9223
|
+
el.addEventListener('click', function (e) { e.stopPropagation(); openProjectMenu(el); });
|
|
9224
|
+
});
|
|
8359
9225
|
/* alpha.43 F1 -- prime the NAC3 manifest cache on boot so the
|
|
8360
9226
|
first chat-response version stamp has something to compare
|
|
8361
9227
|
against. Silent: if the panel never sent a request before,
|
|
@@ -8574,6 +9440,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
8574
9440
|
brainConfigBind();
|
|
8575
9441
|
panelZoomMaximizeBind();
|
|
8576
9442
|
pizarronPlanHintCheck();
|
|
9443
|
+
/* Panel-queue polling runs in ALL modes (Pablo 2026-06-15): the queue
|
|
9444
|
+
* carries cross-cutting events -- system.remote-turn (mobile chats in
|
|
9445
|
+
* the unified session) + system.reanchor (project switch) + chat-log --
|
|
9446
|
+
* that must reflect in Console/API mode too, not only Plan/CLI. The bug
|
|
9447
|
+
* was gating the poller on claude-code-cli, so in Console mode the
|
|
9448
|
+
* desktop never saw mobile turns nor a project switch. */
|
|
9449
|
+
startPanelQueuePolling();
|
|
8577
9450
|
/* Click outside closes. */
|
|
8578
9451
|
document.addEventListener('click', (e) => {
|
|
8579
9452
|
if (!settingsPanel || settingsPanel.classList.contains('hidden')) return;
|