mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-21 14:08:11 +02:00
improve session activity and model display
This commit is contained in:
1 parent
a7290039f6
commit
f6793daed3
6 files changed
+65
-19
No files matched your search
+2
-1
@@ -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>
|
||||||
|
|||||||
@@ -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 |
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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()}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in new issue
Block a user