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
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/rust,linux
|
||||
|
||||
# Ajonaikaiset tietokannat
|
||||
*.db
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "hub"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2024"
|
||||
|
||||
[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")
|
||||
});
|
||||
|
||||
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 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 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);
|
||||
(vram, name.to_string(), ram)
|
||||
(vram, name, ram)
|
||||
} 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!({
|
||||
"gpu_name": gpu_name,
|
||||
"vram_mb": vram_mb,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "native-node"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -388,7 +388,7 @@
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size:14px;
|
||||
color:var(--success-color);
|
||||
height:500px;
|
||||
height: clamp(200px, 35vh, 500px);
|
||||
overflow-y:auto;
|
||||
text-align:left;
|
||||
white-space: pre-wrap;
|
||||
@@ -629,7 +629,7 @@
|
||||
font-family: 'Courier New', monospace;
|
||||
padding: 8px;
|
||||
resize: vertical;
|
||||
min-height: 60px;
|
||||
min-height: 40px;
|
||||
outline: none;
|
||||
}
|
||||
.agent-prompt-editor textarea:focus { border-color: var(--accent-color); }
|
||||
@@ -688,11 +688,26 @@
|
||||
#code-input-container { flex-direction: column !important; }
|
||||
#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>
|
||||
</head>
|
||||
<body>
|
||||
<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>
|
||||
<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>
|
||||
@@ -1068,7 +1083,7 @@
|
||||
</div>
|
||||
|
||||
<!-- 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">
|
||||
<strong id="agent-prompt-name">—</strong>
|
||||
<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
|
||||
3. docker-compose ports: match EXPOSE
|
||||
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
|
||||
|
||||
EXAMPLE:
|
||||
@@ -2256,6 +2271,10 @@ IMPORTANT: Include get_db() dependency for FastAPI` },
|
||||
const card = document.getElementById(cardId);
|
||||
if (!card) return;
|
||||
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
|
||||
function crc32(bytes) {
|
||||
@@ -2276,7 +2295,7 @@ IMPORTANT: Include get_db() dependency for FastAPI` },
|
||||
|
||||
for (const [name, content] of entries) {
|
||||
const nameBytes = new TextEncoder().encode(name);
|
||||
const contentBytes = new TextEncoder().encode(content);
|
||||
const contentBytes = new TextEncoder().encode(content || '');
|
||||
const crc = crc32(contentBytes);
|
||||
|
||||
// Local file header
|
||||
@@ -2414,10 +2433,12 @@ Project: ${task}`;
|
||||
name = "projectname"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = ["fastapi", "uvicorn"]
|
||||
dependencies = ["fastapi", "uvicorn", "sqlalchemy"]
|
||||
|
||||
[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') {
|
||||
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
|
||||
4. README commands: ✓ 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:
|
||||
1. Dockerfile COPY: ✓ OK — copies main.py and models.py which both exist
|
||||
@@ -2838,7 +2859,8 @@ ${generatedFiles['Dockerfile'] || '(puuttuu)'}`;
|
||||
clearInterval(spinTimer);
|
||||
pullLine.remove();
|
||||
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);
|
||||
selected.default = true;
|
||||
} else {
|
||||
@@ -2876,11 +2898,16 @@ ${generatedFiles['Dockerfile'] || '(puuttuu)'}`;
|
||||
const btn = document.getElementById('agent-compute-btn');
|
||||
const wasmLoaded = btn?.dataset.state === 'ready';
|
||||
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');
|
||||
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>' : '';
|
||||
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