feat(ai): refine chat UX and add AI roadmap view

This commit is contained in:
Brad Stein 2025-12-20 23:52:49 -03:00
parent 113cade1b2
commit 370007813d
4 changed files with 124 additions and 33 deletions

View File

@ -126,26 +126,26 @@ export function fallbackServices() {
name: "AI Chat",
category: "ai",
summary: "LLM chat (public beta)",
link: "/ai",
link: "/ai/chat",
host: "chat.ai.bstein.dev",
status: "live",
},
{
name: "AI Image",
category: "ai",
summary: "Visualization tool - Planned",
link: "/ai",
host: "draw.ai.bstein.dev",
status: "planned",
},
{
name: "AI Speech",
category: "ai",
summary: "Live Translation - Planned",
link: "/ai",
host: "talk.ai.bstein.dev",
status: "planned",
},
{
name: "AI Image",
category: "ai",
summary: "Visualization tool - Planned",
link: "/ai/roadmap",
host: "draw.ai.bstein.dev",
status: "planned",
},
{
name: "AI Speech",
category: "ai",
summary: "Live Translation - Planned",
link: "/ai/roadmap",
host: "talk.ai.bstein.dev",
status: "planned",
},
],
};
}

View File

@ -2,6 +2,7 @@ import { createRouter, createWebHistory } from "vue-router";
import HomeView from "./views/HomeView.vue";
import AboutView from "./views/AboutView.vue";
import AiView from "./views/AiView.vue";
import AiPlanView from "./views/AiPlanView.vue";
import MoneroView from "./views/MoneroView.vue";
export default createRouter({
@ -9,7 +10,9 @@ export default createRouter({
routes: [
{ path: "/", name: "home", component: HomeView },
{ path: "/about", name: "about", component: AboutView },
{ path: "/ai", name: "ai", component: AiView },
{ path: "/ai", redirect: "/ai/chat" },
{ path: "/ai/chat", name: "ai-chat", component: AiView },
{ path: "/ai/roadmap", name: "ai-roadmap", component: AiPlanView },
{ path: "/monero", name: "monero", component: MoneroView },
],
});

View File

@ -0,0 +1,89 @@
<template>
<div class="page">
<section class="card hero glass">
<div>
<p class="eyebrow">Atlas AI</p>
<h1>Roadmap</h1>
<p class="lede">
Chat is live today. Image generation and speech / translation will roll out next. This page tracks whats planned and
what hardware it will land on.
</p>
</div>
</section>
<section class="card grid">
<div class="track">
<div class="pill mono">AI Image</div>
<h3>Visualization</h3>
<p class="text">
Goal: small, fast image generation for diagrams, thumbnails, and mockups. Targeting Jetson nodes once stable. Output
will be gated to members only.
</p>
<ul>
<li>Models: open-source SD/FLUX variants distilled for 16GB GPUs.</li>
<li>Pipeline: upload prompt queued job signed URL in Nextcloud.</li>
<li>Status: planned (no UI yet).</li>
</ul>
</div>
<div class="track">
<div class="pill mono">AI Speech</div>
<h3>Voice + Translation</h3>
<p class="text">
Goal: low-latency ASR + TTS for meetings and media. Results should stream back into apps like Jitsi and Pegasus.
</p>
<ul>
<li>Models: whisper-style ASR, lightweight TTS with multilingual support.</li>
<li>Targets: titan-20/21 Jetsons for acceleration; fall back to CPU-only if needed.</li>
<li>Status: planned (no UI yet).</li>
</ul>
</div>
</section>
<section class="card">
<h2>Whats live now?</h2>
<p class="text">
Atlas AI chat is running on local GPU hardware at <code>chat.ai.bstein.dev</code>. The chat page streams responses and
reports latency per turn. As larger models come online on the Jetsons, the chat endpoint will be upgraded in-place.
</p>
<div class="pill mono">Next step: migrate chat to Jetsons when available</div>
</section>
</div>
</template>
<style scoped>
.page {
max-width: 1100px;
margin: 0 auto;
padding: 32px 22px 72px;
}
.grid {
display: grid;
gap: 16px;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
.track {
border: 1px solid var(--card-border);
border-radius: 12px;
padding: 16px;
background: rgba(255, 255, 255, 0.02);
display: flex;
flex-direction: column;
gap: 8px;
}
.text {
color: var(--text-muted);
}
ul {
margin: 0;
padding-left: 18px;
color: var(--text-muted);
}
.pill {
display: inline-block;
}
</style>

View File

@ -5,8 +5,8 @@
<p class="eyebrow">Atlas AI</p>
<h1>Chat</h1>
<p class="lede">
Lightweight LLM running on titan-24 (RTX 3080, 8GB). Anyone can chat without auth. The client streams responses and
shows round-trip latency for each turn.
Lightweight LLM running on local GPU accelerated hardware. Anyone can chat without auth. The client streams responses
and shows round-trip latency for each turn.
</p>
<div class="pill mono pill-live">Online</div>
</div>
@ -17,7 +17,7 @@
</div>
<div class="fact">
<span class="label mono">GPU</span>
<span class="value mono">titan-24 · 3080 (8GB)</span>
<span class="value mono">local GPU (dynamic)</span>
</div>
<div class="fact">
<span class="label mono">Endpoint</span>
@ -30,7 +30,7 @@
<div class="chat-window" ref="chatWindow">
<div v-for="(msg, idx) in messages" :key="idx" :class="['chat-row', msg.role]">
<div class="bubble" :class="{ streaming: msg.streaming }">
<div class="role mono">{{ msg.role === 'assistant' ? 'ai' : 'you' }}</div>
<div class="role mono">{{ msg.role === 'assistant' ? 'Atlas AI' : 'you' }}</div>
<p>{{ msg.content }}</p>
<div v-if="msg.streaming" class="meta mono typing">streaming</div>
<div v-else-if="msg.latency_ms" class="meta mono">{{ msg.latency_ms }} ms</div>
@ -48,25 +48,17 @@
v-model="draft"
placeholder="Ask anything about the lab or general topics..."
rows="3"
@keydown="handleKeydown"
:disabled="sending"
/>
<div class="actions">
<span class="hint mono">Shift+Enter for newline</span>
<span class="hint mono">Enter to send · Shift+Enter for newline</span>
<button class="primary" type="submit" :disabled="sending || !draft.trim()">
{{ sending ? "Sending..." : "Send" }}
</button>
</div>
</form>
</section>
<section class="card info-card">
<h2>Notes</h2>
<ul>
<li>Backend proxies requests to Ollama inside the cluster; no external calls are made.</li>
<li>Short-term context: the chat history in this page is sent each turn. Refresh clears it.</li>
<li>Future: swap in larger models on the Jetsons, add streaming and rate limits.</li>
</ul>
</section>
</div>
</template>
@ -80,7 +72,7 @@ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const messages = ref([
{
role: "assistant",
content: "Hi! I'm the Titan Lab assistant running on titan-24. How can I help?",
content: "Hi! I'm Atlas AI. How can I help?",
},
]);
const draft = ref("");
@ -162,6 +154,13 @@ async function typeReveal(entry, text) {
}
entry.streaming = false;
}
function handleKeydown(e) {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
}
</script>
<style scoped>