Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/anasyoussoufi_copilotosvscode.json

1 line
19 KiB
JSON

{"repo": "anasyoussoufi/copilotosvscode", "files": {"cockpit/public/app.js": {"914d2ccf2782db5875a09b9e5a89b38953f3a579": {"t": 1785746024.1444721, "n": 18611, "h": "38e9e30202f62349", "c": "'use strict';\n\n/* =========================== Onglets =========================== */\nconst tabs = document.querySelectorAll('.tab');\ntabs.forEach((tab) =>\n tab.addEventListener('click', () => {\n tabs.forEach((t) => t.classList.remove('active'));\n tab.classList.add('active');\n document.querySelectorAll('.panel').forEach((p) => p.classList.remove('active'));\n document.getElementById(`panel-${tab.dataset.tab}`).classList.add('active');\n if (tab.dataset.tab === 'agents') loadAgents();\n if (tab.dataset.tab === 'mcp') loadMcp();\n if (tab.dataset.tab === 'settings') loadSettings();\n })\n);\n\nconst esc = (s) =>\n String(s).replace(/[&<>\"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' }[c]));\n\nfunction renderMarkdownLite(text) {\n let html = esc(text);\n html = html.replace(/`([^`\\n]+)`/g, '<code>$1</code>');\n html = html.replace(/\\*\\*([^*\\n]+)\\*\\*/g, '<strong>$1</strong>');\n html = html.replace(/(https?:\\/\\/[^\\s<]+)/g, '<a href=\"$1\" target=\"_blank\" rel=\"noopener\">$1</a>');\n return html;\n}\n\n/* =========================== WebSocket =========================== */\nconst connIndicator = document.getElementById('conn-indicator');\nconst connLabel = document.getElementById('conn-label');\nlet ws = null;\n\nfunction connect() {\n ws = new WebSocket(`${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}/ws`);\n ws.onopen = () => {\n connIndicator.className = 'conn online';\n connLabel.textContent = 'en ligne';\n };\n ws.onclose = () => {\n connIndicator.className = 'conn offline';\n connLabel.textContent = 'déconnecté — reconnexion…';\n setTimeout(connect, 2000);\n };\n ws.onmessage = (event) => handleEvent(JSON.parse(event.data));\n}\nconnect();\n\n/* =========================== Chat =========================== */\nconst chatMessages = document.getElementById('chat-messages');\nconst chatEmpty = document.getElementById('chat-empty');\nconst chatForm = document.getElementById('chat-form');\nconst chatText = document.getElementById('chat-text');\nconst btnSend = document.getElementById('btn-send');\nlet streamingEl = null;\nlet streamingRaw = '';\n\nfunction addMessage(cls, text) {\n chatEmpty.hidden = true;\n const el = document.createElement('div');\n el.className = `msg ${cls}`;\n el.innerHTML = renderMarkdownLite(text);\n chatMessages.appendChild(el);\n chatMessages.scrollTop = chatMessages.scrollHeight;\n return el;\n}\n\nchatForm.addEventListener('submit', (e) => {\n e.preventDefault();\n const text = chatText.value.trim();\n if (!text || !ws || ws.readyState !== 1) return;\n addMessage('user', text);\n chatText.value = '';\n btnSend.disabled = true;\n streamingRaw = '';\n streamingEl = addMessage('assistant streaming', '');\n ws.send(JSON.stringify({ type: 'chat', text }));\n});\n\nchatText.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n chatForm.requestSubmit();\n }\n});\n\ndocument.getElementById('btn-reset').addEventListener('click', () => {\n if (ws && ws.readyState === 1) ws.send(JSON.stringify({ type: 'reset' }));\n chatMessages.querySelectorAll('.msg').forEach((m) => m.remove());\n chatEmpty.hidden = false;\n clearFlow();\n});\n\nfunction handleEvent(event) {\n switch (event.type) {\n case 'token':\n if (!streamingEl) streamingEl = addMessage('assistant streaming', '');\n streamingRaw += event.text;\n streamingEl.innerHTML = renderMarkdownLite(streamingRaw);\n chatMessages.scrollTop = chatMessages.scrollHeight;\n break;\n case 'message_done':\n if (streamingEl) streamingEl.classList.remove('streaming');\n streamingEl = null;\n streamingRaw = '';\n btnSend.disabled = false;\n finishAllNodes();\n break;\n case 'error':\n addMessage('error', event.message);\n break;\n case 'notice':\n addMessage('notice', event.message);\n break;\n case 'agent_event':\n onAgentEvent(event);\n break;\n case 'tool_start':\n onToolStart(event);\n break;\n case 'tool_end':\n onToolEnd(event);\n if (!event.ok) {\n addMessage('error', `Échec de l'outil ${event.tool} (${event.server}) :\\n${event.resultPreview || 'pas de détail'}`);\n }\n break;\n case 'artifact':\n onArtifact(event);\n break;\n case 'claim_check':\n onClaimCheck(event);\n break;\n case 'validation_request':\n onValidationRequest(event);\n break;\n }\n}\n\nfunction onValidationRequest({ id, question }) {\n chatEmpty.hidden = true;\n const el = document.createElement('div');\n el.className = 'msg validation';\n el.innerHTML = `\n <div class=\"v-question\">🙋 Validation demandée :<br>${renderMarkdownLite(question)}</div>\n <div class=\"v-actions\">\n <button class=\"btn v-approve\">👍 Valider</button>\n <button class=\"btn v-reject\">👎 Refuser</button>\n </div>`;\n const answer = (approved) => {\n if (!ws || ws.readyState !== 1) return;\n ws.send(JSON.stringify({ type: 'validation_response', id, approved }));\n el.querySelectorAll('button').forEach((b) => (b.disabled = true));\n el.classList.add(approved ? 'approved' : 'rejected');\n el.querySelector('.v-actions').insertAdjacentHTML('beforeend', `<span class=\"v-result\">${approved ? '👍 validé' : '👎 refusé'}</span>`);\n };\n el.querySelector('.v-approve').addEventListener('click', () => answer(true));\n el.querySelector('.v-reject').addEventListener('click', () => answer(false));\n chatMessages.appendChild(el);\n chatMessages.scrollTop = chatMessages.scrollHeight;\n}\n\nconst claimChips = new Map(); // clé de ticket -> chip (le dernier verdict remplace le précédent)\n\nfunction onClaimCheck({ key, ok }) {\n let chip = claimChips.get(key);\n if (!chip) {\n chip = document.createElement('span');\n chip.className = 'chip';\n claimChips.set(key, chip);\n artifactGroup('🛡️ Vérification Jira').appendChild(chip);\n }\n if (ok) {\n chip.innerHTML = `✅ <span class=\"file\">${esc(key)} vérifié</span>`;\n } else {\n chip.innerHTML = `🚫 <span class=\"file\" style=\"color:#ffb4ae\">${esc(key)} INTROUVABLE</span>`;\n addMessage('error', `🚫 Le ticket ${key} annoncé par l'agent n'existe pas dans Jira. L'agent a probablement halluciné ce résultat.`);\n }\n}\n\n/* =========================== Visualisation =========================== */\nconst colCore = document.getElementById('col-core');\nconst colAgents = document.getElementById('col-agents');\nconst colTools = document.getElementById('col-tools');\nconst flowSvg = document.getElementById('flow-svg');\nconst flowCanvas = document.getElementById('flow-canvas');\nconst artifactList = document.getElementById('artifact-list');\n\nconst nodes = new Map(); // id -> {el, col}\nconst edges = new Map(); // \"from->to\" -> {from, to, cls, active}\nconst artifacts = new Set();\nlet lastActiveAgentId = null;\n\nfunction makeNode(id, column, name, extraClass = '') {\n if (nodes.has(id)) return nodes.get(id);\n const el = document.createElement('div');\n el.className = `node ${extraClass}`;\n el.dataset.id = id;\n el.innerHTML = `<div class=\"node-name\"><span class=\"pulse\"></span>${esc(name)}</div><div class=\"node-label\"></div>`;\n column.appendChild(el);\n const node = { el, column };\n nodes.set(id, node);\n drawEdges();\n return node;\n}\n\nfunction setNodeState(id, { active, done, failed, label }) {\n const node = nodes.get(id);\n if (!node) return;\n if (active !== undefined) node.el.classList.toggle('active', active);\n if (done !== undefined) node.el.classList.toggle('done', done);\n if (failed) node.el.classList.add('failed');\n if (label !== undefined) node.el.querySelector('.node-label').textContent = label;\n}\n\nfunction addEdge(from, to, cls = '') {\n const key = `${from}->${to}`;\n edges.set(key, { from, to, cls, active: true });\n drawEdges();\n}\n\nfunction deactivateEdgesTo(to) {\n for (const edge of edges.values()) if (edge.to === to) edge.active = false;\n drawEdges();\n}\n\nfunction drawEdges() {\n const canvasRect = flowCanvas.getBoundingClientRect();\n flowSvg.innerHTML = '';\n flowSvg.setAttribute('width', flowCanvas.scrollWidth);\n flowSvg.setAttribute('height', flowCanvas.scrollHeight);\n for (const edge of edges.values()) {\n const a = nodes.get(edge.from);\n const b = nodes.get(edge.to);\n if (!a || !b) continue;\n const ra = a.el.getBoundingClientRect();\n const rb = b.el.getBoundingClientRect();\n const x1 = ra.right - canvasRect.left + flowCanvas.scrollLeft;\n const y1 = ra.top + ra.height / 2 - canvasRect.top + flowCanvas.scrollTop;\n const x2 = rb.left - canvasRect.left + flowCanvas.scrollLeft;\n const y2 = rb.top + rb.height / 2 - canvasRect.top + flowCanvas.scrollTop;\n const mx = (x1 + x2) / 2;\n const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');\n path.setAttribute('d', `M ${x1} ${y1} C ${mx} ${y1}, ${mx} ${y2}, ${x2} ${y2}`);\n path.setAttribute('class', `edge ${edge.cls} ${edge.active ? 'active' : ''}`);\n flowSvg.appendChild(path);\n }\n}\nwindow.addEventListener('resize', drawEdges);\nflowCanvas.addEventListener('scroll', drawEdges);\n\nfunction ensureCoreNodes() {\n makeNode('user', colCore, '🧑 Utilisateur');\n makeNode('orchestrateur', colCore, '🎯 Orchestrateur');\n}\n\nfunction onAgentEvent({ agent, label, done }) {\n ensureCoreNodes();\n const isOrch = /orchestrateur/i.test(agent);\n const id = isOrch ? 'orchestrateur' : `agent:${agent}`;\n if (!isOrch) makeNode(id, colAgents, `🤖 ${agent}`, 'agent-node');\n if (done) {\n setNodeState(id, { active: false, done: true });\n deactivateEdgesTo(id);\n if (lastActiveAgentId === id) lastActiveAgentId = null;\n } else {\n setNodeState(id, { active: true, done: false, label: label || '' });\n if (isOrch) {\n addEdge('user', 'orchestrateur');\n } else {\n addEdge('orchestrateur', id, 'agent');\n lastActiveAgentId = id;\n }\n }\n}\n\nfunction onToolStart({ id, server, tool }) {\n ensureCoreNodes();\n const nodeId = `tool:${server}/${tool}`;\n makeNode(nodeId, colTools, `🔧 ${tool}`, '');\n const node = nodes.get(nodeId);\n node.el.querySelector('.node-label').textContent = server;\n setNodeState(nodeId, { active: true, done: false });\n addEdge(lastActiveAgentId || 'orchestrateur', nodeId);\n}\n\nfunction onToolEnd({ server, tool, ok }) {\n const nodeId = `tool:${server}/${tool}`;\n setNodeState(nodeId, { active: false, done: ok, failed: !ok });\n deactivateEdgesTo(nodeId);\n}\n\nconst artifactGroups = new Map(); // nom d'agent -> conteneur de chips\n\nfunction artifactGroup(name) {\n if (artifactGroups.has(name)) return artifactGroups.get(name);\n if (artifactList.querySelector('.muted')) artifactList.innerHTML = '';\n const group = document.createElement('div');\n group.className = 'artifact-group';\n group.innerHTML = `<div class=\"group-title\">${esc(name)}</div><div class=\"group-chips\"></div>`;\n artifactList.appendChild(group);\n const chips = group.querySelector('.group-chips');\n artifactGroups.set(name, chips);\n return chips;\n}\n\nconst ARTIFACT_ICONS = { ticket: '🎫', screenshot: '📸', file: '📄', url: '🔗' };\n\nfunction onArtifact({ kind, value, label, agent }) {\n if (artifacts.has(value)) return;\n artifacts.add(value);\n const chip = document.createElement('span');\n chip.className = `chip artifact-${kind}`;\n chip.title = value;\n const icon = ARTIFACT_ICONS[kind] || '📄';\n const text = label || value.split('/').pop() || value;\n if (kind === 'ticket' || kind === 'url') {\n chip.innerHTML = `${icon} <a href=\"${esc(value)}\" target=\"_blank\" rel=\"noopener\">${esc(text)}</a>`;\n } else {\n chip.innerHTML = `${icon} <span class=\"file\">${esc(text)}</span>`;\n }\n artifactGroup(`🤖 ${agent || 'Orchestrateur'}`).appendChild(chip);\n}\n\nfunction finishAllNodes() {\n for (const [id] of nodes) {\n setNodeState(id, { active: false });\n }\n for (const edge of edges.values()) edge.active = false;\n drawEdges();\n}\n\nfunction clearFlow() {\n nodes.clear();\n edges.clear();\n artifacts.clear();\n artifactGroups.clear();\n claimChips.clear();\n lastActiveAgentId = null;\n colCore.querySelectorAll('.node').forEach((n) => n.remove());\n colAgents.querySelectorAll('.node').forEach((n) => n.remove());\n colTools.querySelectorAll('.node').forEach((n) => n.remove());\n flowSvg.innerHTML = '';\n artifactList.innerHTML = '<span class=\"muted\">rien pour l\\'instant</span>';\n}\ndocument.getElementById('btn-clear-flow').addEventListener('click', clearFlow);\n\n/* =========================== Onglet Agents =========================== */\nasync function loadAgents() {\n const container = document.getElementById('agents-cards');\n const agents = await (await fetch('/api/agents')).json();\n container.innerHTML = '';\n for (const agent of agents) {\n const card = document.createElement('div');\n card.className = 'card';\n card.innerHTML = `\n <h3>🤖 ${esc(agent.name)}</h3>\n <div class=\"desc\">${esc(agent.description)}</div>\n ${agent.model ? `<div class=\"row\"><span class=\"badge\">${esc(agent.model)}</span></div>` : ''}\n <div class=\"row\">${(agent.tools || []).map((t) => `<span class=\"badge tool\">${esc(t)}</span>`).join('')}</div>\n <div class=\"actions\"><button class=\"btn small\" data-file=\"${esc(agent.file)}\">Voir le contrat</button></div>`;\n card.querySelector('button').addEventListener('click', async () => {\n const full = await (await fetch(`/api/agents/${encodeURIComponent(agent.file)}`)).json();\n document.getElementById('agent-dialog-title').textContent = full.name;\n document.getElementById('agent-dialog-body').textContent = full.body;\n document.getElementById('agent-dialog').showModal();\n });\n container.appendChild(card);\n }\n}\ndocument.getElementById('agent-dialog-close').addEventListener('click', () => document.getElementById('agent-dialog').close());\n\n/* =========================== Onglet MCP =========================== */\nlet lastMcpJson = '';\nasync function loadMcp(force = true) {\n const container = document.getElementById('mcp-cards');\n const servers = await (await fetch('/api/mcp')).json();\n const json = JSON.stringify(servers);\n if (!force && json === lastMcpJson) return;\n lastMcpJson = json;\n container.innerHTML = '';\n for (const server of servers) {\n const card = document.createElement('div');\n card.className = 'card';\n const statusLabel = { running: 'démarré', stopped: 'arrêté', starting: 'démarrage…', error: 'erreur' }[server.status] || server.status;\n card.innerHTML = `\n <h3><span class=\"status-dot ${server.status}\"></span>${esc(server.name)}</h3>\n <div class=\"desc\"><code>${esc(server.command)}</code></div>\n <div class=\"row\"><span class=\"badge\">${statusLabel}</span><span class=\"badge tool\">${server.tools.length} outils</span></div>\n ${server.error ? `<div class=\"err-text\">${esc(server.error)}</div>` : ''}\n ${server.tools.length ? `<details><summary>outils exposés</summary><ul class=\"tool-list\">${server.tools.map((t) => `<li><strong>${esc(t.name)}</strong> — ${esc(t.description.slice(0, 90))}</li>`).join('')}</ul></details>` : ''}\n <div class=\"actions\">\n <button class=\"btn small primary\" data-action=\"start\" ${server.status === 'running' ? 'disabled' : ''}>Démarrer</button>\n <button class=\"btn small danger\" data-action=\"stop\" ${server.status !== 'running' ? 'disabled' : ''}>Arrêter</button>\n <button class=\"btn small\" data-action=\"restart\" ${server.status !== 'running' ? 'disabled' : ''}>Redémarrer</button>\n </div>`;\n card.querySelectorAll('button[data-action]').forEach((btn) =>\n btn.addEventListener('click', async () => {\n btn.disabled = true;\n btn.textContent = '…';\n await fetch(`/api/mcp/${encodeURIComponent(server.name)}/${btn.dataset.action}`, { method: 'POST' });\n loadMcp();\n })\n );\n container.appendChild(card);\n }\n}\n\n/* =========================== Onglet Paramètres =========================== */\nconst PRESETS = {\n github: { baseUrl: 'https://models.github.ai/inference', model: 'openai/gpt-4o' },\n deepseek: { baseUrl: 'https://api.deepseek.com/v1', model: 'deepseek-chat' },\n ollama: { baseUrl: 'http://localhost:11434/v1', model: 'llama3.1' },\n lmstudio: { baseUrl: 'http://localhost:1234/v1', model: '' },\n openai: { baseUrl: 'https://api.openai.com/v1', model: 'gpt-4o-mini' },\n custom: { baseUrl: '', model: '' },\n};\n\nconst setPreset = document.getElementById('set-preset');\nconst setBaseUrl = document.getElementById('set-baseurl');\nconst setApiKey = document.getElementById('set-apikey');\nconst setModel = document.getElementById('set-model');\nconst setKeyState = document.getElementById('set-key-state');\nconst settingsStatus = document.getElementById('settings-status');\n\nsetPreset.addEventListener('change', () => {\n const preset = PRESETS[setPreset.value];\n if (preset && setPreset.value !== 'custom') {\n setBaseUrl.value = preset.baseUrl;\n if (preset.model) setModel.value = preset.model;\n }\n});\n\nasync function loadSettings() {\n const settings = await (await fetch('/api/settings')).json();\n setPreset.value = settings.provider.preset || 'custom';\n setBaseUrl.value = settings.provider.baseUrl || '';\n setModel.value = settings.provider.model || '';\n setApiKey.value = '';\n setKeyState.textContent = settings.provider.hasApiKey ? '(une clé est enregistrée)' : '(aucune clé)';\n document.getElementById('model-warning').hidden = Boolean(settings.provider.baseUrl && settings.provider.model);\n}\n\ndocument.getElementById('btn-save-settings').addEventListener('click', async () => {\n settingsStatus.textContent = 'enregistrement…';\n await fetch('/api/settings', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n provider: {\n preset: setPreset.value,\n baseUrl: setBaseUrl.value.trim(),\n apiKey: setApiKey.value.trim(),\n model: setModel.value.trim(),\n },\n }),\n });\n settingsStatus.textContent = '✓ enregistré';\n loadSettings();\n});\n\ndocument.getElementById('btn-test-settings').addEventListener('click', async () => {\n settingsStatus.textContent = 'test en cours…';\n const response = await fetch('/api/settings/test', { method: 'POST' });\n const result = await response.json();\n settingsStatus.textContent = result.ok ? '✓ connexion au modèle OK' : `✗ ${result.error}`;\n});\n\n/* =========================== Init =========================== */\nloadSettings();\n\n// Rafraîchit l'onglet MCP tant qu'il est visible (les serveurs peuvent être\n// auto-démarrés par le chat), sans reconstruire l'affichage si rien ne change.\nsetInterval(() => {\n if (document.getElementById('panel-mcp').classList.contains('active')) loadMcp(false);\n}, 4000);\n"}}}}