feat: edit qdrant workspace collections

This commit is contained in:
2026-08-08 19:56:29 +02:00
parent af1e922a48
commit c3a5621737
15 changed files with 234 additions and 198 deletions
+24 -4
View File
@@ -3,17 +3,16 @@ import type { CanonicalWorkspace } from "../api/workspaces";
import { workspaceDeletionDrafts, workspaceDrafts, workspacePreferences } from "./drafts";
const workspace: CanonicalWorkspace = {
workspace: { schema_version: 2, id: "psd-clinical", name: "PSD Clinical", language: "en" },
workspace: { schema_version: 3, id: "psd-clinical", name: "PSD Clinical", language: "en" },
dwh: {
engine: "postgres", database: "clinical", schema: "datawarehouse",
supported_transports: ["postgres_direct"],
},
semantic_index: {
vector_store: {
engine: "pgvector", database: "vectors", schema: "public", collection: "clinical",
dimensions: 768, distance: "cosine", supported_transports: ["pgvector_direct"],
engine: "qdrant", collection: "clinical", dimensions: 1024, distance: "cosine",
},
embedding: { provider: "ollama_compatible", model: "nomic-embed-text", dimensions: 768 },
embedding: { provider: "ollama_internal", model: "qwen3-embedding:0.6b", dimensions: 1024 },
},
llm_policy: { allowed: ["zai/glm-5.2"] },
};
@@ -126,3 +125,24 @@ test.each([
expect(workspaceDrafts.load("psd-clinical")).toBeUndefined();
expect(localStorage.getItem("thothii.workspace-registry.v1.draft.psd-clinical")).toBeNull();
});
test("rejects a draft that tries to persist removed external semantic configuration fields", () => {
workspaceDrafts.save({
workspaceId: "psd-clinical",
baseCommit: "a".repeat(40),
workspace: {
...workspace,
semantic_index: {
vector_store: {
...workspace.semantic_index.vector_store,
database: "vectors",
},
embedding: workspace.semantic_index.embedding,
},
} as CanonicalWorkspace,
updatedAt: "2026-08-04T10:00:00.000Z",
});
expect(workspaceDrafts.load("psd-clinical")).toBeUndefined();
expect(localStorage.getItem("thothii.workspace-registry.v1.draft.psd-clinical")).toBeNull();
});
+14 -60
View File
@@ -200,7 +200,7 @@ function copyRequest(value: unknown, extraKeys: readonly string[] = []): RestDia
}
function copyDiagnostics(value: unknown): CanonicalDiagnostics | undefined {
const source = exactRecord(value, ["dwh_rest", "vector_rest", "embedding"]);
const source = exactRecord(value, ["dwh_rest"]);
if (!source) return undefined;
const diagnostics: CanonicalDiagnostics = {};
if (source.dwh_rest !== undefined) {
@@ -212,41 +212,6 @@ function copyDiagnostics(value: unknown): CanonicalDiagnostics | undefined {
if (!request || !database || !schema) return undefined;
diagnostics.dwh_rest = { ...request, response: { database, schema } };
}
if (source.vector_rest !== undefined) {
const vector = exactRecord(source.vector_rest, ["metadata", "reversible_probe"]);
const request = copyRequest(vector?.metadata, ["response"]);
const rawMetadata = exactRecord(vector?.metadata, ["method", "path", "auth", "response"]);
const response = exactRecord(rawMetadata?.response, ["collection", "dimensions", "distance"]);
const collection = identifier(response?.collection);
const dimensions = identifier(response?.dimensions);
const distance = identifier(response?.distance);
if (!vector || !request || !collection || !dimensions || !distance) return undefined;
const metadata = { ...request, response: { collection, dimensions, distance } };
let reversibleProbe: NonNullable<CanonicalDiagnostics["vector_rest"]>["reversible_probe"] | undefined;
if (vector.reversible_probe !== undefined) {
const probe = copyRequest(vector.reversible_probe, ["response"]);
const rawProbe = exactRecord(vector.reversible_probe, ["method", "path", "auth", "response"]);
const probeResponse = exactRecord(rawProbe?.response, ["operation"]);
const operation = identifier(probeResponse?.operation);
if (!probe || probe.method !== "POST" || probe.auth === "none" || !operation) return undefined;
reversibleProbe = {
method: "POST",
path: probe.path,
auth: probe.auth as "bearer" | "x-api-key",
response: { operation },
};
}
diagnostics.vector_rest = { metadata, ...(reversibleProbe ? { reversible_probe: reversibleProbe } : {}) };
}
if (source.embedding !== undefined) {
const request = copyRequest(source.embedding, ["response"]);
const raw = exactRecord(source.embedding, ["method", "path", "auth", "response"]);
const response = exactRecord(raw?.response, ["model", "dimensions"]);
const model = identifier(response?.model);
const dimensions = identifier(response?.dimensions);
if (!request || !model || !dimensions) return undefined;
diagnostics.embedding = { ...request, response: { model, dimensions } };
}
return diagnostics;
}
@@ -255,9 +220,9 @@ export function sanitizeCanonicalWorkspace(value: unknown): CanonicalWorkspace |
const source = exactRecord(value, ["workspace", "dwh", "semantic_index", "llm_policy", "diagnostics"]);
const metadata = exactRecord(source?.workspace, ["schema_version", "id", "name", "description", "language"]);
const dwh = exactRecord(source?.dwh, ["engine", "database", "schema", "port", "timeout_ms", "supported_transports"]);
const semanticIndex = exactRecord(source?.semantic_index, ["vector_store", "vector_writer", "embedding"]);
const vectorStore = exactRecord(semanticIndex?.vector_store, ["engine", "database", "schema", "collection", "dimensions", "distance", "port", "timeout_ms", "supported_transports"]);
const embedding = exactRecord(semanticIndex?.embedding, ["provider", "model", "dimensions", "timeout_ms"]);
const semanticIndex = exactRecord(source?.semantic_index, ["vector_store", "embedding"]);
const vectorStore = exactRecord(semanticIndex?.vector_store, ["engine", "collection", "dimensions", "distance"]);
const embedding = exactRecord(semanticIndex?.embedding, ["provider", "model", "dimensions"]);
const policy = exactRecord(source?.llm_policy, ["default", "allowed"]);
const diagnostics = source?.diagnostics === undefined ? undefined : copyDiagnostics(source.diagnostics);
if (!metadata || !dwh || !semanticIndex || !vectorStore || !embedding || !policy) return undefined;
@@ -270,34 +235,28 @@ export function sanitizeCanonicalWorkspace(value: unknown): CanonicalWorkspace |
const dwhPort = dwh.port === undefined ? undefined : positiveInteger(dwh.port, 65_535);
const dwhTimeout = dwh.timeout_ms === undefined ? undefined : positiveInteger(dwh.timeout_ms);
const dwhTransports = uniqueChoices(dwh.supported_transports, ["postgres_direct", "rest_api", "ssh_tunnel"] as const);
const vectorDatabase = identifier(vectorStore.database);
const vectorSchema = identifier(vectorStore.schema);
const collection = identifier(vectorStore.collection);
const vectorDimensions = positiveInteger(vectorStore.dimensions, 32_768);
const distance = oneOf(vectorStore.distance, ["cosine", "l2", "inner_product"] as const);
const vectorPort = vectorStore.port === undefined ? undefined : positiveInteger(vectorStore.port, 65_535);
const vectorTimeout = vectorStore.timeout_ms === undefined ? undefined : positiveInteger(vectorStore.timeout_ms);
const vectorTransports = uniqueChoices(vectorStore.supported_transports, ["pgvector_direct", "rest_api", "ssh_tunnel"] as const);
const embeddingProvider = oneOf(embedding.provider, ["ollama_compatible", "openai_compatible"] as const);
const distance = oneOf(vectorStore.distance, ["cosine"] as const);
const embeddingProvider = oneOf(embedding.provider, ["ollama_internal"] as const);
const embeddingModel = text(embedding.model);
const embeddingDimensions = positiveInteger(embedding.dimensions, 32_768);
const embeddingTimeout = embedding.timeout_ms === undefined ? undefined : positiveInteger(embedding.timeout_ms);
const allowedModels = uniqueModels(policy.allowed);
const defaultModel = policy.default === undefined ? undefined : modelReference(policy.default);
if (
metadata.schema_version !== 2 || !id || !name || !language || (metadata.description !== undefined && !description)
metadata.schema_version !== 3 || !id || !name || !language || (metadata.description !== undefined && !description)
|| dwh.engine !== "postgres" || !database || !schema || (dwh.port !== undefined && !dwhPort) || (dwh.timeout_ms !== undefined && !dwhTimeout) || !dwhTransports
|| vectorStore.engine !== "pgvector" || !vectorDatabase || !vectorSchema || !collection || !vectorDimensions || !distance || (vectorStore.port !== undefined && !vectorPort) || (vectorStore.timeout_ms !== undefined && !vectorTimeout) || !vectorTransports
|| !embeddingProvider || !embeddingModel || !embeddingDimensions || (embedding.timeout_ms !== undefined && !embeddingTimeout) || !allowedModels
|| vectorStore.engine !== "qdrant" || !collection || !vectorDimensions || !distance
|| !embeddingProvider || !embeddingModel || !embeddingDimensions || !allowedModels
|| (defaultModel !== undefined && !allowedModels.includes(defaultModel)) || vectorDimensions !== embeddingDimensions
|| (semanticIndex.vector_writer !== undefined && !exactRecord(semanticIndex.vector_writer, []))
|| vectorDimensions !== 1024 || embeddingDimensions !== 1024
|| embeddingModel !== "qwen3-embedding:0.6b"
) return undefined;
if (source?.diagnostics !== undefined && !diagnostics) return undefined;
if (diagnostics?.dwh_rest && !dwhTransports.includes("rest_api")) return undefined;
if (diagnostics?.vector_rest && !vectorTransports.includes("rest_api")) return undefined;
return {
workspace: {
schema_version: 2,
schema_version: 3,
id,
name,
...(description ? { description } : {}),
@@ -311,15 +270,10 @@ export function sanitizeCanonicalWorkspace(value: unknown): CanonicalWorkspace |
},
semantic_index: {
vector_store: {
engine: "pgvector", database: vectorDatabase, schema: vectorSchema, collection, dimensions: vectorDimensions, distance,
...(vectorPort ? { port: vectorPort } : {}),
...(vectorTimeout ? { timeout_ms: vectorTimeout } : {}),
supported_transports: vectorTransports,
engine: "qdrant", collection, dimensions: 1024, distance: "cosine",
},
...(semanticIndex.vector_writer ? { vector_writer: {} } : {}),
embedding: {
provider: embeddingProvider, model: embeddingModel, dimensions: embeddingDimensions,
...(embeddingTimeout ? { timeout_ms: embeddingTimeout } : {}),
provider: "ollama_internal", model: "qwen3-embedding:0.6b", dimensions: 1024,
},
},
llm_policy: {