diff --git a/plugins/ms-ai-architect/playground/ms-ai-architect-v3.html b/plugins/ms-ai-architect/playground/ms-ai-architect-v3.html index 35ef621..923a463 100644 --- a/plugins/ms-ai-architect/playground/ms-ai-architect-v3.html +++ b/plugins/ms-ai-architect/playground/ms-ai-architect-v3.html @@ -1337,18 +1337,338 @@ ); } - // Stub for project + catalog som fylles i Steps 7, 9. + // ============================================================ + // PROJECT SURFACE (Step 7) + // ============================================================ + // + // Per-prosjekt detalj: header med navn + scenario-chips, 5 kategori-tabs + // (én per CATALOG-kategori), command-kort i hver tab. Sub-zones per kort: + // 1. Skjema-zone — placeholder (Step 8 fyller med renderCommandForm) + // 2. Paste-import — KUN for produces_report=true (textarea + parse-knapp) + // 3. Rapport-slot — KUN for produces_report=true (data-report-slot) + // Verktøy-commands får skjema-zone + .guide-panel--info 'Verktøy'-notis. + // + // Prosjekt-opprettelse via modal (createProjectFromModal). projectId = + // crypto.randomUUID. Sletting via .error-summary-modal med eksplisitt + // bekreftelse. + // + // Active-tab er transient (modul-lokal currentProjectTab) så export-state + // ikke forurenses av UI-state. Default 'regulatory' ved hver project-enter. + + // 8 scenarioer fra v2 — gjenbrukes som scenario-tags på prosjekter. + const SCENARIOS = [ + { id: 'rag-chatbot', name: 'RAG-chatbot for interne dokumenter' }, + { id: 'autonomous-agent', name: 'Autonom agent for saksbehandling' }, + { id: 'document-classification', name: 'Dokumentklassifisering og -prosessering' }, + { id: 'multi-agent', name: 'Multi-agent workflow' }, + { id: 'copilot-extension', name: 'Copilot-utvidelse for M365' }, + { id: 'customer-service', name: 'Kundeservice-chatbot' }, + { id: 'intelligent-search', name: 'Intelligent søk på tvers av fagsystemer' }, + { id: 'reporting', name: 'AI-assistert rapportering' } + ]; + + let currentProjectTab = 'regulatory'; + + function findProject(id) { + const list = store.state.projects || []; + for (let i = 0; i < list.length; i++) { + if (list[i].id === id) return list[i]; + } + return null; + } + + function createProject(data) { + const id = (typeof crypto !== 'undefined' && crypto.randomUUID) + ? crypto.randomUUID() + : 'p-' + Date.now() + '-' + Math.random().toString(36).slice(2, 10); + const project = { + id: id, + name: data.name || 'Uten navn', + description: data.description || '', + scenarios: Array.isArray(data.scenarios) ? data.scenarios.slice() : [], + createdAt: new Date().toISOString(), + reports: {} // commandId → { input: {...}, raw_markdown: '', parsed: {...} } + }; + // Push via Proxy så change-event fyres og persistens skedules. + store.state.projects.push(project); + store.state.activeProjectId = id; + currentProjectTab = 'regulatory'; + return project; + } + + function deleteProject(id) { + const list = store.state.projects; + for (let i = 0; i < list.length; i++) { + if (list[i].id === id) { + list.splice(i, 1); + break; + } + } + if (store.state.activeProjectId === id) store.state.activeProjectId = null; + } + + // ---- Modal infrastructure ---- + + function mountModal(html) { + unmountModal(); + const wrapper = document.createElement('div'); + wrapper.innerHTML = html; + const node = wrapper.firstElementChild; + if (!node) return; + node.setAttribute('data-modal-root', 'true'); + document.body.appendChild(node); + // Klikk på backdrop (selve roten) lukker; klikk inni .modal bobler ikke til root. + node.addEventListener('click', function (ev) { + if (ev.target === node) unmountModal(); + }); + // Esc lukker + function escHandler(ev) { + if (ev.key === 'Escape') { + unmountModal(); + document.removeEventListener('keydown', escHandler); + } + } + document.addEventListener('keydown', escHandler); + // Fokuser første input + setTimeout(function () { + const first = node.querySelector('input, select, textarea, button'); + if (first && first.focus) first.focus(); + }, 0); + } + + function unmountModal() { + const existing = document.querySelector('[data-modal-root]'); + if (existing && existing.parentNode) existing.parentNode.removeChild(existing); + } + + function renderNewProjectModalHtml() { + const scenarioOptions = SCENARIOS.map(function (s, i) { + return ( + '' + ); + }).join(''); + return ( + '' + ); + } + + function renderDeleteProjectModalHtml(project) { + const reportCount = projectReportCount(project); + return ( + '' + ); + } + + // ---- Sub-card rendering ---- + + function renderCommandSubCard(cmd) { + const titleHtml = ( + '
' + + '
' + + '

' + escapeHtml(cmd.label) + '

' + + '

' + escapeHtml(cmd.description) + '

' + + '
' + + '/architect:' + escapeHtml(cmd.id) + '' + + '
' + ); + + const formZone = ( + '
' + + '

Skjema

' + + '
Skjema-renderer kommer i Step 8 (' + cmd.input_fields.length + ' felter, ' + (cmd.input_fields.filter(function (f) { return f.from === 'shared'; }).length) + ' fra shared).
' + + '
' + ); + + if (!cmd.produces_report) { + // Verktøy: skjema-zone + .guide-panel--info notis + const toolNotice = ( + '
' + + '
' + + '' + + '
' + + '

Verktøy

' + + '

Dette er et verktøy. Ingen rapport-import — bruk skjemaet til å bygge en pipeline-streng som kjøres i terminalen.

' + + '
' + + '
' + + '
' + ); + return ( + '
' + + titleHtml + + formZone + + toolNotice + + '
' + ); + } + + // Rapport-produserende: skjema-zone + paste-import-zone + report-zone + const pasteZone = ( + '
' + + '

Lim inn rapport-output

' + + '
' + + '' + + '
' + + '' + + 'Routes via PARSERS[' + escapeHtml(cmd.report_archetype || '?') + '] → ' + escapeHtml(cmd.renderer || '?') + ' (Step 11/12).' + + '
' + + '
' + + '
' + ); + + const reportZone = ( + '
' + + '

Visualisering

' + + '
' + + '
' + ); + + return ( + '
' + + titleHtml + + formZone + + pasteZone + + reportZone + + '
' + ); + } + function renderProjectSurface() { const root = getSurfaceEl('project'); if (!root) return; - root.innerHTML = renderTopbar('Prosjekt') + '

Prosjekt-detaljvisning fylles i Step 7.

'; + + const project = findProject(store.state.activeProjectId); + if (!project) { + // Mistet aktivt prosjekt — fall tilbake til hjem. + navigate('home'); + return; + } + + const reportTotal = CATALOG.commands.filter(function (c) { return c.produces_report; }).length; + const reportFilled = projectReportCount(project); + + const scenarioChips = (project.scenarios || []).map(function (sid) { + const s = SCENARIOS.find(function (x) { return x.id === sid; }); + return '' + escapeHtml(s ? s.name : sid) + ''; + }).join(''); + const dateChip = 'opprettet ' + escapeHtml((project.createdAt || '').slice(0, 10)) + ''; + const progressChip = '' + reportFilled + '/' + reportTotal + ' rapporter'; + + const headerHtml = ( + '
' + + '
' + + '
' + + '

' + escapeHtml(project.name) + '

' + + (project.description ? '

' + escapeHtml(project.description) + '

' : '') + + '
' + + '
' + + '' + + '' + + '
' + + '
' + + '
' + dateChip + progressChip + scenarioChips + '
' + + '
' + ); + + // Tabs per CATALOG.categories + const tabsHtml = '
' + CATALOG.categories.map(function (cat) { + const isActive = currentProjectTab === cat.id; + return ( + '' + ); + }).join('') + '
'; + + // Render ALLE kategori-paneler i DOM (med [hidden] på inaktive). Dette + // sikrer at querySelectorAll('[data-paste-import]') matcher alle 17 + // rapport-produserende commands uavhengig av aktiv tab. + const panelsHtml = CATALOG.categories.map(function (cat) { + const isActive = currentProjectTab === cat.id; + const cards = CATALOG.commands + .filter(function (c) { return c.category === cat.id; }) + .map(renderCommandSubCard).join(''); + return ( + '
' + + cards + + '
' + ); + }).join(''); + + root.innerHTML = ( + renderTopbar('Prosjekt: ' + escapeHtml(project.name)) + + '
' + + headerHtml + + tabsHtml + + panelsHtml + + '
' + ); } + function renderCatalogStub() { const root = getSurfaceEl('catalog'); if (!root) return; root.innerHTML = renderTopbar('Katalog') + '

Command-katalog fylles i Step 9.

'; } + // ---- Paste-import stub (Step 12 erstatter med faktisk routing) ---- + + function handlePasteImport(commandId, markdown) { + // Stub: logger til konsoll for å verifisere DOM-kontrakten. Step 12 + // henter PARSERS[CATALOG[id].report_archetype] + RENDERERS[id], parser + // markdown og injiserer i [data-report-slot=""]. + console.log('parse-pending:', commandId, (markdown || '').slice(0, 80)); + const slot = document.querySelector('[data-report-slot="' + commandId + '"]'); + if (slot) { + slot.innerHTML = '

Markdown mottatt (' + (markdown || '').length + ' tegn). Parser+renderer kommer i Step 12.

'; + } + } + window.__handlePasteImport = handlePasteImport; + // ============================================================ // ONBOARDING SURFACE (Step 5) // ============================================================ @@ -1760,11 +2080,89 @@ navigate('project'); }; - // Stub — Step 7 erstatter med modal-åpning. ACTIONS['new-project'] = function () { - console.log('[playground v3] new-project: modal kommer i Step 7'); + mountModal(renderNewProjectModalHtml()); }; + ACTIONS['modal-cancel'] = function () { unmountModal(); }; + + ACTIONS['create-project'] = function () { + const modal = document.querySelector('[data-modal-root]'); + if (!modal) return; + const nameEl = modal.querySelector('[data-new-project-field="name"]'); + const descEl = modal.querySelector('[data-new-project-field="description"]'); + const errBox = modal.querySelector('[data-new-project-errors]'); + const errText = modal.querySelector('[data-new-project-error-text]'); + const name = nameEl ? String(nameEl.value || '').trim() : ''; + const description = descEl ? String(descEl.value || '').trim() : ''; + if (!name) { + if (errBox && errText) { + errBox.hidden = false; + errText.textContent = 'Prosjektnavn er påkrevd.'; + } + if (nameEl) nameEl.focus(); + return; + } + const scenarios = Array.from(modal.querySelectorAll('[data-new-project-scenario]')) + .filter(function (cb) { return cb.checked; }) + .map(function (cb) { return cb.value; }); + createProject({ name: name, description: description, scenarios: scenarios }); + unmountModal(); + navigate('project'); + }; + + ACTIONS['delete-project'] = function (ev, el) { + const id = el.dataset.projectId; + const project = findProject(id); + if (!project) return; + mountModal(renderDeleteProjectModalHtml(project)); + }; + + ACTIONS['confirm-delete-project'] = function (ev, el) { + const id = el.dataset.projectId; + if (!id) return; + deleteProject(id); + unmountModal(); + navigate('home'); + }; + + ACTIONS['project-tab'] = function (ev, el) { + const tab = el.dataset.tab; + if (!tab) return; + currentProjectTab = tab; + // Toggle visning uten full re-render (bevarer textarea-input). + const root = getSurfaceEl('project'); + if (!root) return; + const tabs = root.querySelectorAll('.project-tab'); + tabs.forEach(function (t) { + if (t.dataset.tab === tab) t.setAttribute('aria-current', 'true'); + else t.removeAttribute('aria-current'); + }); + const panels = root.querySelectorAll('[data-tab-panel]'); + panels.forEach(function (p) { + p.hidden = (p.dataset.tabPanel !== tab); + }); + }; + + ACTIONS['parse'] = function (ev, el) { + const commandId = el.dataset.command; + if (!commandId) return; + const root = getSurfaceEl('project'); + if (!root) return; + const textarea = root.querySelector('[data-paste-import="' + commandId + '"]'); + if (!textarea) return; + const markdown = textarea.value || ''; + handlePasteImport(commandId, markdown); + }; + + // Eksponer for Verify-asserts og Step 8/12. + window.__SCENARIOS = SCENARIOS; + window.__createProject = createProject; + window.__deleteProject = deleteProject; + window.__findProject = findProject; + window.__mountModal = mountModal; + window.__unmountModal = unmountModal; + ACTIONS['export-state'] = function () { try { exportState(); } catch (err) { console.error('[playground v3] export feilet:', err); alert('Eksport feilet: ' + err.message); }