@gmickel/gno 0.28.2 → 0.29.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 +10 -2
- package/package.json +1 -1
- package/src/app/constants.ts +4 -2
- package/src/cli/commands/mcp/install.ts +4 -4
- package/src/cli/commands/mcp/status.ts +7 -7
- package/src/cli/commands/skill/install.ts +5 -5
- package/src/cli/program.ts +2 -2
- package/src/collection/add.ts +10 -0
- package/src/collection/types.ts +1 -0
- package/src/config/types.ts +12 -2
- package/src/core/depth-policy.ts +1 -1
- package/src/core/file-ops.ts +38 -0
- package/src/llm/registry.ts +20 -4
- package/src/serve/AGENTS.md +16 -16
- package/src/serve/CLAUDE.md +16 -16
- package/src/serve/config-sync.ts +32 -1
- package/src/serve/connectors.ts +243 -0
- package/src/serve/context.ts +9 -0
- package/src/serve/doc-events.ts +31 -1
- package/src/serve/embed-scheduler.ts +12 -0
- package/src/serve/import-preview.ts +173 -0
- package/src/serve/public/app.tsx +101 -7
- package/src/serve/public/components/AIModelSelector.tsx +383 -145
- package/src/serve/public/components/AddCollectionDialog.tsx +123 -7
- package/src/serve/public/components/BootstrapStatus.tsx +133 -0
- package/src/serve/public/components/CaptureModal.tsx +5 -2
- package/src/serve/public/components/CollectionsEmptyState.tsx +63 -0
- package/src/serve/public/components/FirstRunWizard.tsx +622 -0
- package/src/serve/public/components/HealthCenter.tsx +128 -0
- package/src/serve/public/components/IndexingProgress.tsx +21 -2
- package/src/serve/public/components/QuickSwitcher.tsx +62 -36
- package/src/serve/public/components/TagInput.tsx +5 -1
- package/src/serve/public/components/WikiLinkAutocomplete.tsx +15 -6
- package/src/serve/public/components/WorkspaceTabs.tsx +60 -0
- package/src/serve/public/hooks/use-doc-events.ts +48 -4
- package/src/serve/public/lib/local-history.ts +40 -7
- package/src/serve/public/lib/navigation-state.ts +156 -0
- package/src/serve/public/lib/workspace-tabs.ts +235 -0
- package/src/serve/public/pages/Ask.tsx +11 -1
- package/src/serve/public/pages/Browse.tsx +73 -0
- package/src/serve/public/pages/Collections.tsx +29 -13
- package/src/serve/public/pages/Connectors.tsx +178 -0
- package/src/serve/public/pages/Dashboard.tsx +493 -67
- package/src/serve/public/pages/DocView.tsx +192 -34
- package/src/serve/public/pages/DocumentEditor.tsx +127 -5
- package/src/serve/public/pages/Search.tsx +12 -1
- package/src/serve/routes/api.ts +532 -62
- package/src/serve/server.ts +79 -2
- package/src/serve/status-model.ts +149 -0
- package/src/serve/status.ts +706 -0
- package/src/serve/watch-service.ts +73 -8
- package/src/types/electrobun-shell.d.ts +43 -0
package/README.md
CHANGED
|
@@ -34,7 +34,13 @@ GNO is a local knowledge engine that turns your documents into a searchable, con
|
|
|
34
34
|
|
|
35
35
|
---
|
|
36
36
|
|
|
37
|
-
## What's New in v0.
|
|
37
|
+
## What's New in v0.29
|
|
38
|
+
|
|
39
|
+
- **GNO Desktop Beta**: first mac-first desktop beta shell with deep-link routing, singleton handoff, and the same onboarding/search/edit flows as `gno serve`
|
|
40
|
+
- **Desktop Onboarding Polish**: guided setup now covers folders, presets, model readiness, indexing, connectors, import preview, app tabs, file actions, and recovery without drift between web and desktop
|
|
41
|
+
- **Default Preset Upgrade**: `slim-tuned` is now the built-in default, using the fine-tuned retrieval expansion model while keeping the same embed, rerank, and answer stack as `slim`
|
|
42
|
+
|
|
43
|
+
### v0.24
|
|
38
44
|
|
|
39
45
|
- **Structured Query Documents**: first-class multi-line query syntax using `term:`, `intent:`, and `hyde:`
|
|
40
46
|
- **Cross-Surface Rollout**: works across CLI, API, MCP, SDK, and Web Search/Ask
|
|
@@ -65,7 +71,7 @@ models:
|
|
|
65
71
|
embed: hf:gpustack/bge-m3-GGUF/bge-m3-Q4_K_M.gguf
|
|
66
72
|
rerank: hf:ggml-org/Qwen3-Reranker-0.6B-Q8_0-GGUF/qwen3-reranker-0.6b-q8_0.gguf
|
|
67
73
|
expand: hf:guiltylemon/gno-expansion-slim-retrieval-v1/gno-expansion-auto-entity-lock-default-mix-lr95-f16.gguf
|
|
68
|
-
gen: hf:unsloth/Qwen3-
|
|
74
|
+
gen: hf:unsloth/Qwen3-1.7B-GGUF/Qwen3-1.7B-Q4_K_M.gguf
|
|
69
75
|
```
|
|
70
76
|
|
|
71
77
|
Then:
|
|
@@ -373,6 +379,8 @@ Open `http://localhost:3000` to:
|
|
|
373
379
|
- **Edit**: Create, edit, and delete documents with live preview
|
|
374
380
|
- **Ask**: AI-powered Q&A with citations
|
|
375
381
|
- **Manage Collections**: Add, remove, and re-index collections
|
|
382
|
+
- **Connect agents**: Install core Skill/MCP integrations from the app
|
|
383
|
+
- **Manage files safely**: Rename, reveal, or move editable files to Trash with explicit index-vs-disk semantics
|
|
376
384
|
- **Switch presets**: Change models live without restart
|
|
377
385
|
|
|
378
386
|
### Search
|
package/package.json
CHANGED
package/src/app/constants.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { homedir, platform } from "node:os";
|
|
9
|
-
import { join } from "node:path";
|
|
9
|
+
import { basename, join } from "node:path";
|
|
10
10
|
|
|
11
11
|
// Bun supports JSON imports natively - version single source of truth
|
|
12
12
|
import pkg from "../../package.json";
|
|
@@ -232,7 +232,9 @@ export function getConfigPath(dirs: ResolvedDirs = resolveDirs()): string {
|
|
|
232
232
|
* Get path to models cache directory.
|
|
233
233
|
*/
|
|
234
234
|
export function getModelsCachePath(dirs: ResolvedDirs = resolveDirs()): string {
|
|
235
|
-
return
|
|
235
|
+
return basename(dirs.cache) === "models"
|
|
236
|
+
? dirs.cache
|
|
237
|
+
: join(dirs.cache, "models");
|
|
236
238
|
}
|
|
237
239
|
|
|
238
240
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -46,7 +46,7 @@ export interface InstallOptions {
|
|
|
46
46
|
quiet?: boolean;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
interface
|
|
49
|
+
export interface McpInstallResult {
|
|
50
50
|
target: McpTarget;
|
|
51
51
|
scope: McpScope;
|
|
52
52
|
configPath: string;
|
|
@@ -61,7 +61,7 @@ interface InstallResult {
|
|
|
61
61
|
/**
|
|
62
62
|
* Install gno to a single target.
|
|
63
63
|
*/
|
|
64
|
-
async function
|
|
64
|
+
export async function installMcpToTarget(
|
|
65
65
|
target: McpTarget,
|
|
66
66
|
scope: McpScope,
|
|
67
67
|
serverEntry: StandardMcpEntry,
|
|
@@ -71,7 +71,7 @@ async function installToTarget(
|
|
|
71
71
|
cwd?: string;
|
|
72
72
|
homeDir?: string;
|
|
73
73
|
}
|
|
74
|
-
): Promise<
|
|
74
|
+
): Promise<McpInstallResult> {
|
|
75
75
|
const { force = false, dryRun = false, cwd, homeDir } = options;
|
|
76
76
|
|
|
77
77
|
const { configPath, configFormat } = resolveMcpConfigPath({
|
|
@@ -165,7 +165,7 @@ export async function installMcp(opts: InstallOptions = {}): Promise<void> {
|
|
|
165
165
|
const serverEntry = buildMcpServerEntry({ enableWrite });
|
|
166
166
|
|
|
167
167
|
// Install
|
|
168
|
-
const result = await
|
|
168
|
+
const result = await installMcpToTarget(target, scope, serverEntry, {
|
|
169
169
|
force,
|
|
170
170
|
dryRun,
|
|
171
171
|
cwd: opts.cwd,
|
|
@@ -37,7 +37,7 @@ export interface StatusOptions {
|
|
|
37
37
|
json?: boolean;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
interface
|
|
40
|
+
export interface McpTargetStatus {
|
|
41
41
|
target: McpTarget;
|
|
42
42
|
scope: McpScope;
|
|
43
43
|
configPath: string;
|
|
@@ -47,7 +47,7 @@ interface TargetStatus {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
interface StatusResult {
|
|
50
|
-
targets:
|
|
50
|
+
targets: McpTargetStatus[];
|
|
51
51
|
summary: {
|
|
52
52
|
configured: number;
|
|
53
53
|
total: number;
|
|
@@ -72,11 +72,11 @@ function normalizeEntry(
|
|
|
72
72
|
return entry as StandardMcpEntry;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
async function
|
|
75
|
+
export async function checkMcpTargetStatus(
|
|
76
76
|
target: McpTarget,
|
|
77
77
|
scope: McpScope,
|
|
78
78
|
options: { cwd?: string; homeDir?: string }
|
|
79
|
-
): Promise<
|
|
79
|
+
): Promise<McpTargetStatus> {
|
|
80
80
|
const { cwd, homeDir } = options;
|
|
81
81
|
|
|
82
82
|
const { configPath, configFormat } = resolveMcpConfigPath({
|
|
@@ -151,7 +151,7 @@ export async function statusMcp(opts: StatusOptions = {}): Promise<void> {
|
|
|
151
151
|
const targets: McpTarget[] =
|
|
152
152
|
targetFilter === "all" ? MCP_TARGETS : [targetFilter];
|
|
153
153
|
|
|
154
|
-
const results:
|
|
154
|
+
const results: McpTargetStatus[] = [];
|
|
155
155
|
|
|
156
156
|
for (const target of targets) {
|
|
157
157
|
const supportsProject = TARGETS_WITH_PROJECT_SCOPE.includes(target);
|
|
@@ -163,7 +163,7 @@ export async function statusMcp(opts: StatusOptions = {}): Promise<void> {
|
|
|
163
163
|
|
|
164
164
|
for (const scope of scopes) {
|
|
165
165
|
results.push(
|
|
166
|
-
await
|
|
166
|
+
await checkMcpTargetStatus(target, scope, {
|
|
167
167
|
cwd: opts.cwd,
|
|
168
168
|
homeDir: opts.homeDir,
|
|
169
169
|
})
|
|
@@ -172,7 +172,7 @@ export async function statusMcp(opts: StatusOptions = {}): Promise<void> {
|
|
|
172
172
|
} else if (scopeFilter === "all" || scopeFilter === "user") {
|
|
173
173
|
// User scope only - skip if filtering by project
|
|
174
174
|
results.push(
|
|
175
|
-
await
|
|
175
|
+
await checkMcpTargetStatus(target, "user", {
|
|
176
176
|
cwd: opts.cwd,
|
|
177
177
|
homeDir: opts.homeDir,
|
|
178
178
|
})
|
|
@@ -54,7 +54,7 @@ export interface InstallOptions {
|
|
|
54
54
|
quiet?: boolean;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
interface
|
|
57
|
+
export interface SkillInstallResult {
|
|
58
58
|
target: SkillTarget;
|
|
59
59
|
scope: SkillScope;
|
|
60
60
|
path: string;
|
|
@@ -63,12 +63,12 @@ interface InstallResult {
|
|
|
63
63
|
/**
|
|
64
64
|
* Install skill to a single target.
|
|
65
65
|
*/
|
|
66
|
-
async function
|
|
66
|
+
export async function installSkillToTarget(
|
|
67
67
|
scope: SkillScope,
|
|
68
68
|
target: SkillTarget,
|
|
69
69
|
force: boolean,
|
|
70
70
|
overrides?: { cwd?: string; homeDir?: string }
|
|
71
|
-
): Promise<
|
|
71
|
+
): Promise<SkillInstallResult> {
|
|
72
72
|
const sourceDir = getSkillSourceDir();
|
|
73
73
|
const paths = resolveSkillPaths({ scope, target, ...overrides });
|
|
74
74
|
|
|
@@ -174,10 +174,10 @@ export async function installSkill(opts: InstallOptions = {}): Promise<void> {
|
|
|
174
174
|
|
|
175
175
|
const targets: SkillTarget[] = target === "all" ? SKILL_TARGETS : [target];
|
|
176
176
|
|
|
177
|
-
const results:
|
|
177
|
+
const results: SkillInstallResult[] = [];
|
|
178
178
|
|
|
179
179
|
for (const t of targets) {
|
|
180
|
-
const result = await
|
|
180
|
+
const result = await installSkillToTarget(scope, t, force || yes, {
|
|
181
181
|
cwd: opts.cwd,
|
|
182
182
|
homeDir: opts.homeDir,
|
|
183
183
|
});
|
package/src/cli/program.ts
CHANGED
|
@@ -542,7 +542,7 @@ function wireSearchCommands(program: Command): void {
|
|
|
542
542
|
const configResult = await loadConfig(globals.config);
|
|
543
543
|
const activePresetId = configResult.ok
|
|
544
544
|
? getActivePreset(configResult.value).id
|
|
545
|
-
: "slim";
|
|
545
|
+
: "slim-tuned";
|
|
546
546
|
const candidateLimit = cmdOpts.candidateLimit
|
|
547
547
|
? parsePositiveInt("candidate-limit", cmdOpts.candidateLimit)
|
|
548
548
|
: undefined;
|
|
@@ -655,7 +655,7 @@ function wireSearchCommands(program: Command): void {
|
|
|
655
655
|
const configResult = await loadConfig(globals.config);
|
|
656
656
|
const activePresetId = configResult.ok
|
|
657
657
|
? getActivePreset(configResult.value).id
|
|
658
|
-
: "slim";
|
|
658
|
+
: "slim-tuned";
|
|
659
659
|
const candidateLimit = cmdOpts.candidateLimit
|
|
660
660
|
? parsePositiveInt("candidate-limit", cmdOpts.candidateLimit)
|
|
661
661
|
: undefined;
|
package/src/collection/add.ts
CHANGED
|
@@ -73,6 +73,16 @@ export async function addCollection(
|
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// Check for duplicate path
|
|
77
|
+
const existingPath = config.collections.find((c) => c.path === absolutePath);
|
|
78
|
+
if (existingPath) {
|
|
79
|
+
return {
|
|
80
|
+
ok: false,
|
|
81
|
+
code: "DUPLICATE_PATH",
|
|
82
|
+
message: `Path is already indexed by collection "${existingPath.name}"`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
76
86
|
// Parse include/exclude lists
|
|
77
87
|
const includeList = parseList(input.include);
|
|
78
88
|
const excludeList =
|
package/src/collection/types.ts
CHANGED
package/src/config/types.ts
CHANGED
|
@@ -176,9 +176,19 @@ export type ModelPreset = z.infer<typeof ModelPresetSchema>;
|
|
|
176
176
|
|
|
177
177
|
/** Default model presets */
|
|
178
178
|
export const DEFAULT_MODEL_PRESETS: ModelPreset[] = [
|
|
179
|
+
{
|
|
180
|
+
id: "slim-tuned",
|
|
181
|
+
name: "GNO Slim Tuned (Default, ~1GB)",
|
|
182
|
+
embed: "hf:gpustack/bge-m3-GGUF/bge-m3-Q4_K_M.gguf",
|
|
183
|
+
rerank:
|
|
184
|
+
"hf:ggml-org/Qwen3-Reranker-0.6B-Q8_0-GGUF/qwen3-reranker-0.6b-q8_0.gguf",
|
|
185
|
+
expand:
|
|
186
|
+
"hf:guiltylemon/gno-expansion-slim-retrieval-v1/gno-expansion-auto-entity-lock-default-mix-lr95-f16.gguf",
|
|
187
|
+
gen: "hf:unsloth/Qwen3-1.7B-GGUF/Qwen3-1.7B-Q4_K_M.gguf",
|
|
188
|
+
},
|
|
179
189
|
{
|
|
180
190
|
id: "slim",
|
|
181
|
-
name: "Slim (
|
|
191
|
+
name: "Slim (~1GB)",
|
|
182
192
|
embed: "hf:gpustack/bge-m3-GGUF/bge-m3-Q4_K_M.gguf",
|
|
183
193
|
rerank:
|
|
184
194
|
"hf:ggml-org/Qwen3-Reranker-0.6B-Q8_0-GGUF/qwen3-reranker-0.6b-q8_0.gguf",
|
|
@@ -209,7 +219,7 @@ export const DEFAULT_MODEL_PRESETS: ModelPreset[] = [
|
|
|
209
219
|
|
|
210
220
|
export const ModelConfigSchema = z.object({
|
|
211
221
|
/** Active preset ID */
|
|
212
|
-
activePreset: z.string().default("slim"),
|
|
222
|
+
activePreset: z.string().default("slim-tuned"),
|
|
213
223
|
/** Model presets */
|
|
214
224
|
presets: z.array(ModelPresetSchema).default(DEFAULT_MODEL_PRESETS),
|
|
215
225
|
/** Model load timeout in ms */
|
package/src/core/depth-policy.ts
CHANGED
|
@@ -21,7 +21,7 @@ export interface ResolvedDepthPolicy {
|
|
|
21
21
|
export const DEFAULT_THOROUGH_CANDIDATE_LIMIT = 40;
|
|
22
22
|
|
|
23
23
|
function normalizePresetId(presetId?: string): string {
|
|
24
|
-
return presetId?.trim().toLowerCase() || "slim";
|
|
24
|
+
return presetId?.trim().toLowerCase() || "slim-tuned";
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export function balancedUsesExpansion(presetId?: string): boolean {
|
package/src/core/file-ops.ts
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
// node:fs/promises for rename/unlink (no Bun equivalent for structure ops)
|
|
8
8
|
import { rename, unlink } from "node:fs/promises";
|
|
9
|
+
// node:os platform: no Bun equivalent
|
|
10
|
+
import { platform } from "node:os";
|
|
11
|
+
// node:path dirname: no Bun equivalent
|
|
12
|
+
import { dirname } from "node:path";
|
|
9
13
|
|
|
10
14
|
export async function atomicWrite(
|
|
11
15
|
path: string,
|
|
@@ -22,3 +26,37 @@ export async function atomicWrite(
|
|
|
22
26
|
throw e;
|
|
23
27
|
}
|
|
24
28
|
}
|
|
29
|
+
|
|
30
|
+
async function runCommand(cmd: string[]): Promise<void> {
|
|
31
|
+
const proc = Bun.spawn({
|
|
32
|
+
cmd,
|
|
33
|
+
stdout: "pipe",
|
|
34
|
+
stderr: "pipe",
|
|
35
|
+
});
|
|
36
|
+
const exitCode = await proc.exited;
|
|
37
|
+
if (exitCode === 0) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const stderr = await new Response(proc.stderr).text();
|
|
41
|
+
throw new Error(stderr.trim() || `Command failed: ${cmd.join(" ")}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function renameFilePath(
|
|
45
|
+
currentPath: string,
|
|
46
|
+
nextPath: string
|
|
47
|
+
): Promise<void> {
|
|
48
|
+
await rename(currentPath, nextPath);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function trashFilePath(path: string): Promise<void> {
|
|
52
|
+
await runCommand(["trash", path]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function revealFilePath(path: string): Promise<void> {
|
|
56
|
+
if (platform() === "darwin") {
|
|
57
|
+
await runCommand(["open", "-R", path]);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await runCommand(["xdg-open", dirname(path)]);
|
|
62
|
+
}
|
package/src/llm/registry.ts
CHANGED
|
@@ -18,11 +18,27 @@ import { DEFAULT_MODEL_PRESETS } from "../config/types";
|
|
|
18
18
|
* Get model config with defaults.
|
|
19
19
|
*/
|
|
20
20
|
export function getModelConfig(config: Config): ModelConfig {
|
|
21
|
+
const customPresets = config.models?.presets ?? [];
|
|
22
|
+
const presetsById = new Map(
|
|
23
|
+
DEFAULT_MODEL_PRESETS.map((preset) => [preset.id, preset] as const)
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
for (const preset of customPresets) {
|
|
27
|
+
presetsById.set(preset.id, preset);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const mergedPresets = [
|
|
31
|
+
...DEFAULT_MODEL_PRESETS.map(
|
|
32
|
+
(preset) => presetsById.get(preset.id) ?? preset
|
|
33
|
+
),
|
|
34
|
+
...customPresets.filter(
|
|
35
|
+
(preset) => !DEFAULT_MODEL_PRESETS.some((base) => base.id === preset.id)
|
|
36
|
+
),
|
|
37
|
+
];
|
|
38
|
+
|
|
21
39
|
return {
|
|
22
|
-
activePreset: config.models?.activePreset ?? "slim",
|
|
23
|
-
presets:
|
|
24
|
-
? config.models.presets
|
|
25
|
-
: DEFAULT_MODEL_PRESETS,
|
|
40
|
+
activePreset: config.models?.activePreset ?? "slim-tuned",
|
|
41
|
+
presets: mergedPresets,
|
|
26
42
|
loadTimeout: config.models?.loadTimeout ?? 60_000,
|
|
27
43
|
inferenceTimeout: config.models?.inferenceTimeout ?? 30_000,
|
|
28
44
|
expandContextSize: config.models?.expandContextSize ?? 2_048,
|
package/src/serve/AGENTS.md
CHANGED
|
@@ -65,22 +65,22 @@ Answer generation uses shared module to stay in sync with CLI:
|
|
|
65
65
|
|
|
66
66
|
## API Endpoints
|
|
67
67
|
|
|
68
|
-
| Endpoint | Method | Description
|
|
69
|
-
| -------------------- | ------ |
|
|
70
|
-
| `/api/health` | GET | Health check
|
|
71
|
-
| `/api/status` | GET | Index stats,
|
|
72
|
-
| `/api/capabilities` | GET | Available features
|
|
73
|
-
| `/api/collections` | GET | List collections
|
|
74
|
-
| `/api/docs` | GET | List documents
|
|
75
|
-
| `/api/doc` | GET | Get document content
|
|
76
|
-
| `/api/search` | POST | BM25 search
|
|
77
|
-
| `/api/query` | POST | Hybrid search
|
|
78
|
-
| `/api/ask` | POST | AI answer with citations
|
|
79
|
-
| `/api/presets` | GET | List model presets
|
|
80
|
-
| `/api/presets` | POST | Switch preset (hot-reload)
|
|
81
|
-
| `/api/models/status` | GET | Download progress
|
|
82
|
-
| `/api/models/pull` | POST | Start model download
|
|
83
|
-
| `/api/tags` | GET | List tags (with counts)
|
|
68
|
+
| Endpoint | Method | Description |
|
|
69
|
+
| -------------------- | ------ | ------------------------------------------ |
|
|
70
|
+
| `/api/health` | GET | Health check |
|
|
71
|
+
| `/api/status` | GET | Index stats, onboarding, health, bootstrap |
|
|
72
|
+
| `/api/capabilities` | GET | Available features |
|
|
73
|
+
| `/api/collections` | GET | List collections |
|
|
74
|
+
| `/api/docs` | GET | List documents |
|
|
75
|
+
| `/api/doc` | GET | Get document content |
|
|
76
|
+
| `/api/search` | POST | BM25 search |
|
|
77
|
+
| `/api/query` | POST | Hybrid search |
|
|
78
|
+
| `/api/ask` | POST | AI answer with citations |
|
|
79
|
+
| `/api/presets` | GET | List model presets |
|
|
80
|
+
| `/api/presets` | POST | Switch preset (hot-reload) |
|
|
81
|
+
| `/api/models/status` | GET | Download progress |
|
|
82
|
+
| `/api/models/pull` | POST | Start model download |
|
|
83
|
+
| `/api/tags` | GET | List tags (with counts) |
|
|
84
84
|
|
|
85
85
|
## Frontend
|
|
86
86
|
|
package/src/serve/CLAUDE.md
CHANGED
|
@@ -65,22 +65,22 @@ Answer generation uses shared module to stay in sync with CLI:
|
|
|
65
65
|
|
|
66
66
|
## API Endpoints
|
|
67
67
|
|
|
68
|
-
| Endpoint | Method | Description
|
|
69
|
-
| -------------------- | ------ |
|
|
70
|
-
| `/api/health` | GET | Health check
|
|
71
|
-
| `/api/status` | GET | Index stats,
|
|
72
|
-
| `/api/capabilities` | GET | Available features
|
|
73
|
-
| `/api/collections` | GET | List collections
|
|
74
|
-
| `/api/docs` | GET | List documents
|
|
75
|
-
| `/api/doc` | GET | Get document content
|
|
76
|
-
| `/api/search` | POST | BM25 search
|
|
77
|
-
| `/api/query` | POST | Hybrid search
|
|
78
|
-
| `/api/ask` | POST | AI answer with citations
|
|
79
|
-
| `/api/presets` | GET | List model presets
|
|
80
|
-
| `/api/presets` | POST | Switch preset (hot-reload)
|
|
81
|
-
| `/api/models/status` | GET | Download progress
|
|
82
|
-
| `/api/models/pull` | POST | Start model download
|
|
83
|
-
| `/api/tags` | GET | List tags (with counts)
|
|
68
|
+
| Endpoint | Method | Description |
|
|
69
|
+
| -------------------- | ------ | ------------------------------------------ |
|
|
70
|
+
| `/api/health` | GET | Health check |
|
|
71
|
+
| `/api/status` | GET | Index stats, onboarding, health, bootstrap |
|
|
72
|
+
| `/api/capabilities` | GET | Available features |
|
|
73
|
+
| `/api/collections` | GET | List collections |
|
|
74
|
+
| `/api/docs` | GET | List documents |
|
|
75
|
+
| `/api/doc` | GET | Get document content |
|
|
76
|
+
| `/api/search` | POST | BM25 search |
|
|
77
|
+
| `/api/query` | POST | Hybrid search |
|
|
78
|
+
| `/api/ask` | POST | AI answer with citations |
|
|
79
|
+
| `/api/presets` | GET | List model presets |
|
|
80
|
+
| `/api/presets` | POST | Switch preset (hot-reload) |
|
|
81
|
+
| `/api/models/status` | GET | Download progress |
|
|
82
|
+
| `/api/models/pull` | POST | Start model download |
|
|
83
|
+
| `/api/tags` | GET | List tags (with counts) |
|
|
84
84
|
|
|
85
85
|
## Frontend
|
|
86
86
|
|
package/src/serve/config-sync.ts
CHANGED
|
@@ -40,7 +40,7 @@ export async function applyConfigChange(
|
|
|
40
40
|
mutate: (config: Config) => Promise<MutationResult> | MutationResult,
|
|
41
41
|
configPath?: string
|
|
42
42
|
): Promise<ApplyConfigResult> {
|
|
43
|
-
|
|
43
|
+
const result = await applyConfigChangeCore(
|
|
44
44
|
{
|
|
45
45
|
store,
|
|
46
46
|
configPath,
|
|
@@ -51,4 +51,35 @@ export async function applyConfigChange(
|
|
|
51
51
|
},
|
|
52
52
|
mutate
|
|
53
53
|
);
|
|
54
|
+
|
|
55
|
+
if (result.ok) {
|
|
56
|
+
ctxHolder.watchService?.updateCollections(ctxHolder.config.collections);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function applyConfigChangeTyped<T>(
|
|
63
|
+
ctxHolder: ContextHolder,
|
|
64
|
+
store: SqliteAdapter,
|
|
65
|
+
mutate: (config: Config) => Promise<MutationResult<T>> | MutationResult<T>,
|
|
66
|
+
configPath?: string
|
|
67
|
+
): Promise<ApplyConfigResult<T>> {
|
|
68
|
+
const result = await applyConfigChangeCore(
|
|
69
|
+
{
|
|
70
|
+
store,
|
|
71
|
+
configPath,
|
|
72
|
+
onConfigUpdated: (config) => {
|
|
73
|
+
ctxHolder.config = config;
|
|
74
|
+
ctxHolder.current = { ...ctxHolder.current, config };
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
mutate
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
if (result.ok) {
|
|
81
|
+
ctxHolder.watchService?.updateCollections(ctxHolder.config.collections);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return result;
|
|
54
85
|
}
|