adding the first run wizard again
This commit is contained in:
@@ -0,0 +1,300 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useToast } from "./Toast";
|
||||
|
||||
export function FirstRunWizard({ onComplete }: { onComplete: () => void }) {
|
||||
const [step, setStep] = useState("welcome");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [models, setModels] = useState<string[]>([]);
|
||||
const [selectedModel, setSelectedModel] = useState<string>("");
|
||||
// ponytail: wizard defaults to local Ollama; no API-key UI here (Settings covers remote).
|
||||
const apiUrl = "http://localhost:11434";
|
||||
const apiKey = "";
|
||||
const { addToast } = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
if (step === "check") {
|
||||
checkOllama();
|
||||
}
|
||||
}, [step]);
|
||||
|
||||
async function checkOllama() {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
try {
|
||||
// Test connection to the default Ollama URL
|
||||
const result = await invoke<{ ok: boolean; models: string[]; error: string }>(
|
||||
"test_connection",
|
||||
{ config: { api_url: apiUrl, api_key: apiKey, model: "", temperature: 0.7, max_tokens: 512, top_p: 0.9, image_model: "", embed_model: "" } }
|
||||
);
|
||||
if (result.ok) {
|
||||
setModels(result.models);
|
||||
setStep("model");
|
||||
} else {
|
||||
setError(result.error || "Failed to connect to Ollama");
|
||||
setStep("error");
|
||||
}
|
||||
} catch (e) {
|
||||
setError(String(e));
|
||||
setStep("error");
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
async function pullModel(model: string) {
|
||||
setLoading(true);
|
||||
setError("");
|
||||
try {
|
||||
// We don't have a direct pull command, but we can suggest the user to run `ollama pull` in terminal.
|
||||
// For now, we'll just set the model in config and hope it exists.
|
||||
// Alternatively, we could invoke a generate command to trigger a pull? Not sure.
|
||||
// We'll just set the config and complete.
|
||||
await invoke("set_llm_config", {
|
||||
config: {
|
||||
api_url: apiUrl,
|
||||
api_key: apiKey,
|
||||
model: model,
|
||||
temperature: 0.7,
|
||||
max_tokens: 512,
|
||||
top_p: 0.9,
|
||||
image_model: "x/flux2-klein:4b",
|
||||
embed_model: "nomic-embed-text",
|
||||
}
|
||||
});
|
||||
addToast(`Model ${model} selected. You may need to run 'ollama pull ${model}' if not already downloaded.`, "success");
|
||||
setStep("done");
|
||||
} catch (e) {
|
||||
setError(String(e));
|
||||
setStep("error");
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
function skip() {
|
||||
// Skip wizard and go to settings
|
||||
onComplete();
|
||||
}
|
||||
|
||||
function back() {
|
||||
if (step === "model") {
|
||||
setStep("welcome");
|
||||
} else if (step === "error") {
|
||||
setStep("welcome");
|
||||
} else if (step === "done") {
|
||||
// Should not happen
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (step === "welcome") {
|
||||
setStep("check");
|
||||
return;
|
||||
}
|
||||
if (step === "model") {
|
||||
if (!selectedModel) {
|
||||
setError("Please select a model");
|
||||
return;
|
||||
}
|
||||
await pullModel(selectedModel);
|
||||
return;
|
||||
}
|
||||
if (step === "done") {
|
||||
onComplete();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (step === "welcome") {
|
||||
return (
|
||||
<div className="glass-card p-6 max-w-md w-full mx-auto">
|
||||
<h2 className="font-heading text-[var(--color-gold-bright)] text-lg font-semibold mb-4">
|
||||
Welcome to DM-Pal
|
||||
</h2>
|
||||
<p className="text-[var(--color-text-primary)] text-sm mb-4">
|
||||
To generate content, DM-Pal needs a language model. Let's set up a local model using Ollama (recommended for privacy and offline use).
|
||||
</p>
|
||||
<div className="flex flex-col gap-3">
|
||||
<button
|
||||
onClick={() => setStep("check")}
|
||||
disabled={loading}
|
||||
className="rounded-lg bg-[var(--color-gold-bright)] text-[var(--color-bg-deep)] px-4 py-2 text-sm font-semibold hover:bg-[var(--color-gold-muted)] transition-colors cursor-pointer disabled:opacity-50"
|
||||
>
|
||||
{loading ? "Checking…" : "Check for Ollama"}
|
||||
</button>
|
||||
<button
|
||||
onClick={skip}
|
||||
className="rounded-lg bg-[var(--color-bg-surface)] border border-[var(--color-border-glass)] text-[var(--color-text-dim)] px-4 py-2 text-sm hover:text-[var(--color-gold-bright)] hover:border-[var(--color-gold-bright)] transition-colors cursor-pointer"
|
||||
>
|
||||
Skip for now (go to Settings)
|
||||
</button>
|
||||
</div>
|
||||
{error && (
|
||||
<p className="text-[var(--color-danger)] text-sm mt-4">{error}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (step === "check") {
|
||||
return (
|
||||
<div className="glass-card p-6 max-w-md w-full mx-auto">
|
||||
<h2 className="font-heading text-[var(--color-gold-bright)] text-lg font-semibold mb-4">
|
||||
Checking for Ollama
|
||||
</h2>
|
||||
<p className="text-[var(--color-text-primary)] text-sm mb-4">
|
||||
We're checking if Ollama is running at {apiUrl}...
|
||||
</p>
|
||||
{loading ? (
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<div className="w-4 h-4 border-2 border-[var(--color-gold-bright)] border-t-transparent rounded-full animate-spin" />
|
||||
<p className="text-[var(--color-text-dim)] text-xs">Checking…</p>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{error ? (
|
||||
<p className="text-[var(--color-danger)] text-sm">{error}</p>
|
||||
) : (
|
||||
<p className="text-[var(--color-success)] text-sm">Ollama is running!</p>
|
||||
)}
|
||||
<div className="flex flex-col gap-3 mt-6">
|
||||
<button
|
||||
onClick={() => setStep("model")}
|
||||
disabled={loading}
|
||||
className="rounded-lg bg-[var(--color-gold-bright)] text-[var(--color-bg-deep)] px-4 py-2 text-sm font-semibold hover:bg-[var(--color-gold-muted)] transition-colors cursor-pointer disabled:opacity-50"
|
||||
>
|
||||
Continue to model selection
|
||||
</button>
|
||||
<button
|
||||
onClick={skip}
|
||||
className="rounded-lg bg-[var(--color-bg-surface)] border border-[var(--color-border-glass)] text-[var(--color-text-dim)] px-4 py-2 text-sm hover:text-[var(--color-gold-bright)] hover:border-[var(--color-gold-bright)] transition-colors cursor-pointer"
|
||||
>
|
||||
Skip for now (go to Settings)
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (step === "model") {
|
||||
return (
|
||||
<div className="glass-card p-6 max-w-md w-full mx-auto">
|
||||
<h2 className="font-heading text-[var(--color-gold-bright)] text-lg font-semibold mb-4">
|
||||
Select a Model
|
||||
</h2>
|
||||
<p className="text-[var(--color-text-primary)] text-sm mb-4">
|
||||
Ollama is running. Choose a model to use for text generation.
|
||||
</p>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<label className="text-[var(--color-text-secondary)] text-xs font-medium">Model</label>
|
||||
<select
|
||||
value={selectedModel}
|
||||
onChange={(e) => setSelectedModel(e.target.value)}
|
||||
className="rounded-lg bg-[var(--color-bg-surface)] border border-[var(--color-border-glass)] px-3 py-2 text-[var(--color-text-primary)] focus:outline-none focus:border-[var(--color-gold-bright)] text-xs cursor-pointer"
|
||||
>
|
||||
{models.map((m) => (
|
||||
<option key={m} value={m}>
|
||||
{m}
|
||||
{m === "llama3.2" && " (recommended)"}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
disabled={loading || !selectedModel}
|
||||
className="rounded-lg bg-[var(--color-gold-bright)] text-[var(--color-bg-deep)] px-4 py-2 text-sm font-semibold hover:bg-[var(--color-gold-muted)] transition-colors cursor-pointer disabled:opacity-50"
|
||||
>
|
||||
{loading ? "Setting model…" : "Set Model"}
|
||||
</button>
|
||||
<button
|
||||
onClick={back}
|
||||
className="rounded-lg bg-[var(--color-bg-surface)] border border-[var(--color-border-glass)] text-[var(--color-text-dim)] px-4 py-2 text-sm hover:text-[var(--color-gold-bright)] hover:border-[var(--color-gold-bright)] transition-colors cursor-pointer"
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
<button
|
||||
onClick={skip}
|
||||
className="rounded-lg bg-[var(--color-bg-surface)] border border-[var(--color-border-glass)] text-[var(--color-text-dim)] px-4 py-2 text-sm hover:text-[var(--color-gold-bright)] hover:border-[var(--color-gold-bright)] transition-colors cursor-pointer"
|
||||
>
|
||||
Skip for now (go to Settings)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{error && (
|
||||
<p className="text-[var(--color-danger)] text-sm mt-4">{error}</p>
|
||||
)}
|
||||
<p className="text-[var(--color-text-dim)] text-sm mt-4">
|
||||
If you don't see your model, you may need to download it first. In a terminal, run:{' '}
|
||||
<code className="font-mono text-[var(--color-gold-bright)]">ollama pull llama3.2</code>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (step === "done") {
|
||||
return (
|
||||
<div className="glass-card p-6 max-w-md w-full mx-auto">
|
||||
<h2 className="font-heading text-[var(--color-gold-bright)] text-lg font-semibold mb-4">
|
||||
Setup Complete
|
||||
</h2>
|
||||
<p className="text-[var(--color-text-primary)] text-sm mb-4">
|
||||
DM-Pal is now configured to use {selectedModel} for text generation.
|
||||
Image generation uses x/flux2-klein:4b (macOS-only via Ollama).
|
||||
Embedding model for lore search is set to nomic-embed-text.
|
||||
</p>
|
||||
<div className="flex flex-col gap-3">
|
||||
<button
|
||||
onClick={() => onComplete()}
|
||||
className="rounded-lg bg-[var(--color-gold-bright)] text-[var(--color-bg-deep)] px-4 py-2 text-sm font-semibold hover:bg-[var(--color-gold-muted)] transition-colors cursor-pointer"
|
||||
>
|
||||
Go to Dashboard
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
// Go to settings to review
|
||||
// We'll just complete and let the user navigate to settings if they want
|
||||
onComplete();
|
||||
}}
|
||||
className="rounded-lg bg-[var(--color-bg-surface)] border border-[var(--color-border-glass)] text-[var(--color-text-dim)] px-4 py-2 text-sm hover:text-[var(--color-gold-bright)] hover:border-[var(--color-gold-bright)] transition-colors cursor-pointer"
|
||||
>
|
||||
Review Settings
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (step === "error") {
|
||||
return (
|
||||
<div className="glass-card p-6 max-w-md w-full mx-auto">
|
||||
<h2 className="font-heading text-[var(--color-gold-bright)] text-lg font-semibold mb-4">
|
||||
Error
|
||||
</h2>
|
||||
<p className="text-[var(--color-text-primary)] text-sm mb-4">
|
||||
{error}
|
||||
</p>
|
||||
<div className="flex flex-col gap-3">
|
||||
<button
|
||||
onClick={() => setStep("welcome")}
|
||||
className="rounded-lg bg-[var(--color-gold-bright)] text-[var(--color-bg-deep)] px-4 py-2 text-sm font-semibold hover:bg-[var(--color-gold-muted)] transition-colors cursor-pointer"
|
||||
>
|
||||
Try Again
|
||||
</button>
|
||||
<button
|
||||
onClick={skip}
|
||||
className="rounded-lg bg-[var(--color-bg-surface)] border border-[var(--color-border-glass)] text-[var(--color-text-dim)] px-4 py-2 text-sm hover:text-[var(--color-gold-bright)] hover:border-[var(--color-gold-bright)] transition-colors cursor-pointer"
|
||||
>
|
||||
Skip for now (go to Settings)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user