diff --git a/network-poc/static/index.html b/network-poc/static/index.html
index 1b68594..7d86d18 100644
--- a/network-poc/static/index.html
+++ b/network-poc/static/index.html
@@ -1870,6 +1870,148 @@
if (container) container.style.display = 'none';
}
+ // Projektikortti: tiedostovälilehdet + kopioi + lataa ZIP
+ function renderProjectCard(files, projectName) {
+ const fileEntries = Object.entries(files);
+ if (fileEntries.length === 0) return;
+
+ const cardId = 'proj-' + Date.now();
+ const tabsHtml = fileEntries.map(([name], i) =>
+ `${esc(name)}`
+ ).join('');
+
+ const panelsHtml = fileEntries.map(([name, code], i) =>
+ `
+
+
+
+
${highlightCode(code)}
+
`
+ ).join('');
+
+ const allText = fileEntries.map(([name, code]) => `# --- ${name} ---\n${code}`).join('\n\n');
+
+ const cardHtml = `
+
+
+ ${esc(projectName || 'Projekti')} (${fileEntries.length} tiedostoa)
+
+
+
+
+
+
${tabsHtml}
+
${panelsHtml}
+
`;
+
+ const div = document.createElement('div');
+ div.innerHTML = cardHtml;
+ termPanel.appendChild(div.firstElementChild);
+ termPanel.scrollTop = termPanel.scrollHeight;
+ }
+
+ // Globaalit funktiot projektikortin interaktioille
+ window.switchProjectTab = function(cardId, idx) {
+ document.querySelectorAll(`.proj-tab[data-card="${cardId}"]`).forEach((tab, i) => {
+ tab.style.background = i === idx ? '#161b22' : 'transparent';
+ tab.style.color = i === idx ? '#58a6ff' : '#8b949e';
+ tab.style.border = i === idx ? '1px solid #30363d' : 'none';
+ tab.style.borderBottom = i === idx ? 'none' : '';
+ });
+ document.querySelectorAll(`.proj-panel[data-card="${cardId}"]`).forEach((panel, i) => {
+ panel.style.display = i === idx ? '' : 'none';
+ });
+ };
+
+ window.copyFileContent = function(cardId, idx) {
+ const card = document.getElementById(cardId);
+ if (!card) return;
+ const files = JSON.parse(card.dataset.files);
+ const entries = Object.entries(files);
+ if (entries[idx]) {
+ navigator.clipboard.writeText(entries[idx][1]);
+ // Visuaalinen palaute
+ const btn = card.querySelectorAll(`.proj-panel[data-idx="${idx}"] button`)[0];
+ if (btn) { const orig = btn.textContent; btn.textContent = '✓ Kopioitu'; setTimeout(() => btn.textContent = orig, 1500); }
+ }
+ };
+
+ window.copyAllFiles = function(cardId) {
+ const card = document.getElementById(cardId);
+ if (!card) return;
+ const files = JSON.parse(card.dataset.files);
+ const text = Object.entries(files).map(([name, code]) => `# --- ${name} ---\n${code}`).join('\n\n');
+ navigator.clipboard.writeText(text);
+ const btn = card.querySelector('[onclick*="copyAllFiles"]');
+ if (btn) { const orig = btn.textContent; btn.textContent = '✓ Kopioitu'; setTimeout(() => btn.textContent = orig, 1500); }
+ };
+
+ window.downloadZip = async function(cardId) {
+ const card = document.getElementById(cardId);
+ if (!card) return;
+ const files = JSON.parse(card.dataset.files);
+
+ // Luodaan ZIP ilman ulkoisia kirjastoja (yksinkertainen uncompressed ZIP)
+ const entries = Object.entries(files);
+ const parts = [];
+ const centralDir = [];
+ let offset = 0;
+
+ for (const [name, content] of entries) {
+ const nameBytes = new TextEncoder().encode(name);
+ const contentBytes = new TextEncoder().encode(content);
+
+ // Local file header
+ const header = new Uint8Array(30 + nameBytes.length);
+ const view = new DataView(header.buffer);
+ view.setUint32(0, 0x04034b50, true); // Signature
+ view.setUint16(4, 20, true); // Version needed
+ view.setUint16(8, 0, true); // Method: store
+ view.setUint32(18, contentBytes.length, true); // Compressed size
+ view.setUint32(22, contentBytes.length, true); // Uncompressed size
+ view.setUint16(26, nameBytes.length, true);
+ header.set(nameBytes, 30);
+
+ // Central directory entry
+ const cdEntry = new Uint8Array(46 + nameBytes.length);
+ const cdView = new DataView(cdEntry.buffer);
+ cdView.setUint32(0, 0x02014b50, true);
+ cdView.setUint16(4, 20, true);
+ cdView.setUint16(6, 20, true);
+ cdView.setUint32(20, contentBytes.length, true);
+ cdView.setUint32(24, contentBytes.length, true);
+ cdView.setUint16(28, nameBytes.length, true);
+ cdView.setUint32(42, offset, true);
+ cdEntry.set(nameBytes, 46);
+
+ parts.push(header, contentBytes);
+ centralDir.push(cdEntry);
+ offset += header.length + contentBytes.length;
+ }
+
+ const cdOffset = offset;
+ let cdSize = 0;
+ for (const cd of centralDir) { parts.push(cd); cdSize += cd.length; }
+
+ // End of central directory
+ const eocd = new Uint8Array(22);
+ const eocdView = new DataView(eocd.buffer);
+ eocdView.setUint32(0, 0x06054b50, true);
+ eocdView.setUint16(8, entries.length, true);
+ eocdView.setUint16(10, entries.length, true);
+ eocdView.setUint32(12, cdSize, true);
+ eocdView.setUint32(16, cdOffset, true);
+ parts.push(eocd);
+
+ const blob = new Blob(parts, { type: 'application/zip' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'project.zip';
+ a.click();
+ URL.revokeObjectURL(url);
+ };
+
// Pipeline: manageri → koodari (per tiedosto) → testaaja → korjausluuppi
async function kpnPipeline(task) {
pipelineClear();
@@ -1984,6 +2126,7 @@ Write the corrected code.`;
}
termLog(`\n━━━ Pipeline valmis (${Object.keys(generatedFiles).length} tiedostoa) ━━━`);
+ renderProjectCard(generatedFiles, task);
}
// Yksinkertainen pipeline (vanha: manageri → koodari → testaaja)