nomos+web: streaming, provider routing, event gap-fill, embedded UI; fix approval FK & session context
Agent (cmd/nomos): - Stream LLM tokens via NewStreaming; emit text_delta then final text. - OpenRouter provider routing: data_collection=deny (ZDR) + require_parameters; NOMOS_PROVIDER_SORT opt-in; Exacto via model suffix. - Multi-turn: reload session history into context; UI passes session id. - Fix agent_activity logging (agent_id/session_id) and mcpClient data race. Events (live control-room feed): - approval.created (mcp), approval.decided (api), execution.completed/failed (approved-action path), signal.raised/resolved + health.changed (scheduler, transition-gated). Fixes: - createApproval FK violation (reuse execution entity) — the agent's only write path; log the previously-swallowed errors. Web UI: - Embed web/dist via //go:embed (single binary); Dockerfile builds SPA into the Go stage; committed .gitkeep placeholder keeps backend-only builds green. - Caddy: Authentik-gated /agent/* -> nomos so the UI reaches the agent same-origin in production. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
155
web/src/App.svelte
Normal file
155
web/src/App.svelte
Normal file
@@ -0,0 +1,155 @@
|
||||
<script lang="ts">
|
||||
import Chat from '$lib/../pages/Chat.svelte'
|
||||
import Sessions from '$lib/../pages/Sessions.svelte'
|
||||
import { newChat } from '$lib/stores/chat'
|
||||
import { onMount } from 'svelte'
|
||||
import { slide } from 'svelte/transition'
|
||||
|
||||
let page = $state('chat')
|
||||
let drawerOpen = $state(false)
|
||||
|
||||
onMount(() => {
|
||||
function sync() {
|
||||
page = location.hash.slice(2) || 'chat'
|
||||
}
|
||||
sync()
|
||||
window.addEventListener('hashchange', sync)
|
||||
return () => window.removeEventListener('hashchange', sync)
|
||||
})
|
||||
|
||||
function navigate(p: string) {
|
||||
location.hash = '#/' + p
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="app">
|
||||
<nav class="sidebar">
|
||||
<div class="logo">Oikos</div>
|
||||
<button class="nav-btn" onclick={() => { newChat(); navigate('chat') }}>
|
||||
<span class="nav-icon">✚</span>
|
||||
<span>New</span>
|
||||
</button>
|
||||
<button class="nav-btn" class:active={page === 'chat'} onclick={() => navigate('chat')}>
|
||||
<span class="nav-icon">💬</span>
|
||||
<span>Chat</span>
|
||||
</button>
|
||||
<button class="nav-btn" class:active={page === 'sessions'} onclick={() => navigate('sessions')}>
|
||||
<span class="nav-icon">📋</span>
|
||||
<span>Sessions</span>
|
||||
</button>
|
||||
|
||||
<div class="spacer"></div>
|
||||
|
||||
<button class="drawer-toggle" onclick={() => drawerOpen = !drawerOpen}>
|
||||
Chat {drawerOpen ? '▼' : '▲'}
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<main class="main">
|
||||
{#if page === 'chat'}
|
||||
<Chat />
|
||||
{:else if page === 'sessions'}
|
||||
<Sessions />
|
||||
{:else}
|
||||
<Chat />
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
{#if drawerOpen}
|
||||
<aside class="drawer" transition:slide={{ axis: 'x' }}>
|
||||
<Chat />
|
||||
</aside>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.app {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 56px;
|
||||
background: var(--bg-surface);
|
||||
border-right: 1px solid var(--border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0.75rem 0;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
color: var(--accent-blue);
|
||||
margin-bottom: 0.5rem;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
padding: 0.5rem;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--text-muted);
|
||||
font-family: inherit;
|
||||
font-size: 0.625rem;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
width: 44px;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
.nav-btn:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.nav-btn.active {
|
||||
background: var(--bg-active);
|
||||
color: var(--accent-blue);
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.drawer-toggle {
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--text-muted);
|
||||
font-family: inherit;
|
||||
font-size: 0.625rem;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.drawer-toggle:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.drawer {
|
||||
width: 380px;
|
||||
border-left: 1px solid var(--border);
|
||||
background: var(--bg);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user