stagent 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/dist/cli.js +146 -2
- package/docs/.coverage-gaps.json +21 -0
- package/docs/.last-generated +1 -1
- package/docs/features/agent-intelligence.md +36 -14
- package/docs/features/chat.md +33 -56
- package/docs/features/cost-usage.md +14 -10
- package/docs/features/dashboard-kanban.md +30 -13
- package/docs/features/delivery-channels.md +198 -0
- package/docs/features/design-system.md +10 -10
- package/docs/features/documents.md +8 -8
- package/docs/features/home-workspace.md +20 -15
- package/docs/features/inbox-notifications.md +22 -10
- package/docs/features/keyboard-navigation.md +11 -11
- package/docs/features/monitoring.md +1 -1
- package/docs/features/playbook.md +30 -32
- package/docs/features/profiles.md +33 -11
- package/docs/features/projects.md +2 -2
- package/docs/features/provider-runtimes.md +58 -14
- package/docs/features/schedules.md +70 -40
- package/docs/features/settings.md +74 -46
- package/docs/features/shared-components.md +7 -15
- package/docs/features/tool-permissions.md +9 -9
- package/docs/features/workflows.md +32 -21
- package/docs/getting-started.md +33 -9
- package/docs/index.md +25 -16
- package/docs/journeys/developer.md +124 -207
- package/docs/journeys/personal-use.md +70 -79
- package/docs/journeys/power-user.md +107 -151
- package/docs/journeys/work-use.md +81 -113
- package/docs/manifest.json +77 -45
- package/docs/superpowers/plans/2026-03-30-finish-in-progress-features.md +547 -0
- package/docs/use-cases/agency-operator.md +84 -0
- package/docs/use-cases/solo-founder.md +75 -0
- package/docs/why-stagent.md +59 -0
- package/package.json +10 -3
- package/src/app/api/channels/[id]/route.ts +103 -0
- package/src/app/api/channels/[id]/test/route.ts +52 -0
- package/src/app/api/channels/inbound/slack/route.ts +109 -0
- package/src/app/api/channels/inbound/telegram/poll/route.ts +128 -0
- package/src/app/api/channels/inbound/telegram/route.ts +76 -0
- package/src/app/api/channels/route.ts +71 -0
- package/src/app/api/chat/conversations/route.ts +15 -0
- package/src/app/api/chat/entities/search/route.ts +46 -31
- package/src/app/api/environment/profiles/suggest/route.ts +19 -3
- package/src/app/api/environment/scan/route.ts +8 -1
- package/src/app/api/handoffs/[id]/route.ts +76 -0
- package/src/app/api/handoffs/route.ts +89 -0
- package/src/app/api/memory/route.ts +181 -0
- package/src/app/api/profiles/[id]/route.ts +16 -1
- package/src/app/api/profiles/[id]/test/route.ts +4 -0
- package/src/app/api/profiles/[id]/test-results/route.ts +22 -0
- package/src/app/api/profiles/[id]/test-single/route.ts +64 -0
- package/src/app/api/profiles/assist/route.ts +35 -0
- package/src/app/api/profiles/import-repo/apply-updates/route.ts +123 -0
- package/src/app/api/profiles/import-repo/check-updates/route.ts +163 -0
- package/src/app/api/profiles/import-repo/confirm/route.ts +118 -0
- package/src/app/api/profiles/import-repo/preview/route.ts +107 -0
- package/src/app/api/profiles/import-repo/route.ts +29 -0
- package/src/app/api/profiles/import-repo/scan/route.ts +25 -0
- package/src/app/api/profiles/route.ts +73 -22
- package/src/app/api/runtimes/ollama/route.ts +86 -0
- package/src/app/api/runtimes/suggest/route.ts +29 -0
- package/src/app/api/schedules/[id]/heartbeat-history/route.ts +77 -0
- package/src/app/api/schedules/[id]/route.ts +41 -3
- package/src/app/api/schedules/parse/route.ts +66 -0
- package/src/app/api/schedules/route.ts +71 -12
- package/src/app/api/settings/author-default/route.ts +7 -0
- package/src/app/api/settings/learning/route.ts +41 -0
- package/src/app/api/settings/ollama/route.ts +34 -0
- package/src/app/api/settings/providers/route.ts +57 -0
- package/src/app/api/settings/routing/route.ts +24 -0
- package/src/app/api/settings/web-search/route.ts +28 -0
- package/src/app/api/tasks/[id]/execute/route.ts +13 -1
- package/src/app/documents/page.tsx +3 -0
- package/src/app/environment/page.tsx +8 -1
- package/src/app/settings/page.tsx +10 -4
- package/src/app/workflows/[id]/edit/page.tsx +2 -0
- package/src/app/workflows/new/page.tsx +2 -0
- package/src/components/chat/chat-command-popover.tsx +22 -19
- package/src/components/chat/chat-input.tsx +5 -0
- package/src/components/chat/chat-model-selector.tsx +42 -1
- package/src/components/chat/chat-shell.tsx +2 -0
- package/src/components/dashboard/welcome-landing.tsx +9 -9
- package/src/components/environment/artifact-card.tsx +27 -1
- package/src/components/environment/environment-dashboard.tsx +50 -2
- package/src/components/environment/environment-summary-card.tsx +5 -2
- package/src/components/environment/suggested-profiles.tsx +117 -52
- package/src/components/handoffs/handoff-approval-card.tsx +159 -0
- package/src/components/memory/memory-browser.tsx +315 -0
- package/src/components/profiles/learned-context-panel.tsx +4 -4
- package/src/components/profiles/profile-assist-panel.tsx +512 -0
- package/src/components/profiles/profile-browser.tsx +109 -8
- package/src/components/profiles/profile-card.tsx +29 -1
- package/src/components/profiles/profile-detail-view.tsx +200 -28
- package/src/components/profiles/profile-form-view.tsx +220 -82
- package/src/components/profiles/repo-import-wizard.tsx +648 -0
- package/src/components/profiles/smoke-test-editor.tsx +106 -0
- package/src/components/schedules/schedule-create-sheet.tsx +9 -1
- package/src/components/schedules/schedule-form.tsx +348 -9
- package/src/components/schedules/schedule-list.tsx +15 -2
- package/src/components/settings/auth-method-selector.tsx +7 -1
- package/src/components/settings/budget-guardrails-section.tsx +111 -48
- package/src/components/settings/channels-section.tsx +526 -0
- package/src/components/settings/chat-settings-section.tsx +27 -1
- package/src/components/settings/data-management-section.tsx +8 -6
- package/src/components/settings/learning-context-section.tsx +124 -0
- package/src/components/settings/ollama-section.tsx +270 -0
- package/src/components/settings/providers-runtimes-section.tsx +499 -0
- package/src/components/settings/web-search-section.tsx +101 -0
- package/src/components/shared/tag-input.tsx +156 -0
- package/src/components/tasks/kanban-board.tsx +32 -0
- package/src/components/tasks/kanban-column.tsx +4 -2
- package/src/components/tasks/task-card.tsx +1 -0
- package/src/components/tasks/task-chip-bar.tsx +6 -1
- package/src/components/tasks/task-create-panel.tsx +55 -5
- package/src/components/workflows/workflow-form-view.tsx +38 -3
- package/src/hooks/use-chat-autocomplete.ts +24 -26
- package/src/hooks/use-project-skills.ts +66 -0
- package/src/hooks/use-tag-suggestions.ts +31 -0
- package/src/instrumentation.ts +4 -1
- package/src/lib/agents/__tests__/claude-agent.test.ts +3 -0
- package/src/lib/agents/__tests__/learned-context.test.ts +10 -0
- package/src/lib/agents/agentic-loop.ts +235 -0
- package/src/lib/agents/browser-mcp.ts +59 -4
- package/src/lib/agents/claude-agent.ts +26 -199
- package/src/lib/agents/handoff/bus.ts +164 -0
- package/src/lib/agents/handoff/governance.ts +47 -0
- package/src/lib/agents/handoff/types.ts +16 -0
- package/src/lib/agents/learned-context.ts +27 -7
- package/src/lib/agents/memory/decay.ts +61 -0
- package/src/lib/agents/memory/extractor.ts +181 -0
- package/src/lib/agents/memory/retrieval.ts +96 -0
- package/src/lib/agents/memory/types.ts +6 -0
- package/src/lib/agents/profiles/__tests__/project-profiles.test.ts +119 -0
- package/src/lib/agents/profiles/__tests__/registry.test.ts +11 -3
- package/src/lib/agents/profiles/builtins/code-reviewer/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/content-creator/SKILL.md +19 -0
- package/src/lib/agents/profiles/builtins/content-creator/profile.yaml +27 -0
- package/src/lib/agents/profiles/builtins/customer-support-agent/SKILL.md +19 -0
- package/src/lib/agents/profiles/builtins/customer-support-agent/profile.yaml +26 -0
- package/src/lib/agents/profiles/builtins/data-analyst/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/devops-engineer/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/document-writer/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/financial-analyst/SKILL.md +19 -0
- package/src/lib/agents/profiles/builtins/financial-analyst/profile.yaml +24 -0
- package/src/lib/agents/profiles/builtins/general/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/health-fitness-coach/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/learning-coach/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/marketing-strategist/SKILL.md +19 -0
- package/src/lib/agents/profiles/builtins/marketing-strategist/profile.yaml +27 -0
- package/src/lib/agents/profiles/builtins/operations-coordinator/SKILL.md +19 -0
- package/src/lib/agents/profiles/builtins/operations-coordinator/profile.yaml +26 -0
- package/src/lib/agents/profiles/builtins/project-manager/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/researcher/SKILL.md +1 -0
- package/src/lib/agents/profiles/builtins/researcher/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/sales-researcher/SKILL.md +19 -0
- package/src/lib/agents/profiles/builtins/sales-researcher/profile.yaml +26 -0
- package/src/lib/agents/profiles/builtins/shopping-assistant/SKILL.md +1 -0
- package/src/lib/agents/profiles/builtins/shopping-assistant/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/sweep/profile.yaml +1 -1
- package/src/lib/agents/profiles/builtins/technical-writer/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/travel-planner/SKILL.md +2 -0
- package/src/lib/agents/profiles/builtins/travel-planner/profile.yaml +2 -2
- package/src/lib/agents/profiles/builtins/wealth-manager/SKILL.md +2 -0
- package/src/lib/agents/profiles/builtins/wealth-manager/profile.yaml +2 -2
- package/src/lib/agents/profiles/project-profiles.ts +193 -0
- package/src/lib/agents/profiles/registry.ts +130 -6
- package/src/lib/agents/profiles/types.ts +28 -0
- package/src/lib/agents/router.ts +174 -2
- package/src/lib/agents/runtime/__tests__/catalog.test.ts +15 -4
- package/src/lib/agents/runtime/anthropic-direct.ts +644 -0
- package/src/lib/agents/runtime/catalog.ts +57 -2
- package/src/lib/agents/runtime/claude.ts +205 -1
- package/src/lib/agents/runtime/index.ts +22 -0
- package/src/lib/agents/runtime/ollama-adapter.ts +409 -0
- package/src/lib/agents/runtime/openai-direct.ts +514 -0
- package/src/lib/agents/runtime/profile-assist-types.ts +30 -0
- package/src/lib/agents/runtime/types.ts +2 -0
- package/src/lib/agents/tool-permissions.ts +203 -0
- package/src/lib/channels/gateway.ts +321 -0
- package/src/lib/channels/poller.ts +268 -0
- package/src/lib/channels/registry.ts +90 -0
- package/src/lib/channels/slack-adapter.ts +188 -0
- package/src/lib/channels/telegram-adapter.ts +218 -0
- package/src/lib/channels/types.ts +43 -0
- package/src/lib/channels/webhook-adapter.ts +74 -0
- package/src/lib/chat/context-builder.ts +22 -2
- package/src/lib/chat/engine.ts +95 -13
- package/src/lib/chat/ollama-engine.ts +198 -0
- package/src/lib/chat/stagent-tools.ts +106 -20
- package/src/lib/chat/tool-catalog.ts +24 -0
- package/src/lib/chat/tool-registry.ts +90 -0
- package/src/lib/chat/tools/chat-history-tools.ts +4 -4
- package/src/lib/chat/tools/document-tools.ts +7 -7
- package/src/lib/chat/tools/handoff-tools.ts +70 -0
- package/src/lib/chat/tools/notification-tools.ts +4 -4
- package/src/lib/chat/tools/profile-tools.ts +3 -3
- package/src/lib/chat/tools/project-tools.ts +3 -3
- package/src/lib/chat/tools/schedule-tools.ts +29 -13
- package/src/lib/chat/tools/settings-tools.ts +2 -2
- package/src/lib/chat/tools/task-tools.ts +66 -11
- package/src/lib/chat/tools/usage-tools.ts +2 -2
- package/src/lib/chat/tools/workflow-tools.ts +8 -8
- package/src/lib/chat/types.ts +11 -5
- package/src/lib/constants/known-tools.ts +19 -0
- package/src/lib/constants/prose-styles.ts +1 -1
- package/src/lib/constants/settings.ts +7 -0
- package/src/lib/data/channel-bindings.ts +85 -0
- package/src/lib/data/clear.ts +22 -0
- package/src/lib/data/profile-test-results.ts +48 -0
- package/src/lib/data/seed-data/conversations.ts +196 -0
- package/src/lib/data/seed-data/learned-context.ts +99 -0
- package/src/lib/data/seed-data/notifications.ts +54 -1
- package/src/lib/data/seed-data/profile-test-results.ts +96 -0
- package/src/lib/data/seed-data/repo-imports.ts +51 -0
- package/src/lib/data/seed-data/views.ts +60 -0
- package/src/lib/data/seed.ts +51 -0
- package/src/lib/db/bootstrap.ts +162 -0
- package/src/lib/db/migrations/0013_add_repo_imports.sql +15 -0
- package/src/lib/db/migrations/0014_add_linked_profile_id.sql +3 -0
- package/src/lib/db/migrations/0015_add_channel_bindings.sql +23 -0
- package/src/lib/db/schema.ts +187 -1
- package/src/lib/environment/__tests__/auto-scan.test.ts +86 -0
- package/src/lib/environment/__tests__/profile-linker.test.ts +187 -0
- package/src/lib/environment/auto-scan.ts +48 -0
- package/src/lib/environment/data.ts +25 -0
- package/src/lib/environment/profile-generator.ts +40 -10
- package/src/lib/environment/profile-linker.ts +143 -0
- package/src/lib/environment/profile-rules.ts +96 -0
- package/src/lib/import/dedup.ts +149 -0
- package/src/lib/import/format-adapter.ts +631 -0
- package/src/lib/import/github-api.ts +219 -0
- package/src/lib/import/repo-scanner.ts +251 -0
- package/src/lib/schedules/__tests__/nlp-parser.test.ts +330 -0
- package/src/lib/schedules/active-hours.ts +120 -0
- package/src/lib/schedules/heartbeat-parser.ts +224 -0
- package/src/lib/schedules/heartbeat-prompt.ts +153 -0
- package/src/lib/schedules/nlp-parser.ts +357 -0
- package/src/lib/schedules/scheduler.ts +218 -3
- package/src/lib/settings/__tests__/budget-guardrails.test.ts +39 -1
- package/src/lib/settings/helpers.ts +6 -0
- package/src/lib/settings/routing.ts +24 -0
- package/src/lib/settings/runtime-setup.ts +28 -1
- package/src/lib/usage/ledger.ts +2 -1
- package/src/lib/validators/__tests__/settings.test.ts +9 -0
- package/src/lib/validators/profile.ts +39 -0
- package/src/lib/workflows/blueprints/builtins/business-daily-briefing.yaml +102 -0
- package/src/lib/workflows/blueprints/builtins/content-marketing-pipeline.yaml +90 -0
- package/src/lib/workflows/blueprints/builtins/customer-support-triage.yaml +107 -0
- package/src/lib/workflows/blueprints/builtins/financial-reporting.yaml +104 -0
- package/src/lib/workflows/blueprints/builtins/lead-research-pipeline.yaml +82 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useCallback } from "react";
|
|
4
|
+
import { Brain } from "lucide-react";
|
|
5
|
+
import { toast } from "sonner";
|
|
6
|
+
import {
|
|
7
|
+
Card,
|
|
8
|
+
CardContent,
|
|
9
|
+
CardDescription,
|
|
10
|
+
CardHeader,
|
|
11
|
+
CardTitle,
|
|
12
|
+
} from "@/components/ui/card";
|
|
13
|
+
import { Slider } from "@/components/ui/slider";
|
|
14
|
+
import { FormSectionCard } from "@/components/shared/form-section-card";
|
|
15
|
+
import {
|
|
16
|
+
Tooltip,
|
|
17
|
+
TooltipContent,
|
|
18
|
+
TooltipProvider,
|
|
19
|
+
TooltipTrigger,
|
|
20
|
+
} from "@/components/ui/tooltip";
|
|
21
|
+
|
|
22
|
+
const DEFAULT_LIMIT = 8000;
|
|
23
|
+
|
|
24
|
+
export function LearningContextSection() {
|
|
25
|
+
const [contextLimit, setContextLimit] = useState(DEFAULT_LIMIT);
|
|
26
|
+
const [saving, setSaving] = useState(false);
|
|
27
|
+
|
|
28
|
+
const fetchSettings = useCallback(async () => {
|
|
29
|
+
try {
|
|
30
|
+
const res = await fetch("/api/settings/learning");
|
|
31
|
+
if (res.ok) {
|
|
32
|
+
const data = await res.json();
|
|
33
|
+
if (data.contextCharLimit)
|
|
34
|
+
setContextLimit(parseInt(data.contextCharLimit, 10));
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
// Use defaults
|
|
38
|
+
}
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
fetchSettings();
|
|
43
|
+
}, [fetchSettings]);
|
|
44
|
+
|
|
45
|
+
const handleSave = async (value: number) => {
|
|
46
|
+
setSaving(true);
|
|
47
|
+
try {
|
|
48
|
+
await fetch("/api/settings/learning", {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: { "Content-Type": "application/json" },
|
|
51
|
+
body: JSON.stringify({ contextCharLimit: String(value) }),
|
|
52
|
+
});
|
|
53
|
+
toast.success("Context memory limit updated");
|
|
54
|
+
} catch {
|
|
55
|
+
toast.error("Failed to save setting");
|
|
56
|
+
} finally {
|
|
57
|
+
setSaving(false);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Card>
|
|
63
|
+
<CardHeader>
|
|
64
|
+
<CardTitle>Self-Learning</CardTitle>
|
|
65
|
+
<CardDescription>
|
|
66
|
+
Configure how agents accumulate and manage learned context from task
|
|
67
|
+
executions.
|
|
68
|
+
</CardDescription>
|
|
69
|
+
</CardHeader>
|
|
70
|
+
<CardContent>
|
|
71
|
+
<TooltipProvider>
|
|
72
|
+
<FormSectionCard
|
|
73
|
+
icon={Brain}
|
|
74
|
+
title="Context Memory Limit"
|
|
75
|
+
hint="Maximum characters of learned context stored per agent profile."
|
|
76
|
+
>
|
|
77
|
+
<div className="space-y-3 w-full">
|
|
78
|
+
<div className="flex items-center justify-between text-sm">
|
|
79
|
+
<span className="text-muted-foreground">Focused</span>
|
|
80
|
+
<Tooltip>
|
|
81
|
+
<TooltipTrigger asChild>
|
|
82
|
+
<span className="font-medium tabular-nums cursor-help">
|
|
83
|
+
{(contextLimit / 1000).toFixed(0)}K characters
|
|
84
|
+
</span>
|
|
85
|
+
</TooltipTrigger>
|
|
86
|
+
<TooltipContent side="top" className="max-w-xs">
|
|
87
|
+
Lower limits keep context tight and focused on recent
|
|
88
|
+
patterns. Higher limits preserve more historical knowledge
|
|
89
|
+
but use more of the AI's context window.
|
|
90
|
+
Auto-summarization kicks in at 75% capacity.
|
|
91
|
+
</TooltipContent>
|
|
92
|
+
</Tooltip>
|
|
93
|
+
<span className="text-muted-foreground">Comprehensive</span>
|
|
94
|
+
</div>
|
|
95
|
+
<div className="relative">
|
|
96
|
+
<div
|
|
97
|
+
className="absolute top-1/2 -translate-y-1/2 h-1.5 rounded-full bg-primary/10"
|
|
98
|
+
style={{
|
|
99
|
+
left: `${((4000 - 2000) / (32000 - 2000)) * 100}%`,
|
|
100
|
+
width: `${((16000 - 4000) / (32000 - 2000)) * 100}%`,
|
|
101
|
+
}}
|
|
102
|
+
/>
|
|
103
|
+
<Slider
|
|
104
|
+
value={[contextLimit]}
|
|
105
|
+
min={2000}
|
|
106
|
+
max={32000}
|
|
107
|
+
step={1000}
|
|
108
|
+
disabled={saving}
|
|
109
|
+
onValueChange={(value) => setContextLimit(value[0])}
|
|
110
|
+
onValueCommit={(value) => handleSave(value[0])}
|
|
111
|
+
/>
|
|
112
|
+
</div>
|
|
113
|
+
<div className="flex justify-center">
|
|
114
|
+
<span className="text-xs text-muted-foreground">
|
|
115
|
+
Recommended: 4K–16K characters
|
|
116
|
+
</span>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
</FormSectionCard>
|
|
120
|
+
</TooltipProvider>
|
|
121
|
+
</CardContent>
|
|
122
|
+
</Card>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from "react";
|
|
4
|
+
import { Download, Loader2, Server } from "lucide-react";
|
|
5
|
+
import { toast } from "sonner";
|
|
6
|
+
import {
|
|
7
|
+
Card,
|
|
8
|
+
CardContent,
|
|
9
|
+
CardDescription,
|
|
10
|
+
CardHeader,
|
|
11
|
+
CardTitle,
|
|
12
|
+
} from "@/components/ui/card";
|
|
13
|
+
import { Button } from "@/components/ui/button";
|
|
14
|
+
import { Input } from "@/components/ui/input";
|
|
15
|
+
import { Label } from "@/components/ui/label";
|
|
16
|
+
|
|
17
|
+
// ── Types ───────────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
interface OllamaModel {
|
|
20
|
+
name: string;
|
|
21
|
+
size: number;
|
|
22
|
+
modified_at: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type ConnectionStatus = "idle" | "testing" | "connected" | "failed";
|
|
26
|
+
|
|
27
|
+
// ── Component ───────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
export function OllamaSection() {
|
|
30
|
+
const [baseUrl, setBaseUrl] = useState("http://localhost:11434");
|
|
31
|
+
const [models, setModels] = useState<OllamaModel[]>([]);
|
|
32
|
+
const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>("idle");
|
|
33
|
+
const [connectionError, setConnectionError] = useState<string | null>(null);
|
|
34
|
+
const [pullModel, setPullModel] = useState("");
|
|
35
|
+
const [pulling, setPulling] = useState(false);
|
|
36
|
+
const [savingUrl, setSavingUrl] = useState(false);
|
|
37
|
+
|
|
38
|
+
// ── Load settings ───────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
async function loadSettings() {
|
|
42
|
+
try {
|
|
43
|
+
const res = await fetch("/api/settings/ollama");
|
|
44
|
+
if (res.ok) {
|
|
45
|
+
const data = await res.json();
|
|
46
|
+
if (data.baseUrl) setBaseUrl(data.baseUrl);
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
// Settings not yet saved, use defaults
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
loadSettings();
|
|
53
|
+
}, []);
|
|
54
|
+
|
|
55
|
+
// ── Fetch models ──────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
const fetchModels = useCallback(async () => {
|
|
58
|
+
try {
|
|
59
|
+
const res = await fetch("/api/runtimes/ollama");
|
|
60
|
+
if (res.ok) {
|
|
61
|
+
const data = await res.json();
|
|
62
|
+
setModels(data.models ?? []);
|
|
63
|
+
setConnectionStatus("connected");
|
|
64
|
+
setConnectionError(null);
|
|
65
|
+
} else {
|
|
66
|
+
const data = await res.json().catch(() => ({}));
|
|
67
|
+
setConnectionStatus("failed");
|
|
68
|
+
setConnectionError(data.error ?? "Failed to connect");
|
|
69
|
+
setModels([]);
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
setConnectionStatus("failed");
|
|
73
|
+
setConnectionError("Cannot reach Ollama API");
|
|
74
|
+
setModels([]);
|
|
75
|
+
}
|
|
76
|
+
}, []);
|
|
77
|
+
|
|
78
|
+
// ── Test connection ───────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
async function handleTestConnection() {
|
|
81
|
+
setConnectionStatus("testing");
|
|
82
|
+
setConnectionError(null);
|
|
83
|
+
await fetchModels();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Save base URL ─────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
async function handleSaveUrl() {
|
|
89
|
+
setSavingUrl(true);
|
|
90
|
+
try {
|
|
91
|
+
const res = await fetch("/api/settings/ollama", {
|
|
92
|
+
method: "POST",
|
|
93
|
+
headers: { "Content-Type": "application/json" },
|
|
94
|
+
body: JSON.stringify({ baseUrl }),
|
|
95
|
+
});
|
|
96
|
+
if (res.ok) {
|
|
97
|
+
toast.success("Ollama base URL saved");
|
|
98
|
+
// Re-test connection with new URL
|
|
99
|
+
await fetchModels();
|
|
100
|
+
} else {
|
|
101
|
+
toast.error("Failed to save base URL");
|
|
102
|
+
}
|
|
103
|
+
} finally {
|
|
104
|
+
setSavingUrl(false);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── Pull model ────────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
async function handlePullModel() {
|
|
111
|
+
if (!pullModel.trim()) return;
|
|
112
|
+
setPulling(true);
|
|
113
|
+
try {
|
|
114
|
+
const res = await fetch("/api/runtimes/ollama", {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: { "Content-Type": "application/json" },
|
|
117
|
+
body: JSON.stringify({ action: "pull", model: pullModel.trim() }),
|
|
118
|
+
});
|
|
119
|
+
if (res.ok) {
|
|
120
|
+
toast.success(`Model "${pullModel.trim()}" pulled successfully`);
|
|
121
|
+
setPullModel("");
|
|
122
|
+
await fetchModels();
|
|
123
|
+
} else {
|
|
124
|
+
const data = await res.json().catch(() => ({}));
|
|
125
|
+
toast.error(data.error ?? "Failed to pull model");
|
|
126
|
+
}
|
|
127
|
+
} finally {
|
|
128
|
+
setPulling(false);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── Format file size ──────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
function formatSize(bytes: number): string {
|
|
135
|
+
if (bytes < 1e9) return `${(bytes / 1e6).toFixed(0)} MB`;
|
|
136
|
+
return `${(bytes / 1e9).toFixed(1)} GB`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ── Connection status dot ─────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
const statusDot =
|
|
142
|
+
connectionStatus === "connected"
|
|
143
|
+
? "bg-success"
|
|
144
|
+
: connectionStatus === "failed"
|
|
145
|
+
? "bg-destructive"
|
|
146
|
+
: connectionStatus === "testing"
|
|
147
|
+
? "bg-warning animate-pulse"
|
|
148
|
+
: "border-2 border-muted-foreground/40";
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<Card className="surface-card">
|
|
152
|
+
<CardHeader>
|
|
153
|
+
<CardTitle className="flex items-center gap-2">
|
|
154
|
+
<Server className="h-5 w-5" />
|
|
155
|
+
Ollama (Local Models)
|
|
156
|
+
</CardTitle>
|
|
157
|
+
<CardDescription>
|
|
158
|
+
Run models locally with Ollama — free, private, no API key required.
|
|
159
|
+
</CardDescription>
|
|
160
|
+
</CardHeader>
|
|
161
|
+
|
|
162
|
+
<CardContent className="space-y-4">
|
|
163
|
+
{/* Base URL */}
|
|
164
|
+
<div className="space-y-2">
|
|
165
|
+
<Label htmlFor="ollama-url">Base URL</Label>
|
|
166
|
+
<div className="flex gap-2">
|
|
167
|
+
<Input
|
|
168
|
+
id="ollama-url"
|
|
169
|
+
value={baseUrl}
|
|
170
|
+
onChange={(e) => setBaseUrl(e.target.value)}
|
|
171
|
+
placeholder="http://localhost:11434"
|
|
172
|
+
className="flex-1"
|
|
173
|
+
/>
|
|
174
|
+
<Button
|
|
175
|
+
variant="outline"
|
|
176
|
+
size="sm"
|
|
177
|
+
onClick={handleSaveUrl}
|
|
178
|
+
disabled={savingUrl}
|
|
179
|
+
>
|
|
180
|
+
{savingUrl ? <Loader2 className="h-4 w-4 animate-spin" /> : "Save"}
|
|
181
|
+
</Button>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
{/* Connection test */}
|
|
186
|
+
<div className="flex items-center gap-3">
|
|
187
|
+
<Button
|
|
188
|
+
variant="outline"
|
|
189
|
+
size="sm"
|
|
190
|
+
onClick={handleTestConnection}
|
|
191
|
+
disabled={connectionStatus === "testing"}
|
|
192
|
+
>
|
|
193
|
+
{connectionStatus === "testing" ? (
|
|
194
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
195
|
+
) : null}
|
|
196
|
+
Test Connection
|
|
197
|
+
</Button>
|
|
198
|
+
|
|
199
|
+
<div className="flex items-center gap-2">
|
|
200
|
+
<div className={`h-2.5 w-2.5 rounded-full ${statusDot}`} />
|
|
201
|
+
<span className="text-sm text-muted-foreground">
|
|
202
|
+
{connectionStatus === "connected" && `Connected — ${models.length} model${models.length !== 1 ? "s" : ""} available`}
|
|
203
|
+
{connectionStatus === "failed" && (connectionError ?? "Not connected")}
|
|
204
|
+
{connectionStatus === "testing" && "Testing..."}
|
|
205
|
+
{connectionStatus === "idle" && "Not tested"}
|
|
206
|
+
</span>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
{/* Available models list */}
|
|
211
|
+
{models.length > 0 && (
|
|
212
|
+
<div className="space-y-1">
|
|
213
|
+
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
|
|
214
|
+
Available Models
|
|
215
|
+
</p>
|
|
216
|
+
<div className="rounded-xl border border-border/60 divide-y divide-border/40">
|
|
217
|
+
{models.map((m) => (
|
|
218
|
+
<div
|
|
219
|
+
key={m.name}
|
|
220
|
+
className="flex items-center justify-between px-3 py-2"
|
|
221
|
+
>
|
|
222
|
+
<span className="text-sm font-medium">{m.name}</span>
|
|
223
|
+
<span className="text-xs text-muted-foreground">
|
|
224
|
+
{formatSize(m.size)}
|
|
225
|
+
</span>
|
|
226
|
+
</div>
|
|
227
|
+
))}
|
|
228
|
+
</div>
|
|
229
|
+
<p className="text-xs text-muted-foreground">
|
|
230
|
+
To set a default Ollama model for chat, use the Chat section above.
|
|
231
|
+
</p>
|
|
232
|
+
</div>
|
|
233
|
+
)}
|
|
234
|
+
|
|
235
|
+
{/* Pull model */}
|
|
236
|
+
<div className="space-y-2">
|
|
237
|
+
<Label htmlFor="ollama-pull">Pull a Model</Label>
|
|
238
|
+
<div className="flex gap-2">
|
|
239
|
+
<Input
|
|
240
|
+
id="ollama-pull"
|
|
241
|
+
value={pullModel}
|
|
242
|
+
onChange={(e) => setPullModel(e.target.value)}
|
|
243
|
+
placeholder="e.g., llama3.2, mistral, codellama"
|
|
244
|
+
className="flex-1"
|
|
245
|
+
onKeyDown={(e) => {
|
|
246
|
+
if (e.key === "Enter") handlePullModel();
|
|
247
|
+
}}
|
|
248
|
+
/>
|
|
249
|
+
<Button
|
|
250
|
+
variant="outline"
|
|
251
|
+
size="sm"
|
|
252
|
+
onClick={handlePullModel}
|
|
253
|
+
disabled={pulling || !pullModel.trim()}
|
|
254
|
+
>
|
|
255
|
+
{pulling ? (
|
|
256
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
257
|
+
) : (
|
|
258
|
+
<Download className="mr-2 h-4 w-4" />
|
|
259
|
+
)}
|
|
260
|
+
Pull
|
|
261
|
+
</Button>
|
|
262
|
+
</div>
|
|
263
|
+
<p className="text-xs text-muted-foreground">
|
|
264
|
+
Download models from the Ollama library. This may take several minutes for large models.
|
|
265
|
+
</p>
|
|
266
|
+
</div>
|
|
267
|
+
</CardContent>
|
|
268
|
+
</Card>
|
|
269
|
+
);
|
|
270
|
+
}
|