improve session activity and model display

This commit is contained in:
Michael committed 2026-06-30 19:19:49 +02:00
1 parent a7290039f6
commit f6793daed3
6 files changed
+65 -19

No files matched your search

+2 -1
View File
@@ -3,13 +3,14 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link <link
href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Press+Start+2P&display=swap" href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Press+Start+2P&display=swap"
rel="stylesheet" rel="stylesheet"
/> />
<title>Loki — Agent local</title> <title>Loki - Agent local</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="10" fill="#18181b"/>
<rect x="11" y="11" width="38" height="38" rx="7" fill="#ff5436" stroke="#ffffff" stroke-width="6"/>
<rect x="25" y="25" width="12" height="12" fill="#ffffff"/>
<path d="M18 54h38V16" fill="none" stroke="#ff5436" stroke-width="5" stroke-linecap="square"/>
</svg>

After

Width:  |  Height:  |  Size: 386 B

+7 -4
View File
@@ -20,12 +20,13 @@ export function ModelSelector() {
<div className="relative" ref={ref}> <div className="relative" ref={ref}>
<button <button
onClick={() => setOpen((v) => !v)} onClick={() => setOpen((v) => !v)}
className="flex h-[34px] items-center gap-2 border-[3px] border-white bg-accent px-3 text-white shadow-accent-soft" className="flex h-[34px] min-w-0 max-w-[260px] items-center gap-2 border-[3px] border-white bg-accent px-3 text-white shadow-accent-soft"
title={selectedModel || undefined}
style={{ borderRadius: 7 }} style={{ borderRadius: 7 }}
> >
<span className="h-2.5 w-2.5 border-2 border-line bg-white" /> <span className="h-2.5 w-2.5 border-2 border-line bg-white" />
<span className="text-[14px] leading-none">{selectedModel || "—"}</span> <span className="min-w-0 truncate text-[14px] leading-none">{selectedModel || "—"}</span>
<ChevronDown size={12} /> <ChevronDown size={12} className="flex-none" />
</button> </button>
{open && ( {open && (
@@ -51,7 +52,9 @@ export function ModelSelector() {
m.name === selectedModel ? "bg-accent" : "bg-card" m.name === selectedModel ? "bg-accent" : "bg-card"
}`} }`}
/> />
<span className="flex-1 text-xs text-ink-2">{m.name}</span> <span className="min-w-0 flex-1 truncate text-xs text-ink-2" title={m.name}>
{m.name}
</span>
{m.size_go > 0 && ( {m.size_go > 0 && (
<span className="text-[10px] text-muted-2">{m.size_go} Go</span> <span className="text-[10px] text-muted-2">{m.size_go} Go</span>
)} )}
+22 -7
View File
@@ -11,6 +11,7 @@ export function ChatPanel() {
selectedModel, selectedModel,
messages, messages,
streaming, streaming,
streamingSessionId,
streamContent, streamContent,
streamThinking, streamThinking,
streamStatus, streamStatus,
@@ -22,6 +23,7 @@ export function ChatPanel() {
pendingShell, pendingShell,
approveShell, approveShell,
rejectShell, rejectShell,
sessions,
} = useStore(); } = useStore();
const activeTools = config const activeTools = config
@@ -49,7 +51,9 @@ export function ChatPanel() {
} }
}; };
const empty = messages.length === 0 && !streaming; const showingStreaming = streaming && currentSessionId === streamingSessionId;
const workingSession = sessions.find((s) => s.id === streamingSessionId);
const empty = messages.length === 0 && !showingStreaming;
return ( return (
<div className="flex min-w-0 flex-1 flex-col bg-base"> <div className="flex min-w-0 flex-1 flex-col bg-base">
@@ -88,7 +92,7 @@ export function ChatPanel() {
{messages.map((m) => ( {messages.map((m) => (
<Bubble key={m.id} msg={m} /> <Bubble key={m.id} msg={m} />
))} ))}
{streaming && ( {showingStreaming && (
<Bubble <Bubble
msg={{ msg={{
id: "stream", id: "stream",
@@ -105,7 +109,7 @@ export function ChatPanel() {
thinking={streamThinking} thinking={streamThinking}
/> />
)} )}
{pendingShell && ( {showingStreaming && pendingShell && (
<ShellConfirm <ShellConfirm
command={pendingShell} command={pendingShell}
onApprove={approveShell} onApprove={approveShell}
@@ -132,12 +136,23 @@ export function ChatPanel() {
<button className="flex h-8 w-8 items-center justify-center border-2 border-line text-ink-2"> <button className="flex h-8 w-8 items-center justify-center border-2 border-line text-ink-2">
<ClipIcon /> <ClipIcon />
</button> </button>
<div className="flex h-8 items-center gap-1.5 border-2 border-line px-2.5 text-[13px] text-ink-2"> <div
className="flex h-8 min-w-0 max-w-[220px] items-center gap-1.5 border-2 border-line px-2.5 text-[13px] text-ink-2"
title={selectedModel || undefined}
>
<span className="h-2 w-2 border-2 border-line bg-accent" /> <span className="h-2 w-2 border-2 border-line bg-accent" />
{selectedModel || "—"} <span className="min-w-0 truncate">{selectedModel || "—"}</span>
</div> </div>
<div className="flex-1" /> {streaming && !showingStreaming ? (
<span className="text-[13px] text-muted-3">⏎ envoyer · ⇧⏎ ligne</span> <span className="min-w-0 flex-1 truncate text-[13px] text-accent">
Travail en cours : {workingSession?.title ?? "session ouverte"}
</span>
) : (
<>
<div className="flex-1" />
<span className="text-[13px] text-muted-3">⏎ envoyer · ⇧⏎ ligne</span>
</>
)}
<button <button
onClick={submit} onClick={submit}
disabled={streaming || !draft.trim()} disabled={streaming || !draft.trim()}
+10
View File
@@ -8,6 +8,7 @@ export function LeftPanel() {
const { const {
sessions, sessions,
currentSessionId, currentSessionId,
streamingSessionId,
refreshSessions, refreshSessions,
refreshFiles, refreshFiles,
fileTree, fileTree,
@@ -43,6 +44,7 @@ export function LeftPanel() {
)} )}
{sessions.map((s) => { {sessions.map((s) => {
const active = s.id === currentSessionId; const active = s.id === currentSessionId;
const working = s.id === streamingSessionId;
return ( return (
<div <div
key={s.id} key={s.id}
@@ -60,6 +62,14 @@ export function LeftPanel() {
> >
{s.title} {s.title}
</div> </div>
{working && (
<span
className="flex-none border-2 border-line bg-accent px-1.5 py-0.5 text-[10px] leading-none text-white"
title="Session en cours de travail"
>
EN COURS
</span>
)}
<button <button
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
+18 -7
View File
@@ -31,6 +31,7 @@ interface LokiState {
currentSessionId: string | null; currentSessionId: string | null;
messages: Message[]; messages: Message[];
streaming: boolean; streaming: boolean;
streamingSessionId: string | null;
streamContent: string; // réponse de l'agent en cours de frappe streamContent: string; // réponse de l'agent en cours de frappe
streamThinking: string; // raisonnement de l'agent en cours streamThinking: string; // raisonnement de l'agent en cours
streamStatus: string; streamStatus: string;
@@ -73,6 +74,7 @@ export const useStore = create<LokiState>((set, get) => ({
currentSessionId: null, currentSessionId: null,
messages: [], messages: [],
streaming: false, streaming: false,
streamingSessionId: null,
streamContent: "", streamContent: "",
streamThinking: "", streamThinking: "",
streamStatus: "", streamStatus: "",
@@ -174,24 +176,26 @@ export const useStore = create<LokiState>((set, get) => ({
newSession: async () => { newSession: async () => {
const s = await createSession(get().selectedModel || undefined); const s = await createSession(get().selectedModel || undefined);
const streaming = get().streaming;
set({ set({
currentSessionId: s.id, currentSessionId: s.id,
messages: [], messages: [],
streamContent: "", ...(streaming
streamStatus: "", ? {}
streamNotice: null, : { streamContent: "", streamStatus: "", streamNotice: null }),
}); });
await get().refreshSessions(); await get().refreshSessions();
}, },
openSession: async (id) => { openSession: async (id) => {
const { messages } = await getSession(id); const { messages } = await getSession(id);
const streaming = get().streaming;
set({ set({
currentSessionId: id, currentSessionId: id,
messages, messages,
streamContent: "", ...(streaming
streamStatus: "", ? {}
streamNotice: null, : { streamContent: "", streamStatus: "", streamNotice: null }),
}); });
}, },
@@ -251,6 +255,7 @@ export const useStore = create<LokiState>((set, get) => ({
set({ set({
messages: [...get().messages, userMsg], messages: [...get().messages, userMsg],
streaming: true, streaming: true,
streamingSessionId: sid,
streamContent: "", streamContent: "",
streamThinking: "", streamThinking: "",
streamStatus: "Connexion à Ollama…", streamStatus: "Connexion à Ollama…",
@@ -292,6 +297,7 @@ export const useStore = create<LokiState>((set, get) => ({
); );
set({ set({
streaming: false, streaming: false,
streamingSessionId: null,
streamContent: "", streamContent: "",
streamThinking: "", streamThinking: "",
streamStatus: "", streamStatus: "",
@@ -314,13 +320,18 @@ export const useStore = create<LokiState>((set, get) => ({
}; };
set({ set({
streaming: false, streaming: false,
streamingSessionId: null,
streamContent: "", streamContent: "",
streamThinking: "", streamThinking: "",
streamStatus: "", streamStatus: "",
streamNotice: null, streamNotice: null,
streamTools: [], streamTools: [],
messages: [...get().messages, errMsg], messages:
get().currentSessionId === sid
? [...get().messages, errMsg]
: get().messages,
}); });
void get().refreshSessions();
}, },
} }
); );