Compare commits
8 Commits
66d1e8c4b1
...
de1cf009fa
| Author | SHA1 | Date | |
|---|---|---|---|
| de1cf009fa | |||
| 060f36f479 | |||
| e2ec0fa43d | |||
| 8752c0f465 | |||
| 8c95282654 | |||
| a1bc1af646 | |||
| 6b27cbbade | |||
| 4d9c51a86f |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -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
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "hub"
|
name = "hub"
|
||||||
version = "0.2.1"
|
version = "0.2.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
Binary file not shown.
@@ -984,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,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "native-node"
|
name = "native-node"
|
||||||
version = "0.2.1"
|
version = "0.2.2"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@@ -388,7 +388,7 @@
|
|||||||
font-family: 'Courier New', Courier, monospace;
|
font-family: 'Courier New', Courier, monospace;
|
||||||
font-size:14px;
|
font-size:14px;
|
||||||
color:var(--success-color);
|
color:var(--success-color);
|
||||||
height:500px;
|
height: clamp(200px, 35vh, 500px);
|
||||||
overflow-y:auto;
|
overflow-y:auto;
|
||||||
text-align:left;
|
text-align:left;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
@@ -629,7 +629,7 @@
|
|||||||
font-family: 'Courier New', monospace;
|
font-family: 'Courier New', monospace;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
min-height: 60px;
|
min-height: 40px;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
.agent-prompt-editor textarea:focus { border-color: var(--accent-color); }
|
.agent-prompt-editor textarea:focus { border-color: var(--accent-color); }
|
||||||
@@ -688,11 +688,26 @@
|
|||||||
#code-input-container { flex-direction: column !important; }
|
#code-input-container { flex-direction: column !important; }
|
||||||
#code-send-btn { width: 100%; margin-top: 5px; }
|
#code-send-btn { width: 100%; margin-top: 5px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Responsiivinen korkeus */
|
||||||
|
@media (max-height: 900px) {
|
||||||
|
.terminal-panel { height: clamp(150px, 25vh, 250px); }
|
||||||
|
.agent-prompt-editor textarea { min-height: 30px; }
|
||||||
|
.container > div:first-child { margin-bottom: 6px; }
|
||||||
|
.container h1 { font-size: 22px; }
|
||||||
|
.container .sub { font-size: 12px; }
|
||||||
|
.avatar-card { padding: 6px 4px; }
|
||||||
|
.avatar-card img { width: 50px; height: 50px; margin-bottom: 4px; }
|
||||||
|
}
|
||||||
|
@media (min-height: 1200px) {
|
||||||
|
.terminal-panel { height: clamp(350px, 40vh, 600px); }
|
||||||
|
.agent-prompt-editor textarea { min-height: 80px; }
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 20px;">
|
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 10px;">
|
||||||
<div>
|
<div>
|
||||||
<h1 style="margin-bottom:0;" data-i18n="main_title"><span style="color:#ff6b00">Kipinä</span> <span>Agentic Playground</span></h1>
|
<h1 style="margin-bottom:0;" data-i18n="main_title"><span style="color:#ff6b00">Kipinä</span> <span>Agentic Playground</span></h1>
|
||||||
<p class="sub" style="margin-bottom:0;"><span data-i18n="main_subtitle">AI-ohjelmistokehitystiimi</span> · <span id="hub-version" style="color:#58a6ff">-</span></p>
|
<p class="sub" style="margin-bottom:0;"><span data-i18n="main_subtitle">AI-ohjelmistokehitystiimi</span> · <span id="hub-version" style="color:#58a6ff">-</span></p>
|
||||||
@@ -1068,7 +1083,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Prompt Editor -->
|
<!-- Prompt Editor -->
|
||||||
<div class="agent-prompt-editor" id="agent-prompt-editor" style="margin-top:20px;">
|
<div class="agent-prompt-editor" id="agent-prompt-editor" style="margin-top:10px;">
|
||||||
<div class="agent-prompt-label">
|
<div class="agent-prompt-label">
|
||||||
<strong id="agent-prompt-name">—</strong>
|
<strong id="agent-prompt-name">—</strong>
|
||||||
<span id="agent-prompt-saved" style="color:var(--success-color);opacity:0;transition:opacity 0.3s">Tallennettu</span>
|
<span id="agent-prompt-saved" style="color:var(--success-color);opacity:0;transition:opacity 0.3s">Tallennettu</span>
|
||||||
@@ -1483,7 +1498,7 @@ IMPORTANT: Check consistency
|
|||||||
2. Dockerfile deps vs imports: all imports covered
|
2. Dockerfile deps vs imports: all imports covered
|
||||||
3. docker-compose ports: match EXPOSE
|
3. docker-compose ports: match EXPOSE
|
||||||
4. Test imports: match actual module names
|
4. Test imports: match actual module names
|
||||||
5. pyproject.toml deps: cover all imports` },
|
5. pyproject.toml deps: cover all imports (but NOT stdlib like sqlite3, os, sys, json)` },
|
||||||
tester: { label: 'DevOps', prompt: `docker-compose.yml: services, volumes, port mappings
|
tester: { label: 'DevOps', prompt: `docker-compose.yml: services, volumes, port mappings
|
||||||
|
|
||||||
EXAMPLE:
|
EXAMPLE:
|
||||||
@@ -2256,6 +2271,10 @@ IMPORTANT: Include get_db() dependency for FastAPI` },
|
|||||||
const card = document.getElementById(cardId);
|
const card = document.getElementById(cardId);
|
||||||
if (!card) return;
|
if (!card) return;
|
||||||
const files = projectFiles[cardId];
|
const files = projectFiles[cardId];
|
||||||
|
if (!files || Object.keys(files).length === 0) {
|
||||||
|
alert('Tiedostot eivät ole enää muistissa — aja pipeline uudelleen.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// CRC-32 laskenta ZIP-tiedostoille
|
// CRC-32 laskenta ZIP-tiedostoille
|
||||||
function crc32(bytes) {
|
function crc32(bytes) {
|
||||||
@@ -2276,7 +2295,7 @@ IMPORTANT: Include get_db() dependency for FastAPI` },
|
|||||||
|
|
||||||
for (const [name, content] of entries) {
|
for (const [name, content] of entries) {
|
||||||
const nameBytes = new TextEncoder().encode(name);
|
const nameBytes = new TextEncoder().encode(name);
|
||||||
const contentBytes = new TextEncoder().encode(content);
|
const contentBytes = new TextEncoder().encode(content || '');
|
||||||
const crc = crc32(contentBytes);
|
const crc = crc32(contentBytes);
|
||||||
|
|
||||||
// Local file header
|
// Local file header
|
||||||
@@ -2414,10 +2433,12 @@ Project: ${task}`;
|
|||||||
name = "projectname"
|
name = "projectname"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
dependencies = ["fastapi", "uvicorn"]
|
dependencies = ["fastapi", "uvicorn", "sqlalchemy"]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
start = "uvicorn main:app --reload"`;
|
start = "uvicorn main:app --reload"
|
||||||
|
|
||||||
|
IMPORTANT: Only list pip-installable packages. NEVER include Python stdlib modules like sqlite3, os, sys, json, typing, collections, etc.`;
|
||||||
} else if (file.name === 'requirements.txt') {
|
} else if (file.name === 'requirements.txt') {
|
||||||
extraInstructions = '\nList one dependency per line. No version pins unless necessary.';
|
extraInstructions = '\nList one dependency per line. No version pins unless necessary.';
|
||||||
}
|
}
|
||||||
@@ -2608,7 +2629,7 @@ Files: ${Object.keys(generatedFiles).join(', ')}`;
|
|||||||
3. docker-compose ports: ✓ OK / ✗ problem description
|
3. docker-compose ports: ✓ OK / ✗ problem description
|
||||||
4. README commands: ✓ OK / ✗ problem description
|
4. README commands: ✓ OK / ✗ problem description
|
||||||
5. Test imports: ✓ OK / ✗ problem description
|
5. Test imports: ✓ OK / ✗ problem description
|
||||||
6. pyproject.toml deps: ✓ OK / ✗ problem description
|
6. pyproject.toml deps: ✓ OK / ✗ problem (NEVER list stdlib: sqlite3, os, sys, json, typing)
|
||||||
|
|
||||||
EXAMPLE output:
|
EXAMPLE output:
|
||||||
1. Dockerfile COPY: ✓ OK — copies main.py and models.py which both exist
|
1. Dockerfile COPY: ✓ OK — copies main.py and models.py which both exist
|
||||||
@@ -2838,7 +2859,8 @@ ${generatedFiles['Dockerfile'] || '(puuttuu)'}`;
|
|||||||
clearInterval(spinTimer);
|
clearInterval(spinTimer);
|
||||||
pullLine.remove();
|
pullLine.remove();
|
||||||
if (hubData.status === 'ok') {
|
if (hubData.status === 'ok') {
|
||||||
termLog(` <span style="color:#3fb950">✓</span> ${selected.name} ladattu ja aktiivinen`, '#3fb950');
|
termLog(` <span style="color:#3fb950">✓</span> ${selected.name} valittu — natiivisolmu lataa mallin`, '#3fb950');
|
||||||
|
termLog(` <span style="color:#8b949e">Ensimmäinen pyyntö voi kestää pidempään jos mallia ei ole ladattu</span>`);
|
||||||
ollamaModels.forEach(m => m.default = false);
|
ollamaModels.forEach(m => m.default = false);
|
||||||
selected.default = true;
|
selected.default = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -2876,11 +2898,16 @@ ${generatedFiles['Dockerfile'] || '(puuttuu)'}`;
|
|||||||
const btn = document.getElementById('agent-compute-btn');
|
const btn = document.getElementById('agent-compute-btn');
|
||||||
const wasmLoaded = btn?.dataset.state === 'ready';
|
const wasmLoaded = btn?.dataset.state === 'ready';
|
||||||
if (hw.gpu_name && hw.gpu_name !== 'ei natiivisolmua') {
|
if (hw.gpu_name && hw.gpu_name !== 'ei natiivisolmua') {
|
||||||
termLog(` <span style="color:#8b949e">GPU: ${hw.gpu_name} | VRAM: ${Math.round((hw.vram_mb||0)/1024)} GB</span>`);
|
const vram = hw.vram_mb ? ` | VRAM: ${Math.round(hw.vram_mb/1024)} GB` : '';
|
||||||
|
termLog(` <span style="color:#8b949e">${hw.gpu_name}${vram}</span>`);
|
||||||
|
}
|
||||||
|
if (loadedNames.length > 0) {
|
||||||
|
termLog(` <span style="color:#3fb950">Ollama: ${loadedNames.length} mallia ladattu</span>`);
|
||||||
}
|
}
|
||||||
termLog(' Mallit <span style="color:#8b949e">(kpn load <numero>)</span>:', '#c9d1d9');
|
termLog(' Mallit <span style="color:#8b949e">(kpn load <numero>)</span>:', '#c9d1d9');
|
||||||
for (const m of allModels) {
|
for (const m of allModels) {
|
||||||
const loaded = (m.id === '1' && wasmLoaded) || loadedNames.some(n => m.name.includes(n) || n.includes(m.name.split(':')[1]));
|
const nameBase = m.name.split(':')[1]; // "7b", "1.5b" etc
|
||||||
|
const loaded = (m.id === '1' && wasmLoaded) || loadedNames.some(n => n.includes(nameBase));
|
||||||
const status = loaded ? ' <span style="color:#3fb950">✓ ladattu</span>' : '';
|
const status = loaded ? ' <span style="color:#3fb950">✓ ladattu</span>' : '';
|
||||||
termLog(` <span style="color:#58a6ff">${m.id}</span> ${m.name} <span style="color:#8b949e">${m.size} | ${m.type}</span>${status}`);
|
termLog(` <span style="color:#58a6ff">${m.id}</span> ${m.name} <span style="color:#8b949e">${m.size} | ${m.type}</span>${status}`);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user