diff --git a/network-poc/hub/nodes.db b/network-poc/hub/nodes.db index 7cf2122..e6efe86 100644 Binary files a/network-poc/hub/nodes.db and b/network-poc/hub/nodes.db differ diff --git a/network-poc/static/avatars/README.md b/network-poc/static/avatars/README.md new file mode 100644 index 0000000..b5f7c11 --- /dev/null +++ b/network-poc/static/avatars/README.md @@ -0,0 +1,34 @@ +# Kipinä Agentic Playground - Animaatioiden käyttöönotto + +Koska Kipinä-verkon agenttien avatarit tällä erää ovat staattisia PNG-kuvatiedostoja, käyttöliittymä hyödyntää CSS-pohjaista pomppimisilmiötä (sekä pulppuavaa 💬 puhekuplaa) "puhumisen" merkkinä. Olemme kuitenkin koodanneet taustalle piilotetun tuen aivioiduille videoloopeille myöhempää käyttöä varten! + +Näin saat UI:n tukemaan oikeasti animoituja kasvoja/videoita. + +## 1. Luo Animoidut GIF-tiedostot +Valitse mikä tahansa ulkoinen AI-työkalu (kuten HeyGen, Pika v1.0, tai Midjourney+Runway yhdistelmä) ja muunna avatar-kuvat (esim. `kettu_notext.png`) 3-5 sekunnin kestäviksi GIF-loopeiksi. Hahmon leuka tulisi pyöriä tai naama vääntyillä puhuessaan. + +## 2. Nimeä Tiedostot Oikein ja Lisää Ne Kansioon +Siirrä uudet GIF-animaatiot samaan kansioon alkuperäisten kuvien kanssa. Muuta niiden nimi siten, että se päättyy tunnisteeseen `_puhuva.gif`. + +Esimerkkejä: +- Koodari `kipina_notext.png` → `kipina_notext_puhuva.gif` +- Manageri `karhunpentu.png` → `karhunpentu_puhuva.gif` +- Asiakas `kettu_notext.png` → `kettu_notext_puhuva.gif` + +## 3. Aktivoi Koodi +Käännä Kipinä Playground -ohjaimen JavaScript-koodista piilotettu ominaisuus päälle. + +Etsi tiedostosta `../index.html` (noin riviltä 1084, `updatePromptEditor`-funktiosta): +```javascript +// Piilotettu ominaisuus: Puhuvien videoiden / gif-animaatioiden kytkentä +window.USE_ANIMATED_GIFS = false; +``` +Muuta tuo `false` arvoon `true`: +```javascript +window.USE_ANIMATED_GIFS = true; +``` + +**Mitä logiikka tekee?** +Aina kun valitset agentin kaaviosta, koodi korvaa aktiivisen kuvakkeen lopussa olevan `.png` -päätteen sanalla `_puhuva.gif` – lennosta! Jos poistut agentin valinnasta tai valitset jonkun toisen, koodi vaihtaa kuvan välittömästi takaisin staattiseen `.png`-versioon ja sulkee ilmentymän suun. + +Näin saat kaikkien asiantuntijoiden face-track looppeja hallittua yhdellä kädenkäänteellä. diff --git a/network-poc/static/avatars/forge_hero.svg b/network-poc/static/avatars/forge_hero.svg deleted file mode 100644 index 6bba7a3..0000000 --- a/network-poc/static/avatars/forge_hero.svg +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/network-poc/static/avatars/lizard_ai.svg b/network-poc/static/avatars/lizard_ai.svg deleted file mode 100644 index 43d1cba..0000000 --- a/network-poc/static/avatars/lizard_ai.svg +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { - } - </> - - diff --git a/network-poc/static/avatars/raccoon_ai.svg b/network-poc/static/avatars/raccoon_ai.svg deleted file mode 100644 index 5e9d292..0000000 --- a/network-poc/static/avatars/raccoon_ai.svg +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/network-poc/static/avatars/sloth_ai.svg b/network-poc/static/avatars/sloth_ai.svg deleted file mode 100644 index 1194b47..0000000 --- a/network-poc/static/avatars/sloth_ai.svg +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Z - z - z - - - - - - - - - diff --git a/network-poc/static/index.html b/network-poc/static/index.html index 3747a83..ffd3030 100644 --- a/network-poc/static/index.html +++ b/network-poc/static/index.html @@ -393,6 +393,7 @@ align-items: center; margin-bottom: 40px; perspective: 1000px; + padding: 25px 50px; } .org-level { display: flex; @@ -439,6 +440,18 @@ border-color: rgba(240, 246, 252, 0.3); box-shadow: 0 12px 20px rgba(0,0,0,0.4); } + @keyframes idle-breathe { + 0%, 100% { transform: translateY(0) scale(1); } + 50% { transform: translateY(-2px) scale(1.01); } + } + @keyframes talking-head { + 0% { transform: scale(1.05) scaleY(1) translateY(0); } + 25% { transform: scale(1.05) scaleY(0.96) scaleX(1.02) translateY(2px); } + 50% { transform: scale(1.05) scaleY(1.02) scaleX(0.98) translateY(-2px); } + 75% { transform: scale(1.05) scaleY(0.97) scaleX(1.01) translateY(1px); } + 100% { transform: scale(1.05) scaleY(1) translateY(0); } + } + .avatar-card img { width: 80px; height: 80px; @@ -448,7 +461,10 @@ transition: all 0.4s ease; object-fit: cover; background: #010409; + animation: idle-breathe 4s infinite ease-in-out; + transform-origin: bottom center; } + .avatar-card.active, .avatar-card.selected { opacity: 1; transform: translateY(-8px) scale(1.05); @@ -457,10 +473,134 @@ box-shadow: 0 16px 24px rgba(0,0,0,0.5), 0 0 20px rgba(88, 166, 255, 0.3); z-index: 2; } - .avatar-card.active img, .avatar-card.selected img { + + .avatar-card.selected img { border-color: var(--accent-color); box-shadow: 0 0 25px rgba(88, 166, 255, 0.5); transform: scale(1.05); + animation: none; + } + + .avatar-card.active img { + border-color: var(--accent-color); + box-shadow: 0 0 25px rgba(88, 166, 255, 0.8); + animation: talking-head 0.4s infinite ease-in-out; + transform-origin: bottom center; + } + @keyframes talking-head-gallery { + 0% { transform: scaleY(1) translateY(0); } + 25% { transform: scaleY(0.94) scaleX(1.04) translateY(3px); } + 50% { transform: scaleY(1.04) scaleX(0.96) translateY(-3px); } + 75% { transform: scaleY(0.96) scaleX(1.02) translateY(1px); } + 100% { transform: scaleY(1) translateY(0); } + } + .gallery-head { + width: 55px; + height: 55px; + border-radius: 12px; + border: 2px solid rgba(240, 246, 252, 0.1); + object-fit: cover; + background: #010409; + transition: all 0.3s ease; + opacity: 0.4; + filter: grayscale(80%); + } + .gallery-head.active { + opacity: 1; + filter: grayscale(0%); + border-color: var(--accent-color); + box-shadow: 0 0 15px rgba(88, 166, 255, 0.5); + animation: talking-head-gallery 0.4s infinite ease-in-out; + transform-origin: bottom center; + } + + @keyframes confused-shake { + 0% { transform: translateX(0); } + 25% { transform: translateX(-2px) rotate(-3deg); } + 50% { transform: translateX(0); } + 75% { transform: translateX(2px) rotate(3deg); } + 100% { transform: translateX(0); } + } + + .gallery-head-wrap[data-tooltip]::before { + content: attr(data-tooltip); + position: absolute; + bottom: 110%; + left: 50%; + transform: translateX(-50%); + background: rgba(13, 17, 23, 0.95); + color: #f0f6fc; + padding: 8px 12px; + border-radius: 6px; + font-size: 11px; + white-space: pre-wrap; + width: 140px; + text-align: left; + border: 1px solid var(--border-color); + z-index: 100; + opacity: 0; + pointer-events: none; + transition: opacity 0.2s; + box-shadow: 0 4px 12px rgba(0,0,0,0.5); + } + .gallery-head-wrap:hover[data-tooltip]:not([data-tooltip=""])::before { opacity: 1; } + + /* Yhteiset kuplasäännöt */ + .gallery-head-wrap.state-question::after, + .gallery-head-wrap.state-alert::after, + .gallery-head-wrap.active:not(.state-question):not(.state-alert)::after { + position: absolute; + top: -10px; + right: -10px; + font-size: 14px; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + z-index: 10; + } + + /* State: Kysymys (Sininen ?) */ + .gallery-head-wrap.state-question::after { + content: '?'; + color: #ffffff; + font-weight: 900; + font-family: system-ui, -apple-system, sans-serif; + animation: speech-pulse 1s infinite alternate; + background: #1f6feb; border: 1px solid #58a6ff; + } + .gallery-head.state-question { + border-color: #58a6ff; box-shadow: 0 0 15px rgba(88, 166, 255, 0.4); + animation: confused-shake 2s infinite ease-in-out; filter: grayscale(10%); opacity: 0.9; + } + + /* State: Alert (Punainen !) */ + .gallery-head-wrap.state-alert::after { + content: '!'; + color: #ffffff; + font-weight: 900; + font-family: system-ui, -apple-system, sans-serif; + animation: speech-pulse 0.5s infinite alternate; + background: #da3633; border: 1px solid #ff7b72; + } + .gallery-head.state-alert { + border-color: #ff4444; box-shadow: 0 0 15px rgba(255, 68, 68, 0.5); + animation: confused-shake 0.5s infinite; filter: grayscale(30%); opacity: 0.9; + } + + .gallery-head-wrap { position: relative; display: inline-block; cursor: help; } + @keyframes speech-pulse { + 0% { transform: scale(0.8) translateY(0); opacity: 0.6; } + 50% { transform: scale(1.1) translateY(-2px); opacity: 1; } + 100% { transform: scale(0.8) translateY(0); opacity: 0.6; } + } + .gallery-head-wrap.active:not(.state-question):not(.state-alert)::after { + content: '💬'; + animation: speech-pulse 0.8s infinite ease-in-out; + background: #0d1117; + border: 1px solid var(--accent-color); } .avatar-name { font-weight: 700; font-size: 13px; color: #f0f6fc; letter-spacing: 0.5px; margin-bottom: 2px; } .avatar-role { font-size: 10px; color: #8b949e; text-transform: uppercase; letter-spacing: 0.5px; font-weight: 600; line-height: 1.2; word-wrap: break-word; } @@ -523,7 +663,7 @@ #metrics-grid { grid-template-columns: 1fr 1fr !important; } /* Org chart mobile tweaks */ - .org-chart { padding-left: 0; padding-right: 0; } + .org-chart { padding: 20px 10px; } .org-branch { display: none; } .org-connector { margin-bottom: 10px; height: 20px; } .org-level { flex-wrap: wrap; justify-content: center; gap: 15px !important; } @@ -859,75 +999,96 @@ Monitoring Active -
- -
-
- Asiakas (Kettu) -
Asiakas
-
Tuoteomistaja
+
+ +
+
+ +
+
+ Asiakas (Kettu) +
Asiakas
+
Tuoteomistaja
+
+
+ +
+ + +
+ +
+ Tarkkailija (Aikuinen Susi) +
Tarkkailija
+
Laadunvalvonta
+
+ +
+ Manageri (Karhunpentu) +
Manageri
+
KPN CLI
+
+
+ +
+ +
+ + +
+
+ Koodari (Salamanteri) +
Koodari
+
SOFTAKEHITYS
+
+
+ Data-Agentti (Pesukarhu) +
Data
+
Tietokannat
+
+
+ QA (Pikkususi) +
QA
+
Testaus
+
+
+ DevOps (Laiskiainen) +
DevOps
+
Käyttöönotto
+
+
+
+ + +
+
+ + Tallennettu +
+ +
- -
- - -
- -
- Tarkkailija (Aikuinen Susi) -
Tarkkailija
-
Laadunvalvonta
-
- -
- Manageri (Karhunpentu) -
Manageri
-
KPN CLI
-
-
- -
- -
- - -
-
- Koodari (Salamanteri) -
Koodari
-
Ohjelmistokehitys
-
-
- Data-Agentti (Pesukarhu) -
Data
-
Tietokannat
-
-
- QA (Pikkususi) -
QA
-
Testaus
-
-
- DevOps (Laiskiainen) -
DevOps
-
Käyttöönotto
+ + +
+
+
-
-
- - Tallennettu -
- - -
- -
+
$ kpn hub connect wss://localhost
✓ Yhdistetty Kipinä Hubiin
@@ -981,6 +1142,32 @@ } } + // Piilotettu ominaisuus: Puhuvien videoiden / gif-animaatioiden kytkentä + // Aseta true kun olet luonut _puhuva.gif tiedostot /avatars -kansioon + window.USE_ANIMATED_GIFS = false; + + // Update gallery heads + document.querySelectorAll('.gallery-head').forEach(el => { + el.classList.remove('active'); + if (window.USE_ANIMATED_GIFS) { + el.src = el.src.replace('_puhuva.gif', '.png'); + } + }); + document.querySelectorAll('.gallery-head-wrap').forEach(el => el.classList.remove('active')); + selectedAgents.forEach(agent => { + const gel = document.getElementById('gallery-' + agent); + const wrap = document.getElementById('wrap-' + agent); + if (gel) { + gel.classList.add('active'); + if (window.USE_ANIMATED_GIFS) { + gel.src = gel.src.replace('.png', '_puhuva.gif'); + } + } + if (wrap) wrap.classList.add('active'); + }); + + checkAgentConfusion(); + if (selectedAgents.size === 0) { editor.classList.remove('visible'); return; @@ -988,6 +1175,9 @@ editor.classList.add('visible'); + + + if (selectedAgents.size === 1) { const agent = [...selectedAgents][0]; const cfg = agentPrompts[agent]; @@ -1062,10 +1252,74 @@ localStorage.setItem('kpn-shared-prompt', e.target.value); } + checkAgentConfusion(); + saved.style.opacity = '1'; clearTimeout(saved._t); saved._t = setTimeout(() => saved.style.opacity = '0', 1500); }); + + function checkAgentConfusion() { + Object.keys(agentPrompts).forEach(agent => { + const prompt = agentPrompts[agent].prompt || ""; + const wrap = document.getElementById('wrap-' + agent); + const gel = document.getElementById('gallery-' + agent); + if (!wrap || !gel) return; + + // Nollataan tilat + wrap.classList.remove('state-question', 'state-alert'); + gel.classList.remove('state-question', 'state-alert'); + wrap.removeAttribute('data-tooltip'); + + const pLow = prompt.toLowerCase(); + const agentTitle = (agentPrompts[agent].name.split(' — ')[0] || "AGENTTI").toUpperCase(); + + // Hälytys / Virhe + if (pLow.includes('todo') || pLow.includes('viallinen') || pLow.includes('virhe')) { + wrap.classList.add('state-alert'); + gel.classList.add('state-alert'); + wrap.setAttribute('data-tooltip', `❗ ${agentTitle}: "Kriittinen virhe ohjeistuksessa!"\n(Koodissa tai promptissa esiintyy teksti TODO, viallinen tai virhe. Korjaa ohje välittömästi.)`); + } + // Kysyttävää / Hämmennys + else if (prompt.trim().length <= 15 || prompt.includes('?')) { + wrap.classList.add('state-question'); + gel.classList.add('state-question'); + + const questions = { + client: 'Millaisia uusia ominaisuuksia tuotteessa pitäisi olla?', + manager: 'Kuka asiantuntijoista ottaa vastuulleen tämän taskin?', + coder: 'Käytetäänkö tässä komponentissa uusinta React-ohjetta?', + data: 'Millainen tietorakenne käyttäjästä tallennetaan kantaan?', + qa: 'Onko tälle koodille olemassa jo kattavat yksikkötestit?', + tester: 'Mihin haluaisit julkaista tämän laiteympäristön version?', + observer: 'Mitä laatumetriikkoja minun tulisi ensisijaisesti painottaa?' + }; + const exampleQ = questions[agent] || 'Mitä minun pitäisi tehdä seuraavaksi?'; + + const reason = prompt.trim().length <= 15 ? 'Määrittely on tällä hetkellä liian lyhyt.' : 'Ohje on jätetty avoimeksi (? -merkki).'; + wrap.setAttribute('data-tooltip', `❓ ${agentTitle}: "${exampleQ}"\n\n(Agentti odottaa päätöstäsi: ${reason})`); + } + // Normaali keskustelu aktiivisena + else if (selectedAgents.has(agent)) { + // Haetaan satunnainen "toinen agentti", johon viitata + // Tehdään tästä deterministinen agentin nimen perusteella, ettei vilku + const targets = { + client: 'Managerin', + manager: 'Asiakkaan', + coder: 'DevOpsin', + data: 'Koodarin', + qa: 'Data-Agentin', + tester: 'QA:n', + observer: 'Managerin' + }; + const targetName = targets[agent] || 'Koodarin'; + wrap.setAttribute('data-tooltip', `💬 ${agentTitle}: "Hei, minäkin haluan osallistua!\nVoisin tehdä ${targetName} asiaan tällaisen toiminnallisuuden!"`); + } + }); + } + + // Tarkistetaan heti alussa + setTimeout(checkAgentConfusion, 100); window.switchMainTab = function(tab) { document.querySelectorAll('.main-panel').forEach(p => p.classList.remove('active'));