mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-19 13:08:15 +02:00
perf: fix model reload thrash and cut time-to-first-token
Ollama reloaded the chat model mid-message because plan/summary/router calls sent divergent runner options (no num_ctx/num_batch) and omitted keep_alive — the main cause of perceived slowness. - share one keep-alive httpx.AsyncClient for all Ollama calls - unify runner options (runner_options) + keep_alive on every model call, embeddings included - drop the blocking LLM routing fallback (pure lexical heuristic) - run RAG recall + plan + code-model pick in parallel inside the SSE stream, after the start event - RAG cosine scoring off the event loop; cache /api/tags 30s and nvidia-smi 5s; frontend polls 2s->5s, warm poll backoff, dedup config fetch feat: working session menu in TopBar (switch/create/rename/delete) feat: workspace file deletion (DELETE /api/files + UI trash buttons) docs: recommended Ollama env vars (KEEP_ALIVE, MAX_LOADED_MODELS...) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
552548e4b4
commit
cb2872c78a
23 files changed
+672
-224
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { useEffect } from "react";
|
||||
import { downloadFile, type FileNode } from "../api/client";
|
||||
import { DownloadIcon } from "../components/Icon";
|
||||
import { DownloadIcon, TrashIcon } from "../components/Icon";
|
||||
import { useStore } from "../store/useStore";
|
||||
|
||||
export function HistoryView({ onOpen }: { onOpen: () => void }) {
|
||||
@@ -61,7 +61,14 @@ export function FilesView() {
|
||||
}
|
||||
|
||||
function WorkspaceTree({ nodes, depth }: { nodes: FileNode[]; depth: number }) {
|
||||
const { openPreview, previewPath } = useStore();
|
||||
const { openPreview, previewPath, removeFile } = useStore();
|
||||
const confirmDelete = (node: FileNode) => {
|
||||
const msg =
|
||||
node.type === "dir"
|
||||
? `Supprimer le dossier ${node.path} et tout son contenu ?`
|
||||
: `Supprimer ${node.path} ?`;
|
||||
if (window.confirm(msg)) void removeFile(node.path);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{nodes.map((node) => (
|
||||
@@ -93,6 +100,13 @@ function WorkspaceTree({ nodes, depth }: { nodes: FileNode[]; depth: number }) {
|
||||
<DownloadIcon size={12} /> Télécharger
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={() => confirmDelete(node)}
|
||||
className="flex items-center gap-1 border-2 border-line bg-base px-2 py-1 text-[11px] text-warn"
|
||||
title={`Supprimer ${node.name}`}
|
||||
>
|
||||
<TrashIcon size={12} /> Supprimer
|
||||
</button>
|
||||
</div>
|
||||
{node.children && <WorkspaceTree nodes={node.children} depth={depth + 1} />}
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useEffect } from "react";
|
||||
import { useStore } from "../store/useStore";
|
||||
import { DownloadIcon, PlusIcon } from "../components/Icon";
|
||||
import { DownloadIcon, PlusIcon, TrashIcon } from "../components/Icon";
|
||||
import { downloadFile, type FileNode } from "../api/client";
|
||||
import { relTime } from "../lib/time";
|
||||
|
||||
/** Panneau gauche : historique des sessions + arborescence de fichiers. */
|
||||
export function LeftPanel() {
|
||||
@@ -116,7 +117,14 @@ export function LeftPanel() {
|
||||
}
|
||||
|
||||
function FileTree({ nodes, depth }: { nodes: FileNode[]; depth: number }) {
|
||||
const { openPreview, previewPath } = useStore();
|
||||
const { openPreview, previewPath, removeFile } = useStore();
|
||||
const confirmDelete = (n: FileNode) => {
|
||||
const msg =
|
||||
n.type === "dir"
|
||||
? `Supprimer le dossier ${n.path} et tout son contenu ?`
|
||||
: `Supprimer ${n.path} ?`;
|
||||
if (window.confirm(msg)) void removeFile(n.path);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{nodes.map((n) => {
|
||||
@@ -156,6 +164,16 @@ function FileTree({ nodes, depth }: { nodes: FileNode[]; depth: number }) {
|
||||
<DownloadIcon size={13} />
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
confirmDelete(n);
|
||||
}}
|
||||
className="hidden flex-none text-muted-2 hover:text-warn group-hover:block"
|
||||
title={`Supprimer ${n.name}`}
|
||||
>
|
||||
<TrashIcon size={13} />
|
||||
</button>
|
||||
</div>
|
||||
{n.children && <FileTree nodes={n.children} depth={depth + 1} />}
|
||||
</div>
|
||||
@@ -165,10 +183,3 @@ function FileTree({ nodes, depth }: { nodes: FileNode[]; depth: number }) {
|
||||
);
|
||||
}
|
||||
|
||||
function relTime(ts: number): string {
|
||||
const diff = Date.now() / 1000 - ts;
|
||||
if (diff < 60) return "à l'instant";
|
||||
if (diff < 3600) return `il y a ${Math.floor(diff / 60)} min`;
|
||||
if (diff < 86400) return `il y a ${Math.floor(diff / 3600)} h`;
|
||||
return `il y a ${Math.floor(diff / 86400)} j`;
|
||||
}
|
||||
@@ -8,13 +8,14 @@ import {
|
||||
type GitCommit,
|
||||
type ToolCall,
|
||||
} from "../api/client";
|
||||
import { DownloadIcon } from "../components/Icon";
|
||||
import { DownloadIcon, TrashIcon } from "../components/Icon";
|
||||
|
||||
type TabId = "preview" | "code" | "logs" | "git";
|
||||
|
||||
/** Panneau droit : onglets Aperçu / Code / Logs. */
|
||||
export function PreviewPanel() {
|
||||
const { previewPath, previewContent, messages, streamTools } = useStore();
|
||||
const { previewPath, previewContent, messages, streamTools, removeFile } =
|
||||
useStore();
|
||||
const [tab, setTab] = useState<TabId>("preview");
|
||||
const [width, setWidth] = useState(() => {
|
||||
const saved = Number(window.localStorage.getItem("loki.preview.width"));
|
||||
@@ -95,14 +96,28 @@ export function PreviewPanel() {
|
||||
</span>
|
||||
</div>
|
||||
{previewPath && (
|
||||
<button
|
||||
onClick={() => downloadFile(previewPath)}
|
||||
className="flex h-8 items-center gap-1.5 border-[3px] border-line bg-card px-2.5 text-[12px] text-accent"
|
||||
title={`Télécharger ${previewPath}`}
|
||||
>
|
||||
<DownloadIcon size={13} />
|
||||
Télécharger
|
||||
</button>
|
||||
<>
|
||||
<button
|
||||
onClick={() => downloadFile(previewPath)}
|
||||
className="flex h-8 items-center gap-1.5 border-[3px] border-line bg-card px-2.5 text-[12px] text-accent"
|
||||
title={`Télécharger ${previewPath}`}
|
||||
>
|
||||
<DownloadIcon size={13} />
|
||||
Télécharger
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (window.confirm(`Supprimer ${previewPath} ?`)) {
|
||||
void removeFile(previewPath);
|
||||
}
|
||||
}}
|
||||
className="flex h-8 items-center gap-1.5 border-[3px] border-line bg-card px-2.5 text-[12px] text-warn"
|
||||
title={`Supprimer ${previewPath}`}
|
||||
>
|
||||
<TrashIcon size={13} />
|
||||
Supprimer
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
Reference in new issue
Block a user