mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-23 23:18:13 +02:00
Auto-réglage GPU : détection, num_ctx optimal et bouton Réglage auto
Backend : - agent_config : num_ctx dans la config, envoyé à Ollama (0 = défaut modèle) - ollama_client : méthode ps() (placement GPU/CPU via /api/ps) - autotune : placement() lit size vs size_vram pour savoir si le modèle est sur GPU - routes/config : POST /api/config/auto (détecte GPU + modèle, calcule et applique num_ctx/max_tokens, renvoie la détection et le placement) Frontend : - client/store : autoTune(), état tuning + résultat - SettingsView : bouton ⚡ Réglage auto, bannière de détection (GPU/VRAM, contexte, placement GPU/CPU), slider Contexte (num_ctx) Config : - GPU_VRAM_MB / GPU_NAME pour déclarer la VRAM si Ollama est distant Tests : recommandation (8B sur 12 Go -> ctx 32768), route /auto + persistance, transmission num_ctx aux options Ollama, placement via /api/ps. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SVay7z3y7q2gEe54ByAE6N
This commit is contained in:
10 files changed
+231
-5
No files matched your search
@@ -18,3 +18,9 @@ PORT=8717
|
|||||||
# Optionnel : URL d'une instance SearxNG pour l'outil web_search.
|
# Optionnel : URL d'une instance SearxNG pour l'outil web_search.
|
||||||
# Si vide, web_search utilise DuckDuckGo (sans clé d'API).
|
# Si vide, web_search utilise DuckDuckGo (sans clé d'API).
|
||||||
SEARX_URL=
|
SEARX_URL=
|
||||||
|
|
||||||
|
# Optionnel : VRAM du GPU (en Mo) pour l'auto-réglage, si Ollama tourne sur une
|
||||||
|
# autre machine (la détection nvidia-smi ne voit pas un GPU distant).
|
||||||
|
# Ex. 12000 pour une carte 12 Go. 0 = détection automatique.
|
||||||
|
GPU_VRAM_MB=0
|
||||||
|
GPU_NAME=
|
||||||
@@ -116,6 +116,26 @@ npm run dev # http://localhost:5173
|
|||||||
| `web_search` | Recherche web (DuckDuckGo / SearxNG) | désactivé |
|
| `web_search` | Recherche web (DuckDuckGo / SearxNG) | désactivé |
|
||||||
| `run_shell` | Exécuter une commande **(sensible)** | désactivé |
|
| `run_shell` | Exécuter une commande **(sensible)** | désactivé |
|
||||||
|
|
||||||
|
## Auto-réglage GPU (« Réglage auto »)
|
||||||
|
|
||||||
|
Dans **Configuration → Génération**, le bouton **⚡ Réglage auto** :
|
||||||
|
1. détecte le GPU (VRAM) via `nvidia-smi`/`rocm-smi`, ou la valeur déclarée
|
||||||
|
`GPU_VRAM_MB` ;
|
||||||
|
2. lit les métadonnées du modèle sélectionné via Ollama (contexte max,
|
||||||
|
architecture, taille, quantization) ;
|
||||||
|
3. calcule la **fenêtre de contexte (`num_ctx`)** la plus grande qui tient en
|
||||||
|
VRAM (estimation du cache KV) et un nombre de **jetons max** cohérent, puis
|
||||||
|
les applique ;
|
||||||
|
4. vérifie le **placement réel** du modèle via `/api/ps` d'Ollama (GPU / CPU).
|
||||||
|
|
||||||
|
> **Ollama distant** : si Ollama tourne sur une autre machine que Loki, la
|
||||||
|
> détection `nvidia-smi` ne voit pas ce GPU. Déclare alors la VRAM avec
|
||||||
|
> `GPU_VRAM_MB` (ex. `12000` pour une carte 12 Go) pour un réglage précis.
|
||||||
|
>
|
||||||
|
> **Pourquoi c'est utile** : un `num_ctx` trop grand fait déborder le modèle sur
|
||||||
|
> le CPU (lent). En l'ajustant à ta VRAM, le modèle reste sur le GPU. Laisse
|
||||||
|
> `num_ctx = 0` (auto) pour utiliser le défaut du modèle.
|
||||||
|
|
||||||
## Sécurité
|
## Sécurité
|
||||||
|
|
||||||
- **Confinement** : toutes les opérations fichier (`read_file`, `write_file`,
|
- **Confinement** : toutes les opérations fichier (`read_file`, `write_file`,
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ DEFAULT_CONFIG: dict = {
|
|||||||
"top_p": 0.9,
|
"top_p": 0.9,
|
||||||
"top_k": 40,
|
"top_k": 40,
|
||||||
"max_tokens": 2048,
|
"max_tokens": 2048,
|
||||||
|
# Fenêtre de contexte envoyée à Ollama (0 = laisser le défaut du modèle).
|
||||||
|
"num_ctx": 0,
|
||||||
"tools": dict(DEFAULT_TOOL_STATE),
|
"tools": dict(DEFAULT_TOOL_STATE),
|
||||||
# Demander une validation utilisateur avant toute commande shell.
|
# Demander une validation utilisateur avant toute commande shell.
|
||||||
"confirm_shell": True,
|
"confirm_shell": True,
|
||||||
@@ -69,12 +71,16 @@ def save_config(patch: dict) -> dict:
|
|||||||
|
|
||||||
def ollama_options(cfg: dict) -> dict:
|
def ollama_options(cfg: dict) -> dict:
|
||||||
"""Traduit la config en options de génération Ollama."""
|
"""Traduit la config en options de génération Ollama."""
|
||||||
return {
|
opts = {
|
||||||
"temperature": cfg["temperature"],
|
"temperature": cfg["temperature"],
|
||||||
"top_p": cfg["top_p"],
|
"top_p": cfg["top_p"],
|
||||||
"top_k": cfg["top_k"],
|
"top_k": cfg["top_k"],
|
||||||
"num_predict": cfg["max_tokens"],
|
"num_predict": cfg["max_tokens"],
|
||||||
}
|
}
|
||||||
|
# num_ctx n'est envoyé que s'il est défini (> 0), sinon défaut du modèle.
|
||||||
|
if cfg.get("num_ctx"):
|
||||||
|
opts["num_ctx"] = cfg["num_ctx"]
|
||||||
|
return opts
|
||||||
|
|
||||||
|
|
||||||
def enabled_tool_names(cfg: dict) -> list[str]:
|
def enabled_tool_names(cfg: dict) -> list[str]:
|
||||||
|
|||||||
@@ -210,6 +210,31 @@ async def recommend(model: str) -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def placement(model: str) -> dict:
|
||||||
|
"""Lit /api/ps : le modèle est-il chargé sur GPU, CPU, ou un mix ?"""
|
||||||
|
try:
|
||||||
|
loaded = await ollama.ps()
|
||||||
|
except (httpx.HTTPError, OSError):
|
||||||
|
return {"loaded": False}
|
||||||
|
|
||||||
|
for m in loaded:
|
||||||
|
if m.get("name") == model or m.get("model") == model:
|
||||||
|
size = m.get("size", 0) or 0
|
||||||
|
size_vram = m.get("size_vram", 0) or 0
|
||||||
|
if size <= 0:
|
||||||
|
where = "inconnu"
|
||||||
|
elif size_vram >= size * 0.99:
|
||||||
|
where = "gpu"
|
||||||
|
elif size_vram <= size * 0.01:
|
||||||
|
where = "cpu"
|
||||||
|
else:
|
||||||
|
where = "mixte"
|
||||||
|
pct = int(size_vram / size * 100) if size else 0
|
||||||
|
return {"loaded": True, "where": where, "gpu_percent": pct,
|
||||||
|
"size_mb": size // (1024 * 1024)}
|
||||||
|
return {"loaded": False}
|
||||||
|
|
||||||
|
|
||||||
def _fallback_model_mb(prof: dict) -> int:
|
def _fallback_model_mb(prof: dict) -> int:
|
||||||
"""Estime la taille des poids si /api/tags n'a rien donné."""
|
"""Estime la taille des poids si /api/tags n'a rien donné."""
|
||||||
ps = (prof.get("parameter_size") or "").upper().replace("B", "")
|
ps = (prof.get("parameter_size") or "").upper().replace("B", "")
|
||||||
|
|||||||
@@ -33,6 +33,13 @@ class OllamaClient:
|
|||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
return resp.json().get("models", [])
|
return resp.json().get("models", [])
|
||||||
|
|
||||||
|
async def ps(self) -> list[dict]:
|
||||||
|
"""Modèles actuellement chargés et leur répartition VRAM/CPU (/api/ps)."""
|
||||||
|
async with httpx.AsyncClient(timeout=5.0, follow_redirects=True) as client:
|
||||||
|
resp = await client.get(f"{self.host}/api/ps")
|
||||||
|
resp.raise_for_status()
|
||||||
|
return resp.json().get("models", [])
|
||||||
|
|
||||||
async def show(self, name: str) -> dict:
|
async def show(self, name: str) -> dict:
|
||||||
"""Métadonnées détaillées d'un modèle (/api/show)."""
|
"""Métadonnées détaillées d'un modèle (/api/show)."""
|
||||||
async with httpx.AsyncClient(timeout=15.0, follow_redirects=True) as client:
|
async with httpx.AsyncClient(timeout=15.0, follow_redirects=True) as client:
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
"""Routes de configuration de l'agent (lecture / mise à jour)."""
|
"""Routes de configuration de l'agent (lecture / mise à jour / auto-réglage)."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from .. import agent_config
|
from .. import agent_config, autotune
|
||||||
|
from ..config import settings
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/config", tags=["config"])
|
router = APIRouter(prefix="/api/config", tags=["config"])
|
||||||
|
|
||||||
@@ -15,7 +16,14 @@ class ConfigPatch(BaseModel):
|
|||||||
top_p: float | None = None
|
top_p: float | None = None
|
||||||
top_k: int | None = None
|
top_k: int | None = None
|
||||||
max_tokens: int | None = None
|
max_tokens: int | None = None
|
||||||
|
num_ctx: int | None = None
|
||||||
tools: dict[str, bool] | None = None
|
tools: dict[str, bool] | None = None
|
||||||
|
confirm_shell: bool | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class AutoTuneRequest(BaseModel):
|
||||||
|
model: str
|
||||||
|
apply: bool = True
|
||||||
|
|
||||||
|
|
||||||
@router.get("")
|
@router.get("")
|
||||||
@@ -30,3 +38,27 @@ async def get_config() -> dict:
|
|||||||
async def put_config(patch: ConfigPatch) -> dict:
|
async def put_config(patch: ConfigPatch) -> dict:
|
||||||
cfg = agent_config.save_config(patch.model_dump(exclude_none=True))
|
cfg = agent_config.save_config(patch.model_dump(exclude_none=True))
|
||||||
return {"config": cfg}
|
return {"config": cfg}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/auto")
|
||||||
|
async def auto_tune(req: AutoTuneRequest) -> dict:
|
||||||
|
"""Détecte le GPU + le modèle, calcule les réglages optimaux, et (par
|
||||||
|
défaut) les applique. Renvoie le détail de la détection pour l'UI."""
|
||||||
|
reco = await autotune.recommend(req.model)
|
||||||
|
placement = await autotune.placement(req.model)
|
||||||
|
|
||||||
|
applied = None
|
||||||
|
if req.apply:
|
||||||
|
applied = agent_config.save_config(
|
||||||
|
{
|
||||||
|
"num_ctx": reco["recommended"]["num_ctx"],
|
||||||
|
"max_tokens": reco["recommended"]["max_tokens"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"detection": reco,
|
||||||
|
"placement": placement,
|
||||||
|
"config": applied or agent_config.get_config(),
|
||||||
|
"vram_override": settings.gpu_vram_mb,
|
||||||
|
}
|
||||||
@@ -35,6 +35,9 @@ services:
|
|||||||
- PORT=8717
|
- PORT=8717
|
||||||
# Optionnel : instance SearxNG pour l'outil web_search (sinon DuckDuckGo)
|
# Optionnel : instance SearxNG pour l'outil web_search (sinon DuckDuckGo)
|
||||||
- SEARX_URL=
|
- SEARX_URL=
|
||||||
|
# Optionnel : VRAM (Mo) de ton GPU pour l'auto-réglage, si Ollama est sur
|
||||||
|
# une autre machine (sinon laisse 0). Ex. 12000 pour une carte 12 Go.
|
||||||
|
- GPU_VRAM_MB=0
|
||||||
volumes:
|
volumes:
|
||||||
- /mnt/user/appdata/loki/workspace:/workspace
|
- /mnt/user/appdata/loki/workspace:/workspace
|
||||||
- /mnt/user/appdata/loki/data:/data
|
- /mnt/user/appdata/loki/data:/data
|
||||||
|
|||||||
@@ -61,10 +61,39 @@ export interface AgentConfig {
|
|||||||
top_p: number;
|
top_p: number;
|
||||||
top_k: number;
|
top_k: number;
|
||||||
max_tokens: number;
|
max_tokens: number;
|
||||||
|
num_ctx: number;
|
||||||
tools: Record<string, boolean>;
|
tools: Record<string, boolean>;
|
||||||
confirm_shell: boolean;
|
confirm_shell: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AutoTuneResult {
|
||||||
|
detection: {
|
||||||
|
gpu: { available: boolean; name: string; vram_total_mb: number; source: string };
|
||||||
|
model_profile: {
|
||||||
|
context_length: number | null;
|
||||||
|
parameter_size: string | null;
|
||||||
|
quantization: string | null;
|
||||||
|
size_mb: number | null;
|
||||||
|
};
|
||||||
|
recommended: { num_ctx: number; max_tokens: number };
|
||||||
|
rationale: string;
|
||||||
|
};
|
||||||
|
placement: { loaded: boolean; where?: string; gpu_percent?: number };
|
||||||
|
config: AgentConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function autoTune(
|
||||||
|
model: string,
|
||||||
|
apply = true
|
||||||
|
): Promise<AutoTuneResult> {
|
||||||
|
const res = await fetch("/api/config/auto", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ model, apply }),
|
||||||
|
});
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
|
|
||||||
export async function runShell(
|
export async function runShell(
|
||||||
command: string
|
command: string
|
||||||
): Promise<{ command: string; exit_code: number; output: string }> {
|
): Promise<{ command: string; exit_code: number; output: string }> {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useStore } from "../store/useStore";
|
import { useStore } from "../store/useStore";
|
||||||
import { pullModel } from "../api/client";
|
import { pullModel } from "../api/client";
|
||||||
import type { AgentConfig } from "../api/client";
|
import type { AgentConfig, AutoTuneResult } from "../api/client";
|
||||||
import { DownloadIcon, RefreshIcon } from "../components/Icon";
|
import { DownloadIcon, RefreshIcon } from "../components/Icon";
|
||||||
|
|
||||||
const TOOL_DESC: Record<string, string> = {
|
const TOOL_DESC: Record<string, string> = {
|
||||||
@@ -25,6 +25,9 @@ export function SettingsView() {
|
|||||||
availableTools,
|
availableTools,
|
||||||
refreshConfig,
|
refreshConfig,
|
||||||
updateConfig,
|
updateConfig,
|
||||||
|
tuning,
|
||||||
|
tuneResult,
|
||||||
|
runAutoTune,
|
||||||
} = useStore();
|
} = useStore();
|
||||||
|
|
||||||
// Brouillon local édité, synchronisé depuis la config serveur.
|
// Brouillon local édité, synchronisé depuis la config serveur.
|
||||||
@@ -221,7 +224,25 @@ export function SettingsView() {
|
|||||||
{/* Génération + Outils */}
|
{/* Génération + Outils */}
|
||||||
<div className="flex flex-col gap-5">
|
<div className="flex flex-col gap-5">
|
||||||
<div className="rounded-card border border-line bg-card p-[18px]">
|
<div className="rounded-card border border-line bg-card p-[18px]">
|
||||||
<div className="mb-4 text-sm font-bold">Génération</div>
|
<div className="mb-4 flex items-center justify-between">
|
||||||
|
<div className="text-sm font-bold">Génération</div>
|
||||||
|
<button
|
||||||
|
onClick={runAutoTune}
|
||||||
|
disabled={tuning || !selectedModel}
|
||||||
|
className="flex h-[26px] items-center gap-1.5 rounded-[7px] border border-[rgba(240,161,92,.4)] bg-[rgba(240,161,92,.10)] px-2.5 text-[11.5px] font-semibold text-accent disabled:opacity-40"
|
||||||
|
title="Détecte le GPU et le modèle, puis optimise le contexte et les jetons"
|
||||||
|
>
|
||||||
|
{tuning ? (
|
||||||
|
<span className="h-3 w-3 animate-spin rounded-full border-2 border-accent/40 border-t-accent" />
|
||||||
|
) : (
|
||||||
|
<span>⚡</span>
|
||||||
|
)}
|
||||||
|
{tuning ? "Détection…" : "Réglage auto"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{tuneResult && <TuneBanner result={tuneResult} />}
|
||||||
|
|
||||||
<Slider
|
<Slider
|
||||||
label="Température"
|
label="Température"
|
||||||
value={draft.temperature}
|
value={draft.temperature}
|
||||||
@@ -257,6 +278,17 @@ export function SettingsView() {
|
|||||||
step={128}
|
step={128}
|
||||||
fmt={(v) => String(Math.round(v))}
|
fmt={(v) => String(Math.round(v))}
|
||||||
onChange={(v) => set("max_tokens", Math.round(v))}
|
onChange={(v) => set("max_tokens", Math.round(v))}
|
||||||
|
/>
|
||||||
|
<Slider
|
||||||
|
label="Contexte (num_ctx)"
|
||||||
|
value={draft.num_ctx}
|
||||||
|
min={0}
|
||||||
|
max={32768}
|
||||||
|
step={1024}
|
||||||
|
fmt={(v) =>
|
||||||
|
v === 0 ? "auto" : v >= 1024 ? `${v / 1024}K` : String(v)
|
||||||
|
}
|
||||||
|
onChange={(v) => set("num_ctx", Math.round(v))}
|
||||||
last
|
last
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -352,6 +384,51 @@ export function SettingsView() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function TuneBanner({ result }: { result: AutoTuneResult }) {
|
||||||
|
const { gpu, model_profile, recommended, rationale } = result.detection;
|
||||||
|
const place = result.placement;
|
||||||
|
|
||||||
|
const placeLabel =
|
||||||
|
place.loaded && place.where
|
||||||
|
? place.where === "gpu"
|
||||||
|
? `chargé GPU (${place.gpu_percent}%)`
|
||||||
|
: place.where === "cpu"
|
||||||
|
? "chargé CPU ⚠️"
|
||||||
|
: `mixte GPU ${place.gpu_percent}%`
|
||||||
|
: "non chargé";
|
||||||
|
const placeColor =
|
||||||
|
place.where === "gpu" ? "text-ok" : place.where === "cpu" ? "text-warn" : "text-muted";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-4 rounded-[10px] border border-[rgba(240,161,92,.35)] bg-[rgba(240,161,92,.06)] p-3 text-[11.5px]">
|
||||||
|
<div className="mb-1.5 flex items-center justify-between">
|
||||||
|
<span className="font-semibold text-accent">⚡ Optimisé</span>
|
||||||
|
<span className={`font-mono ${placeColor}`}>{placeLabel}</span>
|
||||||
|
</div>
|
||||||
|
<div className="font-mono text-muted">
|
||||||
|
{gpu.available
|
||||||
|
? `${gpu.name} · ${(gpu.vram_total_mb / 1024).toFixed(1)} Go VRAM`
|
||||||
|
: "Aucun GPU détecté"}
|
||||||
|
{model_profile.context_length
|
||||||
|
? ` · ctx modèle ${Math.round(model_profile.context_length / 1024)}K`
|
||||||
|
: ""}
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 text-muted-2">
|
||||||
|
→ contexte <b className="text-ink-2">{recommended.num_ctx}</b> · jetons max{" "}
|
||||||
|
<b className="text-ink-2">{recommended.max_tokens}</b>
|
||||||
|
</div>
|
||||||
|
{rationale && <div className="mt-1 text-muted-3">{rationale}</div>}
|
||||||
|
{!gpu.available && (
|
||||||
|
<div className="mt-1.5 text-muted-3">
|
||||||
|
GPU non détecté dans le conteneur. Si Ollama tourne sur une autre
|
||||||
|
machine, déclare la VRAM via <code>GPU_VRAM_MB</code> (ex. 12000) dans
|
||||||
|
le compose pour un réglage précis.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function Slider({
|
function Slider({
|
||||||
label,
|
label,
|
||||||
value,
|
value,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import {
|
import {
|
||||||
|
autoTune,
|
||||||
createSession,
|
createSession,
|
||||||
deleteSession,
|
deleteSession,
|
||||||
getConfig,
|
getConfig,
|
||||||
@@ -13,6 +14,7 @@ import {
|
|||||||
saveConfig,
|
saveConfig,
|
||||||
streamChat,
|
streamChat,
|
||||||
type AgentConfig,
|
type AgentConfig,
|
||||||
|
type AutoTuneResult,
|
||||||
type FileNode,
|
type FileNode,
|
||||||
type Message,
|
type Message,
|
||||||
type OllamaModel,
|
type OllamaModel,
|
||||||
@@ -43,6 +45,10 @@ interface LokiState {
|
|||||||
refreshConfig: () => Promise<void>;
|
refreshConfig: () => Promise<void>;
|
||||||
updateConfig: (patch: Partial<AgentConfig>) => Promise<void>;
|
updateConfig: (patch: Partial<AgentConfig>) => Promise<void>;
|
||||||
|
|
||||||
|
tuning: boolean;
|
||||||
|
tuneResult: AutoTuneResult | null;
|
||||||
|
runAutoTune: () => Promise<void>;
|
||||||
|
|
||||||
pendingShell: string | null; // commande shell en attente de validation
|
pendingShell: string | null; // commande shell en attente de validation
|
||||||
approveShell: () => Promise<void>;
|
approveShell: () => Promise<void>;
|
||||||
rejectShell: () => Promise<void>;
|
rejectShell: () => Promise<void>;
|
||||||
@@ -114,6 +120,21 @@ export const useStore = create<LokiState>((set, get) => ({
|
|||||||
set({ config });
|
set({ config });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
tuning: false,
|
||||||
|
tuneResult: null,
|
||||||
|
|
||||||
|
runAutoTune: async () => {
|
||||||
|
const model = get().selectedModel;
|
||||||
|
if (!model || get().tuning) return;
|
||||||
|
set({ tuning: true });
|
||||||
|
try {
|
||||||
|
const result = await autoTune(model, true);
|
||||||
|
set({ tuneResult: result, config: result.config });
|
||||||
|
} finally {
|
||||||
|
set({ tuning: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
openPreview: async (path) => {
|
openPreview: async (path) => {
|
||||||
const content = await fileContent(path);
|
const content = await fileContent(path);
|
||||||
set({ previewPath: path, previewContent: content });
|
set({ previewPath: path, previewContent: content });
|
||||||
|
|||||||
Reference in new issue
Block a user