48 Commits

Author SHA1 Message Date
afc7f9bcee Versio 0.2.4
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 18:52:21 +03:00
5d2027b2ca Native-node: automaattinen Ollama-haistelu käynnistyksessä
Jos OLLAMA_URL ei ole asetettu, kokeillaan järjestyksessä:
1. localhost:11434 (paikallinen Ollama)
2. 127.0.0.1:11434
3. ollama:11434 (Docker-verkko)
4. host.docker.internal:11434 (Docker-kontti → isäntä)

Ensimmäinen joka vastaa /api/version-kutsuun valitaan.
Timeout 2s per kokeilu. Jos OLLAMA_URL on asetettu, sitä käytetään suoraan.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 17:41:44 +03:00
8a4d515eed Client-compose: Ollama lisätty jokaiseen profiiliin (nvidia/amd/cpu)
Ollama-palvelu puuttui client-composesta — native-node yritti yhdistää
ollamaan jota ei ollut. Nyt jokaisessa profiilissa on oma Ollama
(nvidia: latest+GPU, amd: rocm+/dev/kfd, cpu: latest) network alias
'ollama' jotta native-node löytää sen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 17:40:33 +03:00
987a370a05 Docker: Cargo.lock valinnainen, Ollama AMD ROCm -tuella
- Dockerfile.native-node: Cargo.lock kopioidaan glob-patternilla (ei kaadu
  jos puuttuu, esim. .gitignore poistaa sen)
- docker-compose: Ollama vaihdettu ollama/ollama:rocm -imageen AMD GPU:lle,
  /dev/kfd + /dev/dri laitemappaukset, poistettu nvidia deploy.resources

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 17:33:27 +03:00
f75e7f07e9 Opas: tokenisointiesimerkki korvattu oikealla kuvakaappauksella
- Staattinen tekstitokenisointiesimerkki korvattu kuvalla joka
  näyttää värikoodatut tokenit EN/FI-vertailussa
- Markdown-renderöijään lisätty ![alt](src) kuvatuki
- Kuva: static/images/tokenization-example.png

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:20:50 +03:00
eb6f720fcc Wasm tokenize_js() exportti oppaan live-tokenizeria varten
Lisätty #[wasm_bindgen] tokenize_js(text) → JSON-funktio joka lataa
tokenizerin IndexedDB:stä tai HuggingFacesta tarvittaessa.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:18:07 +03:00
e25d0ea8f2 Versio 0.2.3
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:15:05 +03:00
4d1e89da34 Avatar-kuvakkeet 48px → 50px (+5%)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:14:14 +03:00
bf535b6256 Raportti: syntaksikorostus, CSS-tooltipsit rivityksellä, parannettu layout
- Lisätty highlightCode() — regex-pohjainen korostus .py, .html, Dockerfile
- Swimlane-badget: title-attr → CSS ::after tooltip (white-space:pre-wrap)
- Tiedostokortit: kieli + rivimäärä headeriin
- Yleinen ilme: pyöristetyt kulmat, monospace-fontti, parempi line-height
- Pipeline-vaiheet: selkeämmät erottimet ja väripaletti

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:10:37 +03:00
29e1c440c6 Native-node osoittaa nyt tuotantohubiin (kipina.studio)
HUB_URL vaihdettu ws://agentic-poc:3000/ws → wss://kipina.studio/ws
jotta lokaali GPU-solmu palvelee tuotantoympäristöä.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:05:19 +03:00
560bee1369 Laskentaverkko ja Koodilaboratorio -tabit takaisin näkyviin
Tabit olivat piilotettu display:none-tyylillä, mikä rikkoi myös
hash-navigoinnin (#codelab, #network).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 13:01:21 +03:00
b074e0cb49 Avatar-korttien opacity nostettu 0.5 → 0.8
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:55:46 +03:00
9307c75516 Pysty/vaaka-toggle avatareille
Lisätty nappi jolla org-chartin voi vaihtaa vaaka- ja pystyasennon välillä.
Vaaka on oletuksena (kompakti), pysty lisää vertical-CSS-luokan.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:55:07 +03:00
86191fbb6c Avatarit vaakariviin: kompakti layout, säästää pystytilaa
Org-chart muutettu vertikaalisesta hierarkiasta horisontaaliseksi riviksi:
Asiakas → Manageri → [Koodari, Data, QA, DevOps, Tarkkailija]
- Connector-viivat muutettu pystysuuntaisista vaakanuoliksi
- Avatar-kortit 72px leveitä (oli 130px), kuvat 48px (oli 80px)
- Roolikuvaus poistettu korteista — pelkkä nimi riittää
- Tarkkailija siirretty absolute-asemasta rivin loppuun
- Responsive-tyylit päivitetty

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:53:25 +03:00
a6a94f7688 Avatar-logiikka: poistettu kilpaileva llm_prompt-handler, korjattu vilkkumisjärjestys
Ongelma: kaksi erillistä avatar-aktivointilogiikkaa kilpaili:
1) pipelineStep() — oikea agentti statuksen perusteella
2) llm_prompt-handler — arvasi agentin prompt-tekstistä (usein väärin → DevOps vilkkui QA:n/Datan sijaan)

Korjaus:
- Poistettu llm_prompt- ja llm_done-handlerien avatar-heuristiikat
- pipelineStep() hoitaa kaiken: active → syttyy, done → sammuu heti
- Pipeline-lopussa kaikki avataret sammutetaan eksplisiittisesti

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:42:02 +03:00
8d5c5440d2 Avatar-vilahdus: oikea agentti aktivoituu vuorollaan, manageri-bugi korjattu
pipelineStep() aktivoi nyt oikean agentin avatarin (sekä card että gallery-head)
kun status on 'active', ja poistaa 'done'-statuksella sekunnin viiveellä.
Poistettu llm_done-handlerin turha manageri-aktivointi joka vilkutti aina manageria.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:38:08 +03:00
a12bd7ce7f One-liner koodi: system prompt vaatii rivinvaihdot + staattinen tarkistus
Ollaman system prompt: 'Use proper newlines and indentation'.
Staattinen analyysi: havaitsee jos .py-tiedosto on yhdellä rivillä.
Native node vaatii rebuildin.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:07:51 +03:00
9ac90aa540 pyproject.toml esimerkki: lisätty httpx ja pytest riippuvuuksiin
TestClient vaatii httpx:n, testit vaativat pyTestin.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:01:35 +03:00
32065d5818 Korjattu </script> index.html-esimerkissä joka katkaisi pääsivun JS:n
Sama bugi kuin aiemmin: template-literalin </script> sulkee
ulomman script-tagin. Pilkottu: '<'+'/script>'.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:00:53 +03:00
321943ee3c Esimerkkiprojektit: täysi CRUD + HTML UI root-osoitteessa
Few-shot esimerkit päivitetty:
- main.py: GET/POST/PUT/DELETE + FileResponse("/") index.html:lle
- index.html: yksinkertainen UI fetch()-kutsuilla API:in
- /api/ -prefiksi JSON-endpointeille
- Esimerkkipromptit kuvaavat CRUD-operaatiot eksplisiittisesti

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:49:38 +03:00
1b75c89320 Raportti: Mermaid → custom swimlane (kompakti, tooltipsit)
Agenteittain ryhmitelty visualisointi:
  Manageri  ✓ Suunnittelu
  Koodari   ✓ models.py → ✓ main.py → ✓ index.html
  DevOps    ✓ Review → ✓ Dockerfile → ✓ Compose → ✓ README
  QA        ✓ Testit → ✓ Validointi
Hover näyttää selityksen + output-esikatselun.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:39:23 +03:00
01622a960f Mermaid-kaavio LR (vaakasuunta) + tooltipsit joka vaiheelle
graph TD → graph LR: kaavio kiemurtelee vasemmalta oikealle.
Hover näyttää vaiheen selityksen ja output-esikatselun.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:33:23 +03:00
4e4efda67d Korjattu </script> template-literalissa joka katkaisi pääsivun JS:n
Template-stringin sisällä oleva </script> sulki ulomman script-tagin.
Pilkottu string-konkatenoimalla: '</'+' script>'.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:25:36 +03:00
f5db2eb034 Esimerkkiprojektit HTML UI:lla + Mermaid-kaavio raporttiin + tooltips
- Esimerkkipromptit sisältävät nyt HTML-käyttöliittymän
- Manageri generoi index.html tiedoston, Dockerfile kopioi sen
- README: docker compose up → http://localhost:8000
- Raporttiin Mermaid-kaavio agenttien workflowsta (CDN)
- Pipeline-vaiheiden hover näyttää selityksen

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 11:06:15 +03:00
77c8d46e7b Staattinen analyysi: tiedostojen väliset importit tarkistetaan
from db import get_db → tarkistaa onko get_db määritelty db.py:ssä
(def, class tai muuttuja). Löytää puuttuvat exportit ennen Docker-buildia.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:34:41 +03:00
f14eba1b49 Projektiraportti: HTML-dokumentaatio pipeline-vaiheista ja tiedostoista
Pipeline generoi HTML-raportin joka sisältää:
- Pipeline-vaiheet prompteineen ja tuloksineen (avattavat details)
- Staattisen analyysin tulokset
- Kaikki generoidut tiedostot korostettuina
- Raportti avautuu uuteen välilehteen linkistä terminaalissa
- Projektikorttiin lisätty 📄 Raportti -nappi

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:22:03 +03:00
6d15298418 Parannettu staattinen analyysi + docker-compose template
Staattinen analyysi: tarkistaa 15 yleistä symbolia (Boolean, Text,
Depends, HTTPException jne.) puuttuvista importeista.
Models-esimerkki: lisätty Boolean ja Text importteihin.
docker-compose.yml: template ilman LLM:ää — ei enää version tai
turhaa PostgreSQL-containeria SQLite-projektissa.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:16:19 +03:00
cea1961183 Testaaja: staattinen analyysi + strukturoitu LLM-arviointi
Staattinen analyysi selaimessa (ennen LLM:ää):
- Käyttämättömät importit
- Puuttuvat importit (FastAPI, Session)
- Tyhjät funktiot

LLM-arviointi 5 kohdalla (esimerkkivastauksineen):
1. Imports, 2. Database, 3. Endpoints, 4. Error handling, 5. Security

Korjausluuppi käynnistyy jos ✗ löytyy tai staattinen analyysi huomauttaa.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:13:15 +03:00
21a8015ea3 SQLAlchemy esimerkki: declarative_base() → DeclarativeBase (v2.0+)
declarative_base() on poistettu SQLAlchemy 2.0:sta.
Päivitetty molemmat esimerkit käyttämään class Base(DeclarativeBase).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:07:23 +03:00
c3991193d9 Pipeline-vaiheet rivittyvät + fixedDockerfile viittaus korjattu
Poistettu fixedDockerfile-viittaus joka kaatoi pipelinen ennen
renderProjectCard:ia → ZIP ei generoitunut.
Pipeline-vaiheet käyttävät nyt flex-wrap:ia eikä overflow-x:ää.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:03:14 +03:00
02c6d67218 Korjausvaihe ei ylikirjoita Dockerfilea — template pysyy
QA-validoinnin korjausvaihe antoi LLM:n generoida uuden Dockerfilen
joka sekoitti pip:n ja uv:n. Nyt korjaus kohdistuu vain .py ja
pyproject.toml -tiedostoihin. Dockerfile pysyy templatena.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:58:49 +03:00
de1cf009fa pyproject.toml: stdlib-moduulit (sqlite3, os, sys) kielletty riippuvuuksista
Malli laittoi sqlite3:n dependencies-listaan → uv sync epäonnistui.
Koodarin, QA:n ja validoinnin prompteihin lisätty selkeä kielto.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:53:10 +03:00
060f36f479 ZIP-lataus: null-tarkistus tiedostoille + virheilmoitus
Lisätty guard puuttuvalle projectFiles-datalle ja null-safe content.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:46:34 +03:00
e2ec0fa43d v0.2.2: responsiivinen UI, Ollama-proxy, mixed content korjaus
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:23:35 +03:00
8752c0f465 Responsiivinen korkeus: terminaali ja UI skaalautuvat viewport-korkeuteen
Terminaali: clamp(200px, 35vh, 500px) — skaalautuu ikkunan mukaan.
<900px korkeus: pienempi otsikko, tiiviimmät avataret, matalampi terminaali.
>1200px korkeus: isompi terminaali ja promptikenttä.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:22:15 +03:00
8c95282654 Tiivistetty layout: terminaali 500→300px, pienemmät marginaalit
Mahtuu 1440px korkeuteen ilman vieritystä.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:20:06 +03:00
a1bc1af646 Hardware API: Ollama-fallback kun wgpu ei tunnista GPU:ta Dockerissa
/api/v1/hardware tarkistaa nyt myös Ollaman tilan fallbackina.
kpn models näyttää ladattujen mallien määrän ja ✓ oikein.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:18:00 +03:00
6b27cbbade kpn load: rehellinen viesti — 'valittu' eikä 'ladattu ja aktiivinen'
Frontend ei tiedä onko malli oikeasti ladattu Ollamaan.
Nyt näytetään 'valittu — natiivisolmu lataa mallin' ja
varoitus ensimmäisen pyynnön hitaudesta.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:04:11 +03:00
4d9c51a86f .gitignore: *.db — ajonaikaiset tietokannat pois versionhallinnasta
nodes.db muuttui jatkuvasti ja esti deployn.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:59:35 +03:00
66d1e8c4b1 Ollama-kutsut hubin kautta: ei mixed content HTTPS-sivulla
Lisätty GET /api/v1/ollama/tags proxy-endpoint hubiin.
Poistettu suorat http://hostname:11434 -kutsut frontendistä.
Hub välittää Ollama-kutsut sisäisessä Docker-verkossa.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:57:48 +03:00
2eeac255f6 Piilotetut paneelit tavoitettavissa hashilla: #network, #laskentaverkko, #codelab
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:52:57 +03:00
6097cfc263 Ylimääräiset rönsyt karsittu. Playground suht ok. 2026-04-07 08:51:47 +03:00
8aed9f97a2 Puhuvat päät ja simulaatio-nappi piilotettu (koodi säilytetty)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:45:39 +03:00
c0ccd76a4c v0.2.1: Ollama-integraatio, pipeline, prompt-editori
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:41:28 +03:00
d2edb38879 Alaotsikko: 'Hajautettu WebGPU Laskentaverkko' → 'AI-ohjelmistokehitystiimi'
Simulaatio-viittaukset poistettu näkyvistä. Käännökset päivitetty.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:40:22 +03:00
2755794554 Agenttien valinta: klikkaus = yksi, Shift+klikkaus = multi-select
Normaali klikkaus valitsee yhden agentin (poistaa muut valinnat).
Shift+klikkaus lisää/poistaa agentin valinnasta yhteistä promptia varten.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:36:52 +03:00
dbb37b3c60 Laskentaverkko ja Koodilaboratorio piilotettu, Agents oletuksena
Simulaatio-välilehdet piilotettu display:none:lla (koodi säilytetty).
Agents & CLI on nyt oletusvälilehti.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:34:57 +03:00
0e7497b627 Oletuspromptit agentille + klikattavat pipeline-vaiheet
- Jokaisella agentilla on nyt oletusprompt joka näkyy heti modalissa
- Muokatut promptit tallentuvat localStorageen
- Pipeline-vaiherivin (✓ Suunnittelu → ✓ models.py → ...) klikkaus
  avaa modalin jossa näkyy kyseisen vaiheen prompti + tulos
- 📋 Näytä prompti -nappi näkyy aina kun agentti on valittu

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:30:56 +03:00
14 changed files with 836 additions and 256 deletions

3
.gitignore vendored
View File

@@ -34,3 +34,6 @@ Cargo.lock
*.pdb *.pdb
# End of https://www.toptal.com/developers/gitignore/api/rust,linux # End of https://www.toptal.com/developers/gitignore/api/rust,linux
# Ajonaikaiset tietokannat
*.db

View File

@@ -5,7 +5,8 @@ RUN apt-get update && apt-get install -y \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
WORKDIR /app WORKDIR /app
COPY Cargo.toml Cargo.lock ./ COPY Cargo.toml ./
COPY Cargo.loc[k] ./
COPY hub/Cargo.toml hub/Cargo.toml COPY hub/Cargo.toml hub/Cargo.toml
COPY node/Cargo.toml node/Cargo.toml COPY node/Cargo.toml node/Cargo.toml
COPY native-node/Cargo.toml native-node/Cargo.toml COPY native-node/Cargo.toml native-node/Cargo.toml

View File

@@ -1,13 +1,12 @@
services: services:
# NVIDIA GPU -solmu # Ollama NVIDIA GPU:lla
native-node-nvidia: ollama-nvidia:
build: image: ollama/ollama:latest
context: . container_name: kipina-ollama
dockerfile: Dockerfile.native-node ports:
container_name: kipina-node-nvidia - "11434:11434"
environment: volumes:
- HUB_URL=wss://kipina.studio/ws - ollama-models:/root/.ollama
- ALLOCATED_GB=4
restart: unless-stopped restart: unless-stopped
deploy: deploy:
resources: resources:
@@ -16,6 +15,65 @@ services:
- driver: nvidia - driver: nvidia
count: all count: all
capabilities: [gpu] capabilities: [gpu]
networks:
default:
aliases:
- ollama
profiles:
- nvidia
# Ollama AMD ROCm GPU:lla
ollama-amd:
image: ollama/ollama:rocm
container_name: kipina-ollama
ports:
- "11434:11434"
volumes:
- ollama-models:/root/.ollama
restart: unless-stopped
devices:
- /dev/kfd:/dev/kfd
- /dev/dri:/dev/dri
group_add:
- video
- render
networks:
default:
aliases:
- ollama
profiles:
- amd
# Ollama CPU:lla
ollama-cpu:
image: ollama/ollama:latest
container_name: kipina-ollama
ports:
- "11434:11434"
volumes:
- ollama-models:/root/.ollama
restart: unless-stopped
networks:
default:
aliases:
- ollama
profiles:
- cpu
# NVIDIA GPU -solmu
native-node-nvidia:
build:
context: .
dockerfile: Dockerfile.native-node
container_name: kipina-node-nvidia
environment:
- HUB_URL=wss://kipina.studio/ws
- OLLAMA_URL=http://ollama:11434
- OLLAMA_MODEL=qwen2.5-coder:7b
- ALLOCATED_GB=4
restart: unless-stopped
depends_on:
- ollama-nvidia
profiles: profiles:
- nvidia - nvidia
@@ -27,14 +85,12 @@ services:
container_name: kipina-node-amd container_name: kipina-node-amd
environment: environment:
- HUB_URL=wss://kipina.studio/ws - HUB_URL=wss://kipina.studio/ws
- OLLAMA_URL=http://ollama:11434
- OLLAMA_MODEL=qwen2.5-coder:7b
- ALLOCATED_GB=4 - ALLOCATED_GB=4
restart: unless-stopped restart: unless-stopped
devices: depends_on:
- /dev/kfd:/dev/kfd - ollama-amd
- /dev/dri:/dev/dri
group_add:
- video
- render
profiles: profiles:
- amd - amd
@@ -46,7 +102,14 @@ services:
container_name: kipina-node-cpu container_name: kipina-node-cpu
environment: environment:
- HUB_URL=wss://kipina.studio/ws - HUB_URL=wss://kipina.studio/ws
- OLLAMA_URL=http://ollama:11434
- OLLAMA_MODEL=qwen2.5-coder:7b
- ALLOCATED_GB=2 - ALLOCATED_GB=2
restart: unless-stopped restart: unless-stopped
depends_on:
- ollama-cpu
profiles: profiles:
- cpu - cpu
volumes:
ollama-models:

View File

@@ -11,21 +11,19 @@ services:
# Käännetään aina käynnistyksen yhteydessä varmuuden vuoksi Wasm uusimmista koodeista, ja päälle pyöräytetään Hub! # Käännetään aina käynnistyksen yhteydessä varmuuden vuoksi Wasm uusimmista koodeista, ja päälle pyöräytetään Hub!
command: bash -c "cd node && wasm-pack build --release --target web --out-dir ../static/pkg && cd ../hub && cargo run" command: bash -c "cd node && wasm-pack build --release --target web --out-dir ../static/pkg && cd ../hub && cargo run"
# Ollama — LLM-inferenssi GPU:lla (NVIDIA/AMD/Apple) # Ollama — LLM-inferenssi
# NVIDIA: vaihda image → ollama/ollama:latest ja lisää deploy.resources (ks. README)
# CPU: vaihda image → ollama/ollama:latest ja poista devices
ollama: ollama:
image: ollama/ollama:latest image: ollama/ollama:rocm
container_name: kipina_ollama container_name: kipina_ollama
ports: ports:
- "11434:11434" - "11434:11434"
volumes: volumes:
- ollama-models:/root/.ollama - ollama-models:/root/.ollama
deploy: devices:
resources: - /dev/kfd
reservations: - /dev/dri
devices:
- driver: nvidia
count: all
capabilities: [gpu]
profiles: profiles:
- native - native
@@ -36,12 +34,11 @@ services:
dockerfile: Dockerfile.native-node dockerfile: Dockerfile.native-node
container_name: kipina_native_node container_name: kipina_native_node
environment: environment:
- HUB_URL=ws://agentic-poc:3000/ws - HUB_URL=wss://kipina.studio/ws
- OLLAMA_URL=http://ollama:11434 - OLLAMA_URL=http://ollama:11434
- OLLAMA_MODEL=qwen2.5-coder:7b - OLLAMA_MODEL=qwen2.5-coder:7b
- ALLOCATED_GB=4 - ALLOCATED_GB=4
depends_on: depends_on:
- agentic-poc
- ollama - ollama
profiles: profiles:
- native - native

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "hub" name = "hub"
version = "0.2.0" version = "0.2.4"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
@@ -16,3 +16,4 @@ futures = "0.3"
rusqlite = { version = "0.31", features = ["bundled"] } rusqlite = { version = "0.31", features = ["bundled"] }
chrono = "0.4" chrono = "0.4"
base64 = "0.22" base64 = "0.22"
reqwest = { version = "0.12", features = ["json"] }

Binary file not shown.

View File

@@ -386,6 +386,7 @@ async fn main() {
.route("/api/v1/chat/completions", axum::routing::post(api_chat_completions)) .route("/api/v1/chat/completions", axum::routing::post(api_chat_completions))
.route("/api/v1/model", axum::routing::post(api_change_model)) .route("/api/v1/model", axum::routing::post(api_change_model))
.route("/api/v1/hardware", get(api_hardware)) .route("/api/v1/hardware", get(api_hardware))
.route("/api/v1/ollama/tags", get(api_ollama_tags))
.route("/admin", get(admin_page)) .route("/admin", get(admin_page))
.nest_service("/", { .nest_service("/", {
let static_dir = std::env::var("STATIC_DIR").unwrap_or_else(|_| "../static".to_string()); let static_dir = std::env::var("STATIC_DIR").unwrap_or_else(|_| "../static".to_string());
@@ -960,6 +961,20 @@ struct ChatCompletionResponse {
tokens_generated: u64, tokens_generated: u64,
} }
async fn api_ollama_tags() -> axum::response::Response {
let ollama_url = std::env::var("OLLAMA_URL").unwrap_or_else(|_| "http://ollama:11434".to_string());
match reqwest::get(format!("{}/api/tags", ollama_url)).await {
Ok(resp) => {
if let Ok(body) = resp.json::<serde_json::Value>().await {
axum::Json(body).into_response()
} else {
axum::Json(serde_json::json!({ "models": [] })).into_response()
}
}
Err(_) => axum::Json(serde_json::json!({ "models": [] })).into_response(),
}
}
async fn api_hardware( async fn api_hardware(
axum::extract::State(state): axum::extract::State<Arc<AppState>>, axum::extract::State(state): axum::extract::State<Arc<AppState>>,
) -> axum::response::Response { ) -> axum::response::Response {
@@ -969,17 +984,34 @@ async fn api_hardware(
s.get("node_type").and_then(|v| v.as_str()) == Some("native") s.get("node_type").and_then(|v| v.as_str()) == Some("native")
}); });
let (vram_mb, gpu_name, ram_mb) = if let Some(s) = native { let (mut vram_mb, mut gpu_name, ram_mb) = if let Some(s) = native {
let gpus = s.get("gpus").and_then(|v| v.as_array()); let gpus = s.get("gpus").and_then(|v| v.as_array());
let gpu = gpus.and_then(|g| g.first()); let gpu = gpus.and_then(|g| g.first());
let vram = gpu.and_then(|g| g.get("vram_total_mb")).and_then(|v| v.as_u64()).unwrap_or(0); let vram = gpu.and_then(|g| g.get("vram_total_mb")).and_then(|v| v.as_u64()).unwrap_or(0);
let name = gpu.and_then(|g| g.get("name")).and_then(|v| v.as_str()).unwrap_or("?"); let name = gpu.and_then(|g| g.get("name")).and_then(|v| v.as_str()).unwrap_or("").to_string();
let ram = s.get("system").and_then(|v| v.get("ram_total_mb")).and_then(|v| v.as_u64()).unwrap_or(0); let ram = s.get("system").and_then(|v| v.get("ram_total_mb")).and_then(|v| v.as_u64()).unwrap_or(0);
(vram, name.to_string(), ram) (vram, name, ram)
} else { } else {
(0, "ei natiivisolmua".to_string(), 0) (0, String::new(), 0)
}; };
// Fallback: kysytään Ollamalta onko malleja ladattu (= Ollama on käynnissä)
if vram_mb == 0 {
let ollama_url = std::env::var("OLLAMA_URL").unwrap_or_else(|_| "http://ollama:11434".to_string());
if let Ok(resp) = reqwest::get(format!("{}/api/tags", ollama_url)).await {
if let Ok(body) = resp.json::<serde_json::Value>().await {
let models = body["models"].as_array().map(|a| a.len()).unwrap_or(0);
if models > 0 {
gpu_name = "Ollama (GPU/CPU)".to_string();
// Natiivisolmun RAM fallbackina
vram_mb = if ram_mb > 0 { ram_mb } else { 0 };
}
}
}
}
if gpu_name.is_empty() { gpu_name = "ei natiivisolmua".to_string(); }
axum::Json(serde_json::json!({ axum::Json(serde_json::json!({
"gpu_name": gpu_name, "gpu_name": gpu_name,
"vram_mb": vram_mb, "vram_mb": vram_mb,

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "native-node" name = "native-node"
version = "0.2.0" version = "0.2.2"
edition = "2024" edition = "2024"
[dependencies] [dependencies]

View File

@@ -8,17 +8,47 @@ pub struct LlmEngine {
} }
impl LlmEngine { impl LlmEngine {
pub fn load() -> Result<Self, String> { pub async fn load() -> Result<Self, String> {
let ollama_url = std::env::var("OLLAMA_URL").unwrap_or_else(|_| "http://localhost:11434".to_string());
let model = std::env::var("OLLAMA_MODEL").unwrap_or_else(|_| "qwen2.5-coder:7b".to_string()); let model = std::env::var("OLLAMA_MODEL").unwrap_or_else(|_| "qwen2.5-coder:7b".to_string());
tracing::info!("Ollama backend: {} | malli: {}", ollama_url, model);
let client = reqwest::Client::builder() let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(600)) .timeout(std::time::Duration::from_secs(600))
.connect_timeout(std::time::Duration::from_secs(3))
.build() .build()
.map_err(|e| format!("HTTP client: {}", e))?; .map_err(|e| format!("HTTP client: {}", e))?;
// Jos OLLAMA_URL on asetettu, käytetään sitä suoraan
let ollama_url = if let Ok(url) = std::env::var("OLLAMA_URL") {
tracing::info!("Ollama backend (env): {}", url);
url
} else {
// Haistellaan Ollamaa tunnetuista osoitteista
let candidates = [
"http://localhost:11434",
"http://127.0.0.1:11434",
"http://ollama:11434",
"http://host.docker.internal:11434",
];
let mut found = None;
for url in &candidates {
let probe = reqwest::Client::builder()
.connect_timeout(std::time::Duration::from_secs(2))
.build().unwrap_or(client.clone());
if let Ok(resp) = probe.get(format!("{}/api/version", url)).send().await {
if resp.status().is_success() {
tracing::info!("Ollama löytyi osoitteesta: {}", url);
found = Some(url.to_string());
break;
}
}
}
found.unwrap_or_else(|| {
tracing::warn!("Ollamaa ei löytynyt — käytetään oletusta http://localhost:11434");
"http://localhost:11434".to_string()
})
};
tracing::info!("Ollama backend: {} | malli: {}", ollama_url, model);
Ok(LlmEngine { ollama_url, model: RefCell::new(model), client }) Ok(LlmEngine { ollama_url, model: RefCell::new(model), client })
} }
@@ -49,7 +79,7 @@ impl LlmEngine {
} }
pub async fn generate(&self, prompt: &str, max_tokens: usize) -> Result<GenerateResult, String> { pub async fn generate(&self, prompt: &str, max_tokens: usize) -> Result<GenerateResult, String> {
let system = "You are a coding assistant. Respond with ONLY code. No explanations, no markdown, no comments unless asked."; let system = "You are a coding assistant. Respond with ONLY code. Use proper newlines and indentation. No explanations, no markdown fences, no comments unless asked.";
let model = self.model.borrow().clone(); let model = self.model.borrow().clone();
let start = Instant::now(); let start = Instant::now();

View File

@@ -287,7 +287,7 @@ async fn main() {
// Ollama-backend // Ollama-backend
tracing::info!("Alustetaan Ollama-yhteyttä..."); tracing::info!("Alustetaan Ollama-yhteyttä...");
let llm = match inference::LlmEngine::load() { let llm = match inference::LlmEngine::load().await {
Ok(engine) => { Ok(engine) => {
// Varmistetaan malli (ollama pull) — odotetaan kunnes valmis // Varmistetaan malli (ollama pull) — odotetaan kunnes valmis
match engine.ensure_model().await { match engine.ensure_model().await {

View File

@@ -118,6 +118,27 @@ async fn run_ai_tensor_inference(difficulty: usize) -> String {
format!("PoC {} Matmul ({}x{}) >> {}", backend_name, active_workload_size, active_workload_size, result) format!("PoC {} Matmul ({}x{}) >> {}", backend_name, active_workload_size, active_workload_size, result)
} }
/// JS-exportti: tokenisoi tekstin ja palauttaa JSON-merkkijonon
/// Tokenizer ladataan IndexedDB:stä (täytyy olla ladattu aiemmin)
#[wasm_bindgen]
pub async fn tokenize_js(text: String) -> Result<String, JsValue> {
let cached_tok = storage::load_from_idb("tokenizer.json").await.unwrap_or(None);
let Some(bytes) = cached_tok else {
// Yritetään ladata verkosta
let resp = reqwest::get("https://huggingface.co/Qwen/Qwen2.5-Coder-0.5B/resolve/main/tokenizer.json").await
.map_err(|e| JsValue::from_str(&format!("Tokenizer-lataus epäonnistui: {}", e)))?;
let bytes = resp.bytes().await
.map_err(|e| JsValue::from_str(&format!("Tokenizer-lataus epäonnistui: {}", e)))?;
let _ = storage::save_to_idb("tokenizer.json", &bytes).await;
let tokenizer = tokenizers::Tokenizer::from_bytes(&bytes)
.map_err(|e| JsValue::from_str(&format!("Tokenizer-parsinta: {}", e)))?;
return Ok(tokenize_text(&tokenizer, &text).to_string());
};
let tokenizer = tokenizers::Tokenizer::from_bytes(&bytes)
.map_err(|e| JsValue::from_str(&format!("Tokenizer-parsinta: {}", e)))?;
Ok(tokenize_text(&tokenizer, &text).to_string())
}
/// Tokenisoi yhden tekstin ja palauttaa metriikat /// Tokenisoi yhden tekstin ja palauttaa metriikat
fn tokenize_text(tokenizer: &tokenizers::Tokenizer, text: &str) -> serde_json::Value { fn tokenize_text(tokenizer: &tokenizers::Tokenizer, text: &str) -> serde_json::Value {
let char_count = text.chars().count(); let char_count = text.chars().count();

View File

@@ -36,29 +36,13 @@ sanan osa, kokonainen sana tai välilyönti. Tokenisaatio tehdään
BPE-algoritmilla (Byte Pair Encoding) joka oppii yleisimmät BPE-algoritmilla (Byte Pair Encoding) joka oppii yleisimmät
merkkijonot harjoitusdatasta. merkkijonot harjoitusdatasta.
### Esimerkki: koodi
```
"print('Hello')" → [print] [(' ] [Hello] [')] = 4 tokenia
"tulosta('Hei')" → [tul] [osta] [(' ] [He] [i] [')] = 6 tokenia
```
Koodi tokenisoidaan tehokkaasti koska `print`, `def`, `return` yms.
ovat kokonaisia tokeneita. Suomenkielinen `tulosta` joudutaan pilkkomaan
osiin koska se ei esiinny harjoitusdatassa kokonaisena.
### Esimerkki: suomi vs. englanti ### Esimerkki: suomi vs. englanti
Sama lause kahdella kielellä Qwen2.5-Coder -tokenisaattorilla: Alla oikea tokenisointitulos Qwen2.5-Coder-tokenisaattorilla. Jokainen
värikoodattu lohko on yksi tokeni — huomaa miten suomi vaatii enemmän
tokeneita saman merkityksen välittämiseen:
| | Teksti | Tokenit | Määrä | Merkkejä/token | ![Tokenisointivertailu EN/FI](/images/tokenization-example.png)
|---|---|---|---|---|
| EN | The cat sat on the mat | [The] [ cat] [ sat] [ on] [ the] [ mat] | **6** | 3.7 |
| FI | Kissa istui matolla | [K] [issa] [ ist] [ui] [ mat] [olla] | **6** | 3.2 |
| EN | Distributed computing in the browser | [Dist] [ributed] [ computing] [ in] [ the] [ browser] | **6** | 6.0 |
| FI | Hajautettu laskenta selaimessa | [H] [aj] [au] [tettu] [ las] [kenta] [ sel] [aim] [essa] | **9** | 3.3 |
| EN | Write a function that sorts a list | [Write] [ a] [ function] [ that] [ sorts] [ a] [ list] | **7** | 5.0 |
| FI | Kirjoita funktio joka lajittelee listan | [K] [irj] [oita] [ funkt] [io] [ joka] [ laj] [ittel] [ee] [ listan] | **10** | 4.0 |
**Huomaa miten:** **Huomaa miten:**
- Englannin yleiset sanat (`the`, `in`, `a`, `function`) ovat kokonaisia tokeneita - Englannin yleiset sanat (`the`, `in`, `a`, `function`) ovat kokonaisia tokeneita

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

File diff suppressed because it is too large Load Diff