feat(platform): add new services, tools, tests and crews modules
New router intelligence modules (26 files): alert_ingest/store, audit_store, architecture_pressure, backlog_generator/store, cost_analyzer, data_governance, dependency_scanner, drift_analyzer, incident_* (5 files), llm_enrichment, platform_priority_digest, provider_budget, release_check_runner, risk_* (6 files), signature_state_store, sofiia_auto_router, tool_governance New services: - sofiia-console: Dockerfile, adapters/, monitor/nodes/ops/voice modules, launchd, react static - memory-service: integration_endpoints, integrations, voice_endpoints, static UI - aurora-service: full app suite (analysis, job_store, orchestrator, reporting, schemas, subagents) - sofiia-supervisor: new supervisor service - aistalk-bridge-lite: Telegram bridge lite - calendar-service: CalDAV calendar service with reminders - mlx-stt-service / mlx-tts-service: Apple Silicon speech services - binance-bot-monitor: market monitor service - node-worker: STT/TTS memory providers New tools (9): agent_email, browser_tool, contract_tool, observability_tool, oncall_tool, pr_reviewer_tool, repo_tool, safe_code_executor, secure_vault New crews: agromatrix_crew (10 modules: depth_classifier, doc_facts, doc_focus, farm_state, light_reply, llm_factory, memory_manager, proactivity, reflection_engine, session_context, style_adapter, telemetry) Tests: 85+ test files for all new modules Made-with: Cursor
This commit is contained in:
10
services/memory-service/static/sofiia-avatar.svg
Normal file
10
services/memory-service/static/sofiia-avatar.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200">
|
||||
<defs>
|
||||
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#f093fb"/>
|
||||
<stop offset="100%" style="stop-color:#f5576c"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<circle cx="100" cy="100" r="95" fill="url(#grad)"/>
|
||||
<text x="100" y="130" font-family="Arial, sans-serif" font-size="100" font-weight="bold" fill="white" text-anchor="middle">S</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 475 B |
1141
services/memory-service/static/sofiia-ui.html
Normal file
1141
services/memory-service/static/sofiia-ui.html
Normal file
File diff suppressed because it is too large
Load Diff
206
services/memory-service/static/test-ui.html
Normal file
206
services/memory-service/static/test-ui.html
Normal file
@@ -0,0 +1,206 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="uk">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sofiia Test</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, sans-serif; background: #1a1a1f; color: #e0e0e0; padding: 20px; }
|
||||
.container { max-width: 800px; margin: 0 auto; }
|
||||
h1 { text-align: center; margin-bottom: 20px; color: #c9a87c; }
|
||||
#chat { height: 400px; overflow-y: auto; background: #27272a; border-radius: 12px; padding: 15px; margin-bottom: 15px; }
|
||||
.msg { margin-bottom: 12px; padding: 10px 14px; border-radius: 12px; max-width: 85%; }
|
||||
.user { background: rgba(201,168,124,0.2); margin-left: auto; }
|
||||
.ai { background: rgba(255,255,255,0.05); }
|
||||
.input-row { display: flex; gap: 10px; }
|
||||
input { flex: 1; padding: 12px; background: #27272a; border: 1px solid #3f3f46; border-radius: 8px; color: #e0e0e0; font-size: 16px; }
|
||||
input:focus { outline: none; border-color: #c9a87c; }
|
||||
button { padding: 12px 20px; background: linear-gradient(135deg, #c9a87c, #8b7355); border: none; border-radius: 8px; color: #1a1a1f; cursor: pointer; font-weight: 500; }
|
||||
button:hover { opacity: 0.9; }
|
||||
#voiceBtn { width: 50px; padding: 12px; }
|
||||
#voiceBtn.recording { background: #ef4444; animation: pulse 1s infinite; }
|
||||
@keyframes pulse { 0%,100% { box-shadow: 0 0 0 0 rgba(239,68,68,0.4); } 50% { box-shadow: 0 0 0 10px rgba(239,68,68,0); } }
|
||||
#status { text-align: center; padding: 10px; color: #888; font-size: 14px; }
|
||||
.controls { display: flex; gap: 15px; margin-bottom: 15px; flex-wrap: wrap; }
|
||||
select { padding: 8px; background: #27272a; border: 1px solid #3f3f46; color: #e0e0e0; border-radius: 6px; }
|
||||
label { display: flex; align-items: center; gap: 5px; font-size: 14px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>SOFIIA</h1>
|
||||
<p class="subtitle">CTO DAARION | AI Architect</p>
|
||||
</div>
|
||||
<div id="status" style="text-align:center;padding:8px;color:#888;font-size:14px;">Перевірка сервісів...</div>
|
||||
<div class="controls" style="display:flex;gap:15px;margin-bottom:15px;flex-wrap:wrap;padding:0 20px;">
|
||||
<select id="model" style="padding:8px;background:#27272a;border:1px solid #3f3f46;color:#e0e0e0;border-radius:6px;">
|
||||
<option value="glm-4.7-flash:32k">GLM-4.7 Flash 32K</option>
|
||||
<option value="mistral-nemo:12b">Mistral Nemo 12B</option>
|
||||
<option value="qwen3-coder:30b">Qwen3 Coder 30B</option>
|
||||
<option value="deepseek-r1:70b">DeepSeek R1 70B</option>
|
||||
</select>
|
||||
<label style="display:flex;align-items:center;gap:5px;font-size:14px;"><input type="checkbox" id="autoSpeak" checked> 🔊 TTS</label>
|
||||
<label style="display:flex;align-items:center;gap:5px;font-size:14px;"><input type="checkbox" id="vMode"> 🎙️ Безперервний</label>
|
||||
</div>
|
||||
<div id="chat"></div>
|
||||
<div class="input-row">
|
||||
<input type="text" id="input" placeholder="Напишіть повідомлення..." onkeypress="if(event.key==='Enter')send()">
|
||||
<button id="voiceBtn" onclick="toggleVoice()">🎤</button>
|
||||
<button onclick="send()">Надіслати</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const OLLAMA = 'http://localhost:11434';
|
||||
const SERVICE = 'http://localhost:8001';
|
||||
const OLLAMA = 'http://localhost:11434';
|
||||
let history = [];
|
||||
let recording = false;
|
||||
let voiceMode = false;
|
||||
let mediaRecorder, chunks;
|
||||
|
||||
const SYSTEM_PROMPT = 'Ти Sofiia — Chief AI Architect та Technical Sovereign екосистеми DAARION.city. Координуєш R&D, архітектуру, безпеку та еволюцію платформи. Маєш доступ до нод: NODA1 (production), NODA2 (development), NODA3 (AI/ML). Відповідай українською, професійно, структуровано. Користувайся своєю Identity з AGENTS.md.';
|
||||
|
||||
async function check() {
|
||||
try {
|
||||
const r = await fetch(SERVICE + '/health');
|
||||
const d = await r.json();
|
||||
document.getElementById('status').textContent = '✓ Memory: ' + (d.vector_store?.memories?.points_count || 0) + ' | Voice: OK';
|
||||
} catch(e) {
|
||||
document.getElementById('status').textContent = '✗ Помилка: ' + e.message;
|
||||
}
|
||||
}
|
||||
check();
|
||||
setInterval(check, 30000);
|
||||
|
||||
function add(text, sender) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'msg ' + sender;
|
||||
div.textContent = text;
|
||||
document.getElementById('chat').appendChild(div);
|
||||
document.getElementById('chat').scrollTop = 9999;
|
||||
}
|
||||
|
||||
async function send() {
|
||||
const input = document.getElementById('input');
|
||||
const text = input.value.trim();
|
||||
if (!text) return;
|
||||
|
||||
input.value = '';
|
||||
add(text, 'user');
|
||||
|
||||
const model = document.getElementById('model').value;
|
||||
history.push({role: 'user', content: text});
|
||||
|
||||
try {
|
||||
const r = await fetch(`${OLLAMA}/api/chat`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
model: model,
|
||||
messages: [{role: 'system', content: SYSTEM_PROMPT}, ...history.slice(-8)],
|
||||
stream: false
|
||||
})
|
||||
});
|
||||
|
||||
const d = await r.json();
|
||||
const reply = d.message?.content || 'Помилка';
|
||||
add(reply, 'ai');
|
||||
history.push({role: 'assistant', content: reply});
|
||||
|
||||
if (document.getElementById('autoSpeak').checked) {
|
||||
await speak(reply);
|
||||
}
|
||||
|
||||
// Auto-restart listening in voice mode
|
||||
if (voiceMode && !recording) {
|
||||
setTimeout(() => startListening(), 500);
|
||||
}
|
||||
} catch(e) {
|
||||
add('Помилка: ' + e.message, 'ai');
|
||||
}
|
||||
}
|
||||
|
||||
async function speak(text) {
|
||||
try {
|
||||
const r = await fetch(`${SERVICE}/voice/tts`, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({text: text.substring(0, 300)})
|
||||
});
|
||||
if (r.ok) {
|
||||
const blob = await r.blob();
|
||||
const audio = new Audio(URL.createObjectURL(blob));
|
||||
await audio.play();
|
||||
}
|
||||
} catch(e) {
|
||||
console.log('TTS error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function startListening() {
|
||||
if (recording) return;
|
||||
|
||||
const stream = await navigator.mediaDevices.getUserMedia({audio: true});
|
||||
mediaRecorder = new MediaRecorder(stream);
|
||||
chunks = [];
|
||||
|
||||
mediaRecorder.ondataavailable = e => chunks.push(e.data);
|
||||
mediaRecorder.onstop = async () => {
|
||||
stream.getTracks().forEach(t => t.stop());
|
||||
const blob = new Blob(chunks, {type: 'audio/webm'});
|
||||
const fd = new FormData();
|
||||
fd.append('audio', blob, 'audio.webm');
|
||||
|
||||
document.getElementById('status').textContent = '🔄 Розпізнаю...';
|
||||
|
||||
try {
|
||||
const r = await fetch(`${SERVICE}/voice/stt`, {method: 'POST', body: fd});
|
||||
const d = await r.json();
|
||||
if (d.text) {
|
||||
document.getElementById('input').value = d.text;
|
||||
send();
|
||||
} else {
|
||||
document.getElementById('status').textContent = '✓ Готовий';
|
||||
if (voiceMode) startListening();
|
||||
}
|
||||
} catch(e) {
|
||||
console.log('STT error:', e);
|
||||
document.getElementById('status').textContent = '✓ Готовий';
|
||||
if (voiceMode) startListening();
|
||||
}
|
||||
};
|
||||
|
||||
mediaRecorder.start();
|
||||
recording = true;
|
||||
document.getElementById('voiceBtn').classList.add('recording');
|
||||
document.getElementById('status').textContent = '🎙️ Слухаю...';
|
||||
|
||||
// Auto-stop after silence or 10 seconds
|
||||
setTimeout(() => {
|
||||
if (recording && mediaRecorder.state === 'recording') {
|
||||
mediaRecorder.stop();
|
||||
recording = false;
|
||||
document.getElementById('voiceBtn').classList.remove('recording');
|
||||
}
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
function toggleVoice() {
|
||||
voiceMode = !voiceMode;
|
||||
|
||||
if (voiceMode) {
|
||||
startListening();
|
||||
} else {
|
||||
if (recording && mediaRecorder) {
|
||||
mediaRecorder.stop();
|
||||
recording = false;
|
||||
document.getElementById('voiceBtn').classList.remove('recording');
|
||||
}
|
||||
document.getElementById('status').textContent = '✓ Голосовий режим вимкнено';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user