mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-01 09:29:38 +02:00
feat(ts): complete schema-first phase 2
This commit is contained in:
parent
0746d7ffd5
commit
be2370ee7b
24 changed files with 465 additions and 433 deletions
|
|
@ -18,7 +18,7 @@ import {
|
|||
} from "./async-processor.js";
|
||||
import type { Spec } from "../spec/types.js";
|
||||
import type { BackendConsumer, PubSubBackend } from "../backend/types.js";
|
||||
import type { FlowDefinition } from "./flow.js";
|
||||
import { FlowDefinition } from "./flow.js";
|
||||
import { Flow, } from "./flow.js";
|
||||
import { topics } from "../schema/topics.js";
|
||||
import type {
|
||||
|
|
@ -128,14 +128,9 @@ const ConfigPushSchema = S.Struct({
|
|||
config: S.Record(S.String, S.Unknown),
|
||||
});
|
||||
|
||||
const FlowDefinitionSchema = S.Struct({
|
||||
topics: S.optionalKey(S.Record(S.String, S.String)),
|
||||
parameters: S.optionalKey(S.Record(S.String, S.Unknown)),
|
||||
});
|
||||
const FlowDefinitions = S.Record(S.String, FlowDefinition);
|
||||
|
||||
const FlowDefinitionsSchema = S.Record(S.String, FlowDefinitionSchema);
|
||||
|
||||
const decodeFlowDefinitions = S.decodeUnknownOption(FlowDefinitionsSchema);
|
||||
const decodeFlowDefinitions = S.decodeUnknownOption(FlowDefinitions);
|
||||
|
||||
export function runFlowProcessorDefinitionScoped<
|
||||
FlowRequirements = never,
|
||||
|
|
|
|||
|
|
@ -43,12 +43,14 @@ import type { ProducerSpec } from "../spec/producer-spec.js";
|
|||
import type { RequestResponseSpec } from "../spec/request-response-spec.js";
|
||||
import type { Spec, SpecRuntimeRequirements } from "../spec/types.js";
|
||||
|
||||
export interface FlowDefinition {
|
||||
export class FlowDefinition extends S.Class<FlowDefinition>("FlowDefinition")({
|
||||
/** Topic overrides keyed by spec name */
|
||||
topics?: Record<string, string>;
|
||||
topics: S.optionalKey(S.Record(S.String, S.String)),
|
||||
/** Parameter values keyed by spec name */
|
||||
parameters?: Record<string, unknown>;
|
||||
}
|
||||
parameters: S.optionalKey(S.Record(S.String, S.Unknown)),
|
||||
}, {
|
||||
description: "Per-flow configuration: topic overrides and parameter values keyed by spec name.",
|
||||
}) {}
|
||||
|
||||
export interface FlowProducer<T> {
|
||||
readonly send: (id: string, message: T) => Effect.Effect<void, MessagingDeliveryError>;
|
||||
|
|
|
|||
|
|
@ -3,22 +3,25 @@
|
|||
*/
|
||||
|
||||
import { Config, Duration, Effect } from "effect";
|
||||
import * as S from "effect/Schema";
|
||||
|
||||
export interface MessagingRuntimeConfig {
|
||||
readonly consumerReceiveTimeout: Duration.Duration;
|
||||
readonly consumerErrorBackoff: Duration.Duration;
|
||||
readonly rateLimitRetry: Duration.Duration;
|
||||
readonly rateLimitTimeout: Duration.Duration;
|
||||
readonly requestTimeout: Duration.Duration;
|
||||
}
|
||||
export class MessagingRuntimeConfig extends S.Class<MessagingRuntimeConfig>("MessagingRuntimeConfig")({
|
||||
consumerReceiveTimeout: S.Duration,
|
||||
consumerErrorBackoff: S.Duration,
|
||||
rateLimitRetry: S.Duration,
|
||||
rateLimitTimeout: S.Duration,
|
||||
requestTimeout: S.Duration,
|
||||
}, {
|
||||
description: "Messaging runtime timing windows for consumer receive, backoff, rate-limit retry, and request timeout.",
|
||||
}) {}
|
||||
|
||||
export const defaultMessagingRuntimeConfig: MessagingRuntimeConfig = {
|
||||
export const defaultMessagingRuntimeConfig: MessagingRuntimeConfig = MessagingRuntimeConfig.make({
|
||||
consumerReceiveTimeout: Duration.millis(2_000),
|
||||
consumerErrorBackoff: Duration.millis(1_000),
|
||||
rateLimitRetry: Duration.millis(10_000),
|
||||
rateLimitTimeout: Duration.millis(7_200_000),
|
||||
requestTimeout: Duration.millis(300_000),
|
||||
};
|
||||
});
|
||||
|
||||
const durationConfig = (name: string, defaultValue: Duration.Duration) =>
|
||||
Config.duration(name).pipe(
|
||||
|
|
@ -48,11 +51,11 @@ export const loadMessagingRuntimeConfig = Effect.fn("loadMessagingRuntimeConfig"
|
|||
defaultMessagingRuntimeConfig.requestTimeout,
|
||||
);
|
||||
|
||||
return {
|
||||
return MessagingRuntimeConfig.make({
|
||||
consumerReceiveTimeout,
|
||||
consumerErrorBackoff,
|
||||
rateLimitRetry,
|
||||
rateLimitTimeout,
|
||||
requestTimeout,
|
||||
} satisfies MessagingRuntimeConfig;
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@ import * as S from "effect/Schema";
|
|||
import * as Command from "effect/unstable/cli/Command";
|
||||
import * as Flag from "effect/unstable/cli/Flag";
|
||||
|
||||
export interface CliOpts {
|
||||
gateway: string;
|
||||
user: string;
|
||||
token?: string;
|
||||
flow: string;
|
||||
}
|
||||
export class CliOpts extends S.Class<CliOpts>("CliOpts")({
|
||||
gateway: S.String,
|
||||
user: S.String,
|
||||
token: S.optionalKey(S.String),
|
||||
flow: S.String,
|
||||
}, { description: "Resolved TrustGraph CLI connection options." }) {}
|
||||
|
||||
export const rootCommand = Command.make("tg").pipe(
|
||||
Command.withDescription("TrustGraph CLI - interact with TrustGraph services"),
|
||||
|
|
|
|||
|
|
@ -430,7 +430,7 @@ const onAgentRequest = Effect.fn("AgentService.onRequest")(function* (
|
|||
chunk_type: "explain",
|
||||
content: "",
|
||||
explain_id: explain.explainId,
|
||||
explain_triples: explain.triples,
|
||||
explain_triples: [...explain.triples],
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,10 +15,8 @@ import type {
|
|||
TriplesQueryResponse,
|
||||
ToolRequest,
|
||||
ToolResponse,
|
||||
Term,
|
||||
Triple,
|
||||
} from "@trustgraph/base";
|
||||
import {Term as TermSchema} from "@trustgraph/base";
|
||||
import { Term, Triple } from "@trustgraph/base";
|
||||
import { Effect, Match } from "effect";
|
||||
import * as O from "effect/Option";
|
||||
import * as Predicate from "effect/Predicate";
|
||||
|
|
@ -27,7 +25,7 @@ import type { AgentTool, ToolArg } from "./types.js";
|
|||
import { agentToolError, } from "./types.js";
|
||||
|
||||
const decodeJsonUnknown = S.decodeUnknownOption(S.UnknownFromJsonString);
|
||||
const decodeTerm = S.decodeUnknownOption(TermSchema);
|
||||
const decodeTerm = S.decodeUnknownOption(Term);
|
||||
|
||||
/**
|
||||
* Format a Term to a human-readable string.
|
||||
|
|
@ -64,10 +62,10 @@ function parseQuestion(input: string): string {
|
|||
/**
|
||||
* Explain data extracted from a graph-rag response.
|
||||
*/
|
||||
export interface ExplainData {
|
||||
explainId: string;
|
||||
triples: Triple[];
|
||||
}
|
||||
export class ExplainData extends S.Class<ExplainData>("ExplainData")({
|
||||
explainId: S.String,
|
||||
triples: S.Array(Triple),
|
||||
}, { description: "Explain payload extracted from a graph-rag response: id plus supporting triples." }) {}
|
||||
|
||||
/**
|
||||
* Query the knowledge graph for information about entities and their relationships.
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@ export const agentToolError = (operation: string, cause: unknown): AgentToolErro
|
|||
message: errorMessage(cause),
|
||||
});
|
||||
|
||||
export interface ToolArg {
|
||||
name: string;
|
||||
type: string;
|
||||
description: string;
|
||||
}
|
||||
export class ToolArg extends S.Class<ToolArg>("ToolArg")({
|
||||
name: S.String,
|
||||
type: S.String,
|
||||
description: S.String,
|
||||
}, { description: "A named, typed argument accepted by an agent tool." }) {}
|
||||
|
||||
export interface AgentTool {
|
||||
name: string;
|
||||
|
|
@ -43,10 +43,10 @@ export type ReActState =
|
|||
| "final_answer"
|
||||
| "complete";
|
||||
|
||||
export interface ParsedEvent {
|
||||
type: "thought" | "action" | "action_input" | "final_answer";
|
||||
content: string;
|
||||
}
|
||||
export class ParsedEvent extends S.Class<ParsedEvent>("ParsedEvent")({
|
||||
type: S.Literals(["thought", "action", "action_input", "final_answer"]),
|
||||
content: S.String,
|
||||
}, { description: "A parsed ReAct stream event with its section content." }) {}
|
||||
|
||||
export type OnThought = (text: string, isFinal: boolean) => Effect.Effect<void, AgentToolError>;
|
||||
export type OnObservation = (text: string, isFinal: boolean) => Effect.Effect<void, AgentToolError>;
|
||||
|
|
|
|||
|
|
@ -8,14 +8,15 @@
|
|||
|
||||
import * as MutableHashMap from "effect/MutableHashMap";
|
||||
import * as O from "effect/Option";
|
||||
import * as S from "effect/Schema";
|
||||
|
||||
export interface CollectionEntry {
|
||||
user: string;
|
||||
collection: string;
|
||||
name: string;
|
||||
description: string;
|
||||
tags: string[];
|
||||
}
|
||||
export class CollectionEntry extends S.Class<CollectionEntry>("CollectionEntry")({
|
||||
user: S.String,
|
||||
collection: S.String,
|
||||
name: S.String,
|
||||
description: S.String,
|
||||
tags: S.Array(S.String),
|
||||
}, { description: "A librarian collection registration with its metadata tags." }) {}
|
||||
|
||||
export interface CollectionManager {
|
||||
readonly listCollections: (user: string) => CollectionEntry[];
|
||||
|
|
|
|||
|
|
@ -39,10 +39,10 @@ export type TextCompletionRuntimeError =
|
|||
| TextCompletionProviderError
|
||||
| TooManyRequestsError;
|
||||
|
||||
export interface LanguageModelProviderRequest {
|
||||
readonly model: string;
|
||||
readonly temperature: number;
|
||||
}
|
||||
export class LanguageModelProviderRequest extends S.Class<LanguageModelProviderRequest>("LanguageModelProviderRequest")({
|
||||
model: S.String,
|
||||
temperature: S.Finite,
|
||||
}, { description: "Resolved model id and temperature for a language-model call." }) {}
|
||||
|
||||
export interface LanguageModelProviderOptions<Requirements> {
|
||||
readonly provider: string;
|
||||
|
|
|
|||
|
|
@ -47,10 +47,10 @@ import * as MutableHashMap from "effect/MutableHashMap";
|
|||
import * as O from "effect/Option";
|
||||
import * as S from "effect/Schema";
|
||||
|
||||
export interface PromptTemplate {
|
||||
system: string;
|
||||
prompt: string;
|
||||
}
|
||||
export class PromptTemplate extends S.Class<PromptTemplate>("PromptTemplate")({
|
||||
system: S.String,
|
||||
prompt: S.String,
|
||||
}, { description: "A prompt template: system preamble plus prompt body." }) {}
|
||||
|
||||
export interface PromptTemplateConfig extends ProcessorConfig {
|
||||
configKey?: string;
|
||||
|
|
|
|||
|
|
@ -4,22 +4,22 @@ import { errorMessage } from "@trustgraph/base";
|
|||
import { Effect } from "effect";
|
||||
import * as S from "effect/Schema";
|
||||
|
||||
export interface QdrantCollectionStatus {
|
||||
readonly exists: boolean;
|
||||
}
|
||||
export class QdrantCollectionStatus extends S.Class<QdrantCollectionStatus>("QdrantCollectionStatus")({
|
||||
exists: S.Boolean,
|
||||
}, { description: "Qdrant collection existence probe result." }) {}
|
||||
|
||||
export interface QdrantCollectionDescription {
|
||||
readonly name: string;
|
||||
}
|
||||
export class QdrantCollectionDescription extends S.Class<QdrantCollectionDescription>("QdrantCollectionDescription")({
|
||||
name: S.String,
|
||||
}, { description: "A named Qdrant collection." }) {}
|
||||
|
||||
export interface QdrantCollections {
|
||||
readonly collections: ReadonlyArray<QdrantCollectionDescription>;
|
||||
}
|
||||
export class QdrantCollections extends S.Class<QdrantCollections>("QdrantCollections")({
|
||||
collections: S.Array(QdrantCollectionDescription),
|
||||
}, { description: "Qdrant collection listing." }) {}
|
||||
|
||||
export interface QdrantScoredPoint {
|
||||
readonly score: number;
|
||||
readonly payload?: unknown;
|
||||
}
|
||||
export class QdrantScoredPoint extends S.Class<QdrantScoredPoint>("QdrantScoredPoint")({
|
||||
score: S.Finite,
|
||||
payload: S.optionalKey(S.Unknown),
|
||||
}, { description: "A scored Qdrant search hit with optional payload." }) {}
|
||||
|
||||
export class QdrantClientError extends S.TaggedErrorClass<QdrantClientError>()("QdrantClientError", {
|
||||
message: S.String,
|
||||
|
|
|
|||
|
|
@ -20,18 +20,18 @@ export interface QdrantDocQueryConfig {
|
|||
clientFactory?: QdrantClientFactory;
|
||||
}
|
||||
|
||||
export interface ChunkMatch {
|
||||
chunkId: string;
|
||||
score: number;
|
||||
content?: string;
|
||||
}
|
||||
export class ChunkMatch extends S.Class<ChunkMatch>("ChunkMatch")({
|
||||
chunkId: S.String,
|
||||
score: S.Finite,
|
||||
content: S.optionalKey(S.String),
|
||||
}, { description: "A scored document-chunk match from embeddings query." }) {}
|
||||
|
||||
export interface DocEmbeddingsQueryRequest {
|
||||
vector: number[];
|
||||
user: string;
|
||||
collection: string;
|
||||
limit: number;
|
||||
}
|
||||
export class DocEmbeddingsQueryRequest extends S.Class<DocEmbeddingsQueryRequest>("DocEmbeddingsQueryRequest")({
|
||||
vector: S.Array(S.Finite),
|
||||
user: S.String,
|
||||
collection: S.String,
|
||||
limit: S.Finite,
|
||||
}, { description: "Document embeddings similarity query request." }) {}
|
||||
|
||||
export class QdrantDocEmbeddingsQueryError extends S.TaggedErrorClass<QdrantDocEmbeddingsQueryError>()(
|
||||
"QdrantDocEmbeddingsQueryError",
|
||||
|
|
|
|||
|
|
@ -10,8 +10,7 @@
|
|||
* Python reference: trustgraph-flow/trustgraph/query/graph_embeddings/qdrant/service.py
|
||||
*/
|
||||
|
||||
import type { Term } from "@trustgraph/base";
|
||||
import { errorMessage, } from "@trustgraph/base";
|
||||
import { Term, errorMessage } from "@trustgraph/base";
|
||||
import { Config, Context, Effect, Layer } from "effect";
|
||||
import * as O from "effect/Option";
|
||||
import * as S from "effect/Schema";
|
||||
|
|
@ -24,17 +23,17 @@ export interface QdrantGraphQueryConfig {
|
|||
clientFactory?: QdrantClientFactory;
|
||||
}
|
||||
|
||||
export interface EntityMatch {
|
||||
entity: Term;
|
||||
score: number;
|
||||
}
|
||||
export class EntityMatch extends S.Class<EntityMatch>("EntityMatch")({
|
||||
entity: Term,
|
||||
score: S.Finite,
|
||||
}, { description: "A scored graph-entity match from embeddings query." }) {}
|
||||
|
||||
export interface GraphEmbeddingsQueryRequest {
|
||||
vector: number[];
|
||||
user: string;
|
||||
collection: string;
|
||||
limit: number;
|
||||
}
|
||||
export class GraphEmbeddingsQueryRequest extends S.Class<GraphEmbeddingsQueryRequest>("GraphEmbeddingsQueryRequest")({
|
||||
vector: S.Array(S.Finite),
|
||||
user: S.String,
|
||||
collection: S.String,
|
||||
limit: S.Finite,
|
||||
}, { description: "Graph embeddings similarity query request." }) {}
|
||||
|
||||
export class QdrantGraphEmbeddingsQueryError extends S.TaggedErrorClass<QdrantGraphEmbeddingsQueryError>()(
|
||||
"QdrantGraphEmbeddingsQueryError",
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ const onGraphRagRequest = Effect.fn("GraphRagService.onRequest")(function* (
|
|||
endOfStream: true,
|
||||
message_type: "explain",
|
||||
explain_id: `explain-${requestId}`,
|
||||
explain_triples: result.subgraph,
|
||||
explain_triples: [...result.subgraph],
|
||||
};
|
||||
|
||||
yield* producer.send(requestId, response);
|
||||
|
|
|
|||
|
|
@ -16,23 +16,22 @@ import type {
|
|||
Term,
|
||||
TextCompletionRequest,
|
||||
TextCompletionResponse,
|
||||
Triple,
|
||||
TriplesQueryRequest,
|
||||
TriplesQueryResponse,
|
||||
} from "@trustgraph/base";
|
||||
import { errorMessage } from "@trustgraph/base";
|
||||
import { Triple, errorMessage } from "@trustgraph/base";
|
||||
import { Context, Effect, Layer, Match } from "effect";
|
||||
import * as O from "effect/Option";
|
||||
import * as S from "effect/Schema";
|
||||
|
||||
export interface GraphRagConfig {
|
||||
entityLimit?: number;
|
||||
tripleLimit?: number;
|
||||
maxSubgraphSize?: number;
|
||||
maxPathLength?: number;
|
||||
edgeScoreLimit?: number;
|
||||
edgeLimit?: number;
|
||||
}
|
||||
export class GraphRagConfig extends S.Class<GraphRagConfig>("GraphRagConfig")({
|
||||
entityLimit: S.optionalKey(S.Finite),
|
||||
tripleLimit: S.optionalKey(S.Finite),
|
||||
maxSubgraphSize: S.optionalKey(S.Finite),
|
||||
maxPathLength: S.optionalKey(S.Finite),
|
||||
edgeScoreLimit: S.optionalKey(S.Finite),
|
||||
edgeLimit: S.optionalKey(S.Finite),
|
||||
}, { description: "Graph RAG retrieval tuning limits." }) {}
|
||||
|
||||
export interface GraphRagClients {
|
||||
llm: EffectRequestResponse<TextCompletionRequest, TextCompletionResponse>;
|
||||
|
|
@ -53,10 +52,10 @@ export interface GraphRagQueryOptions {
|
|||
readonly chunkCallback?: ChunkCallback;
|
||||
}
|
||||
|
||||
export interface GraphRagResult {
|
||||
answer: string;
|
||||
subgraph: Triple[];
|
||||
}
|
||||
export class GraphRagResult extends S.Class<GraphRagResult>("GraphRagResult")({
|
||||
answer: S.String,
|
||||
subgraph: S.Array(Triple),
|
||||
}, { description: "Graph RAG answer with the supporting subgraph." }) {}
|
||||
|
||||
interface NormalizedGraphRagConfig {
|
||||
entityLimit: number;
|
||||
|
|
|
|||
|
|
@ -22,17 +22,17 @@ export interface QdrantDocEmbeddingsConfig {
|
|||
clientFactory?: QdrantClientFactory;
|
||||
}
|
||||
|
||||
export interface DocEmbeddingChunk {
|
||||
chunkId: string;
|
||||
vector: number[];
|
||||
content?: string;
|
||||
}
|
||||
export class DocEmbeddingChunk extends S.Class<DocEmbeddingChunk>("DocEmbeddingChunk")({
|
||||
chunkId: S.String,
|
||||
vector: S.Array(S.Finite),
|
||||
content: S.optionalKey(S.String),
|
||||
}, { description: "A document chunk paired with its embedding vector." }) {}
|
||||
|
||||
export interface DocEmbeddingsMessage {
|
||||
user: string;
|
||||
collection: string;
|
||||
chunks: DocEmbeddingChunk[];
|
||||
}
|
||||
export class DocEmbeddingsMessage extends S.Class<DocEmbeddingsMessage>("DocEmbeddingsMessage")({
|
||||
user: S.String,
|
||||
collection: S.String,
|
||||
chunks: S.Array(DocEmbeddingChunk),
|
||||
}, { description: "Document embeddings store message: chunks to upsert for a user collection." }) {}
|
||||
|
||||
export class QdrantDocEmbeddingsStoreError extends S.TaggedErrorClass<QdrantDocEmbeddingsStoreError>()(
|
||||
"QdrantDocEmbeddingsStoreError",
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@
|
|||
* Python reference: trustgraph-flow/trustgraph/storage/graph_embeddings/qdrant/write.py
|
||||
*/
|
||||
|
||||
import type { Term } from "@trustgraph/base";
|
||||
import { errorMessage, } from "@trustgraph/base";
|
||||
import { Term, errorMessage } from "@trustgraph/base";
|
||||
import { Config, Context, Effect, Layer, Match, Random } from "effect";
|
||||
import * as MutableHashSet from "effect/MutableHashSet";
|
||||
import * as O from "effect/Option";
|
||||
|
|
@ -23,17 +22,17 @@ export interface QdrantGraphEmbeddingsConfig {
|
|||
clientFactory?: QdrantClientFactory;
|
||||
}
|
||||
|
||||
export interface GraphEmbeddingEntity {
|
||||
entity: Term;
|
||||
vector: number[];
|
||||
chunkId?: string;
|
||||
}
|
||||
export class GraphEmbeddingEntity extends S.Class<GraphEmbeddingEntity>("GraphEmbeddingEntity")({
|
||||
entity: Term,
|
||||
vector: S.Array(S.Finite),
|
||||
chunkId: S.optionalKey(S.String),
|
||||
}, { description: "A graph entity paired with its embedding vector." }) {}
|
||||
|
||||
export interface GraphEmbeddingsMessage {
|
||||
user: string;
|
||||
collection: string;
|
||||
entities: GraphEmbeddingEntity[];
|
||||
}
|
||||
export class GraphEmbeddingsMessage extends S.Class<GraphEmbeddingsMessage>("GraphEmbeddingsMessage")({
|
||||
user: S.String,
|
||||
collection: S.String,
|
||||
entities: S.Array(GraphEmbeddingEntity),
|
||||
}, { description: "Graph embeddings store message: entities to upsert for a user collection." }) {}
|
||||
|
||||
export class QdrantGraphEmbeddingsStoreError extends S.TaggedErrorClass<QdrantGraphEmbeddingsStoreError>()(
|
||||
"QdrantGraphEmbeddingsStoreError",
|
||||
|
|
|
|||
|
|
@ -1217,16 +1217,18 @@ export interface TrustGraphMcpOptions {
|
|||
readonly port?: number | undefined
|
||||
}
|
||||
|
||||
export interface TrustGraphMcpConfigShape {
|
||||
readonly gatewayUrl: string
|
||||
readonly user: string
|
||||
readonly token: string | undefined
|
||||
readonly flowId: string
|
||||
readonly name: string
|
||||
readonly version: string
|
||||
readonly mcpPath: HttpRouter.PathInput
|
||||
readonly port: number
|
||||
}
|
||||
const McpPathInput = S.Union([S.Literal("*"), S.TemplateLiteral(["/", S.String])])
|
||||
|
||||
export class TrustGraphMcpConfigShape extends S.Class<TrustGraphMcpConfigShape>("TrustGraphMcpConfigShape")({
|
||||
gatewayUrl: S.String,
|
||||
user: S.String,
|
||||
token: S.UndefinedOr(S.String),
|
||||
flowId: S.String,
|
||||
name: S.String,
|
||||
version: S.String,
|
||||
mcpPath: McpPathInput,
|
||||
port: S.Finite,
|
||||
}, { description: "Resolved TrustGraph MCP server configuration." }) {}
|
||||
|
||||
const readNonEmpty = (value: string | undefined): string | undefined =>
|
||||
value !== undefined && value.length > 0 ? value : undefined
|
||||
|
|
|
|||
|
|
@ -71,6 +71,44 @@ type WorkbenchError = WorkbenchPromiseError;
|
|||
|
||||
const isWorkbenchPromiseError = S.is(WorkbenchPromiseError);
|
||||
|
||||
const ClientTriple: S.Codec<Triple, Triple> = S.suspend(() =>
|
||||
S.Struct({
|
||||
s: ClientTerm,
|
||||
p: ClientTerm,
|
||||
o: ClientTerm,
|
||||
g: S.optionalKey(S.String),
|
||||
})
|
||||
);
|
||||
|
||||
const ClientTerm: S.Codec<Term, Term> = S.suspend(() =>
|
||||
S.Union([
|
||||
S.Struct({
|
||||
t: S.Literal("i"),
|
||||
i: S.String,
|
||||
}),
|
||||
S.Struct({
|
||||
t: S.Literal("b"),
|
||||
d: S.String,
|
||||
}),
|
||||
S.Struct({
|
||||
t: S.Literal("l"),
|
||||
v: S.String,
|
||||
dt: S.optionalKey(S.String),
|
||||
ln: S.optionalKey(S.String),
|
||||
}),
|
||||
S.Struct({
|
||||
t: S.Literal("t"),
|
||||
tr: S.optionalKey(ClientTriple),
|
||||
}),
|
||||
])
|
||||
);
|
||||
|
||||
const ClientExplainEvent: S.Codec<ExplainEvent, ExplainEvent> = S.Struct({
|
||||
explainId: S.String,
|
||||
explainGraph: S.String,
|
||||
explainTriples: S.optionalKey(S.Array(ClientTriple).pipe(S.mutable)),
|
||||
});
|
||||
|
||||
function errorMessage(error: unknown): string {
|
||||
if (isWorkbenchPromiseError(error)) return error.message;
|
||||
if (Predicate.isObject(error) && Predicate.hasProperty(error, "message")) {
|
||||
|
|
@ -146,25 +184,25 @@ const mutationCounter = Metric.counter("trustgraph_workbench_mutation_total", {
|
|||
// Shared types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface FeatureSwitches {
|
||||
flowClasses: boolean;
|
||||
submissions: boolean;
|
||||
tokenCost: boolean;
|
||||
schemas: boolean;
|
||||
structuredQuery: boolean;
|
||||
ontologyEditor: boolean;
|
||||
agentTools: boolean;
|
||||
mcpTools: boolean;
|
||||
llmModels: boolean;
|
||||
}
|
||||
export class FeatureSwitches extends S.Class<FeatureSwitches>("FeatureSwitches")({
|
||||
flowClasses: S.Boolean,
|
||||
submissions: S.Boolean,
|
||||
tokenCost: S.Boolean,
|
||||
schemas: S.Boolean,
|
||||
structuredQuery: S.Boolean,
|
||||
ontologyEditor: S.Boolean,
|
||||
agentTools: S.Boolean,
|
||||
mcpTools: S.Boolean,
|
||||
llmModels: S.Boolean,
|
||||
}, { description: "Workbench feature visibility switches." }) {}
|
||||
|
||||
export interface Settings {
|
||||
user: string;
|
||||
apiKey: string;
|
||||
collection: string;
|
||||
gatewayUrl: string;
|
||||
featureSwitches: FeatureSwitches;
|
||||
}
|
||||
export class Settings extends S.Class<Settings>("Settings")({
|
||||
user: S.String,
|
||||
apiKey: S.String,
|
||||
collection: S.String,
|
||||
gatewayUrl: S.String,
|
||||
featureSwitches: FeatureSwitches,
|
||||
}, { description: "Persisted workbench connection and display settings." }) {}
|
||||
|
||||
export interface WorkbenchApiFactory {
|
||||
readonly create: (settings: Settings) => BaseApi;
|
||||
|
|
@ -176,31 +214,31 @@ export type ChatMode = "graph-rag" | "document-rag" | "agent";
|
|||
export type MessageRole = "user" | "assistant" | "system";
|
||||
export type AgentPhase = "think" | "observe" | "answer";
|
||||
|
||||
export interface ChatMessage {
|
||||
id: string;
|
||||
role: MessageRole;
|
||||
content: string;
|
||||
timestamp: number;
|
||||
isStreaming?: boolean;
|
||||
metadata?: {
|
||||
model?: string;
|
||||
inTokens?: number;
|
||||
outTokens?: number;
|
||||
};
|
||||
agentPhases?: {
|
||||
think: string;
|
||||
observe: string;
|
||||
answer: string;
|
||||
};
|
||||
activePhase?: AgentPhase;
|
||||
explainEvents?: ExplainEvent[];
|
||||
}
|
||||
export class ChatMessage extends S.Class<ChatMessage>("ChatMessage")({
|
||||
id: S.String,
|
||||
role: S.Literals(["user", "assistant", "system"]),
|
||||
content: S.String,
|
||||
timestamp: S.Finite,
|
||||
isStreaming: S.optionalKey(S.Boolean),
|
||||
metadata: S.optionalKey(S.Struct({
|
||||
model: S.optionalKey(S.String),
|
||||
inTokens: S.optionalKey(S.Finite),
|
||||
outTokens: S.optionalKey(S.Finite),
|
||||
})),
|
||||
agentPhases: S.optionalKey(S.Struct({
|
||||
think: S.String,
|
||||
observe: S.String,
|
||||
answer: S.String,
|
||||
})),
|
||||
activePhase: S.optionalKey(S.Literals(["think", "observe", "answer"])),
|
||||
explainEvents: S.optionalKey(S.Array(ClientExplainEvent).pipe(S.mutable)),
|
||||
}, { description: "A rendered chat transcript message." }) {}
|
||||
|
||||
export interface ConversationState {
|
||||
messages: ChatMessage[];
|
||||
input: string;
|
||||
chatMode: ChatMode;
|
||||
}
|
||||
export class ConversationState extends S.Class<ConversationState>("ConversationState")({
|
||||
messages: S.Array(ChatMessage).pipe(S.mutable),
|
||||
input: S.String,
|
||||
chatMode: S.Literals(["graph-rag", "document-rag", "agent"]),
|
||||
}, { description: "Persisted workbench chat state." }) {}
|
||||
|
||||
export interface FlowSummary {
|
||||
id: string;
|
||||
|
|
@ -216,60 +254,60 @@ export interface ProcessingMetadata {
|
|||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface UploadProgress {
|
||||
phase: "preparing" | "uploading" | "finalizing";
|
||||
chunksTotal: number;
|
||||
chunksUploaded: number;
|
||||
bytesTotal: number;
|
||||
bytesUploaded: number;
|
||||
}
|
||||
export class UploadProgress extends S.Class<UploadProgress>("UploadProgress")({
|
||||
phase: S.Literals(["preparing", "uploading", "finalizing"]),
|
||||
chunksTotal: S.Finite,
|
||||
chunksUploaded: S.Finite,
|
||||
bytesTotal: S.Finite,
|
||||
bytesUploaded: S.Finite,
|
||||
}, { description: "Current chunked document upload progress." }) {}
|
||||
|
||||
export interface UploadForm {
|
||||
file: File | null;
|
||||
title: string;
|
||||
tags: string;
|
||||
comments: string;
|
||||
uploading: boolean;
|
||||
dragOver: boolean;
|
||||
progress: UploadProgress | null;
|
||||
}
|
||||
export class UploadForm extends S.Class<UploadForm>("UploadForm")({
|
||||
file: S.NullOr(S.File),
|
||||
title: S.String,
|
||||
tags: S.String,
|
||||
comments: S.String,
|
||||
uploading: S.Boolean,
|
||||
dragOver: S.Boolean,
|
||||
progress: S.NullOr(UploadProgress),
|
||||
}, { description: "Workbench document upload form state." }) {}
|
||||
|
||||
export interface McpServerConfig {
|
||||
url: string;
|
||||
"remote-name"?: string;
|
||||
"auth-token"?: string;
|
||||
}
|
||||
export class McpServerConfig extends S.Class<McpServerConfig>("McpServerConfig")({
|
||||
url: S.String,
|
||||
"remote-name": S.optionalKey(S.String),
|
||||
"auth-token": S.optionalKey(S.String),
|
||||
}, { description: "Workbench MCP server config entry payload." }) {}
|
||||
|
||||
export interface McpServerEntry {
|
||||
key: string;
|
||||
config: McpServerConfig;
|
||||
}
|
||||
export class McpServerEntry extends S.Class<McpServerEntry>("McpServerEntry")({
|
||||
key: S.String,
|
||||
config: McpServerConfig,
|
||||
}, { description: "Workbench MCP server config entry." }) {}
|
||||
|
||||
export interface ToolArgument {
|
||||
name: string;
|
||||
type: string;
|
||||
description: string;
|
||||
}
|
||||
export class ToolArgument extends S.Class<ToolArgument>("ToolArgument")({
|
||||
name: S.String,
|
||||
type: S.String,
|
||||
description: S.String,
|
||||
}, { description: "Workbench MCP tool argument descriptor." }) {}
|
||||
|
||||
export interface ToolConfig {
|
||||
type: string;
|
||||
name: string;
|
||||
description: string;
|
||||
"mcp-tool"?: string;
|
||||
group?: string[];
|
||||
arguments?: ToolArgument[];
|
||||
}
|
||||
export class ToolConfig extends S.Class<ToolConfig>("ToolConfig")({
|
||||
type: S.String,
|
||||
name: S.String,
|
||||
description: S.String,
|
||||
"mcp-tool": S.optionalKey(S.String),
|
||||
group: S.optionalKey(S.Array(S.String).pipe(S.mutable)),
|
||||
arguments: S.optionalKey(S.Array(ToolArgument).pipe(S.mutable)),
|
||||
}, { description: "Workbench tool config entry payload." }) {}
|
||||
|
||||
export interface ToolEntry {
|
||||
key: string;
|
||||
config: ToolConfig;
|
||||
}
|
||||
export class ToolEntry extends S.Class<ToolEntry>("ToolEntry")({
|
||||
key: S.String,
|
||||
config: ToolConfig,
|
||||
}, { description: "Workbench tool config entry." }) {}
|
||||
|
||||
export interface TokenCost {
|
||||
model: string;
|
||||
input_price: number;
|
||||
output_price: number;
|
||||
}
|
||||
export class TokenCost extends S.Class<TokenCost>("TokenCost")({
|
||||
model: S.String,
|
||||
input_price: S.Finite,
|
||||
output_price: S.Finite,
|
||||
}, { description: "Model token pricing row." }) {}
|
||||
|
||||
export interface CollectionSummary {
|
||||
id?: string;
|
||||
|
|
@ -280,61 +318,61 @@ export interface CollectionSummary {
|
|||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface Notification {
|
||||
id: string;
|
||||
type: "success" | "error" | "warning" | "info";
|
||||
title: string;
|
||||
description?: string;
|
||||
}
|
||||
export class Notification extends S.Class<Notification>("Notification")({
|
||||
id: S.String,
|
||||
type: S.Literals(["success", "error", "warning", "info"]),
|
||||
title: S.String,
|
||||
description: S.optionalKey(S.String),
|
||||
}, { description: "Transient workbench notification toast." }) {}
|
||||
|
||||
export interface McpServerForm {
|
||||
key: string;
|
||||
url: string;
|
||||
remoteName: string;
|
||||
authToken: string;
|
||||
showToken: boolean;
|
||||
saving: boolean;
|
||||
keyError: string;
|
||||
}
|
||||
export class McpServerForm extends S.Class<McpServerForm>("McpServerForm")({
|
||||
key: S.String,
|
||||
url: S.String,
|
||||
remoteName: S.String,
|
||||
authToken: S.String,
|
||||
showToken: S.Boolean,
|
||||
saving: S.Boolean,
|
||||
keyError: S.String,
|
||||
}, { description: "Editable MCP server dialog state." }) {}
|
||||
|
||||
export interface McpToolForm {
|
||||
key: string;
|
||||
name: string;
|
||||
description: string;
|
||||
mcpTool: string;
|
||||
group: string;
|
||||
args: ToolArgument[];
|
||||
saving: boolean;
|
||||
keyError: string;
|
||||
}
|
||||
export class McpToolForm extends S.Class<McpToolForm>("McpToolForm")({
|
||||
key: S.String,
|
||||
name: S.String,
|
||||
description: S.String,
|
||||
mcpTool: S.String,
|
||||
group: S.String,
|
||||
args: S.Array(ToolArgument).pipe(S.mutable),
|
||||
saving: S.Boolean,
|
||||
keyError: S.String,
|
||||
}, { description: "Editable MCP tool dialog state." }) {}
|
||||
|
||||
export interface StartFlowForm {
|
||||
id: string;
|
||||
blueprint: string;
|
||||
description: string;
|
||||
paramsJson: string;
|
||||
submitting: boolean;
|
||||
paramsError: string | null;
|
||||
submitted: boolean;
|
||||
definitionExpanded: boolean;
|
||||
}
|
||||
export class StartFlowForm extends S.Class<StartFlowForm>("StartFlowForm")({
|
||||
id: S.String,
|
||||
blueprint: S.String,
|
||||
description: S.String,
|
||||
paramsJson: S.String,
|
||||
submitting: S.Boolean,
|
||||
paramsError: S.NullOr(S.String),
|
||||
submitted: S.Boolean,
|
||||
definitionExpanded: S.Boolean,
|
||||
}, { description: "Start-flow dialog form state." }) {}
|
||||
|
||||
export interface CollectionForm {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
tags: string;
|
||||
submitting: boolean;
|
||||
}
|
||||
export class CollectionForm extends S.Class<CollectionForm>("CollectionForm")({
|
||||
id: S.String,
|
||||
name: S.String,
|
||||
description: S.String,
|
||||
tags: S.String,
|
||||
submitting: S.Boolean,
|
||||
}, { description: "Collection creation form state." }) {}
|
||||
|
||||
export interface GraphViewState {
|
||||
searchTerm: string;
|
||||
selectedNodeId: string | null;
|
||||
selectedNodeLabel: string | null;
|
||||
showLabels: boolean;
|
||||
showTypes: boolean;
|
||||
nodeLimit: number;
|
||||
}
|
||||
export class GraphViewState extends S.Class<GraphViewState>("GraphViewState")({
|
||||
searchTerm: S.String,
|
||||
selectedNodeId: S.NullOr(S.String),
|
||||
selectedNodeLabel: S.NullOr(S.String),
|
||||
showLabels: S.Boolean,
|
||||
showTypes: S.Boolean,
|
||||
nodeLimit: S.Finite,
|
||||
}, { description: "Workbench graph display controls." }) {}
|
||||
|
||||
const DEFAULT_FEATURE_SWITCHES: FeatureSwitches = {
|
||||
flowClasses: false,
|
||||
|
|
@ -356,50 +394,6 @@ export const DEFAULT_SETTINGS: Settings = {
|
|||
featureSwitches: DEFAULT_FEATURE_SWITCHES,
|
||||
};
|
||||
|
||||
const SettingsSchema = S.Struct({
|
||||
user: S.String,
|
||||
apiKey: S.String,
|
||||
collection: S.String,
|
||||
gatewayUrl: S.String,
|
||||
featureSwitches: S.Struct({
|
||||
flowClasses: S.Boolean,
|
||||
submissions: S.Boolean,
|
||||
tokenCost: S.Boolean,
|
||||
schemas: S.Boolean,
|
||||
structuredQuery: S.Boolean,
|
||||
ontologyEditor: S.Boolean,
|
||||
agentTools: S.Boolean,
|
||||
mcpTools: S.Boolean,
|
||||
llmModels: S.Boolean,
|
||||
}),
|
||||
});
|
||||
|
||||
const ChatMessageSchema = S.Struct({
|
||||
id: S.String,
|
||||
role: S.Union([S.Literal("user"), S.Literal("assistant"), S.Literal("system")]),
|
||||
content: S.String,
|
||||
timestamp: S.Finite,
|
||||
isStreaming: S.optionalKey(S.Boolean),
|
||||
metadata: S.optionalKey(S.Struct({
|
||||
model: S.optionalKey(S.String),
|
||||
inTokens: S.optionalKey(S.Finite),
|
||||
outTokens: S.optionalKey(S.Finite),
|
||||
})),
|
||||
agentPhases: S.optionalKey(S.Struct({
|
||||
think: S.String,
|
||||
observe: S.String,
|
||||
answer: S.String,
|
||||
})),
|
||||
activePhase: S.optionalKey(S.Union([S.Literal("think"), S.Literal("observe"), S.Literal("answer")])),
|
||||
explainEvents: S.optionalKey(S.Array(S.Unknown)),
|
||||
});
|
||||
|
||||
const ConversationSchema = S.Struct({
|
||||
messages: S.Array(ChatMessageSchema),
|
||||
input: S.String,
|
||||
chatMode: S.Union([S.Literal("graph-rag"), S.Literal("document-rag"), S.Literal("agent")]),
|
||||
});
|
||||
|
||||
const ThemeSchema = S.Union([S.Literal("dark"), S.Literal("light")]);
|
||||
const FlowIdSchema = S.String;
|
||||
|
||||
|
|
@ -523,11 +517,14 @@ const randomId = Effect.fn("trustgraph.workbench.randomId")(function*(prefix: st
|
|||
|
||||
function metadataFrom(metadata: StreamingMetadata | undefined): ChatMessage["metadata"] | undefined {
|
||||
if (metadata === undefined) return undefined;
|
||||
const result: NonNullable<ChatMessage["metadata"]> = {};
|
||||
if (metadata.model !== undefined) result.model = metadata.model;
|
||||
if (metadata.in_token !== undefined) result.inTokens = metadata.in_token;
|
||||
if (metadata.out_token !== undefined) result.outTokens = metadata.out_token;
|
||||
return Object.keys(result).length > 0 ? result : undefined;
|
||||
if (metadata.model === undefined && metadata.in_token === undefined && metadata.out_token === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
...(metadata.model !== undefined ? { model: metadata.model } : {}),
|
||||
...(metadata.in_token !== undefined ? { inTokens: metadata.in_token } : {}),
|
||||
...(metadata.out_token !== undefined ? { outTokens: metadata.out_token } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function withoutActivePhase(message: ChatMessage): ChatMessage {
|
||||
|
|
@ -609,40 +606,8 @@ const StreamingEnvelopeSchema = S.Struct({
|
|||
});
|
||||
type StreamingEnvelope = typeof StreamingEnvelopeSchema.Type;
|
||||
|
||||
const ClientTripleSchema: S.Codec<Triple, Triple> = S.suspend(() =>
|
||||
S.Struct({
|
||||
s: ClientTermSchema,
|
||||
p: ClientTermSchema,
|
||||
o: ClientTermSchema,
|
||||
g: S.optionalKey(S.String),
|
||||
})
|
||||
);
|
||||
|
||||
const ClientTermSchema: S.Codec<Term, Term> = S.suspend(() =>
|
||||
S.Union([
|
||||
S.Struct({
|
||||
t: S.Literal("i"),
|
||||
i: S.String,
|
||||
}),
|
||||
S.Struct({
|
||||
t: S.Literal("b"),
|
||||
d: S.String,
|
||||
}),
|
||||
S.Struct({
|
||||
t: S.Literal("l"),
|
||||
v: S.String,
|
||||
dt: S.optionalKey(S.String),
|
||||
ln: S.optionalKey(S.String),
|
||||
}),
|
||||
S.Struct({
|
||||
t: S.Literal("t"),
|
||||
tr: S.optionalKey(ClientTripleSchema),
|
||||
}),
|
||||
])
|
||||
);
|
||||
|
||||
const decodeStreamingEnvelope = S.decodeUnknownOption(StreamingEnvelopeSchema);
|
||||
const decodeClientTriples = S.decodeUnknownOption(S.Array(ClientTripleSchema).pipe(S.mutable));
|
||||
const decodeClientTriples = S.decodeUnknownOption(S.Array(ClientTriple).pipe(S.mutable));
|
||||
|
||||
function gatewayHttpBaseUrl(settings: Settings): string {
|
||||
const raw = settings.gatewayUrl.trim();
|
||||
|
|
@ -1532,7 +1497,7 @@ function withActivity<A, R>(
|
|||
export const settingsAtom = Atom.kvs({
|
||||
runtime: workbenchRuntime,
|
||||
key: "trustgraph-workbench-settings-v1",
|
||||
schema: S.toCodecJson(SettingsSchema),
|
||||
schema: S.toCodecJson(Settings),
|
||||
defaultValue: legacySettings,
|
||||
}).pipe(Atom.keepAlive) as Atom.Writable<Settings, Settings>;
|
||||
|
||||
|
|
@ -1553,7 +1518,7 @@ export const flowIdAtom = Atom.kvs({
|
|||
export const conversationAtom = Atom.kvs({
|
||||
runtime: workbenchRuntime,
|
||||
key: "trustgraph-workbench-conversation-v1",
|
||||
schema: S.toCodecJson(ConversationSchema),
|
||||
schema: S.toCodecJson(ConversationState),
|
||||
defaultValue: legacyConversation,
|
||||
}).pipe(Atom.keepAlive) as unknown as Atom.Writable<ConversationState, ConversationState>;
|
||||
|
||||
|
|
@ -1855,17 +1820,17 @@ export const collectionsAtom = queryAtom(
|
|||
{ reactivityKeys: ["collections"] },
|
||||
).pipe(Atom.setIdleTTL("2 minutes"));
|
||||
|
||||
export interface GraphTriplesInput {
|
||||
readonly flowId: string;
|
||||
readonly collection: string;
|
||||
readonly limit: number;
|
||||
}
|
||||
export class GraphTriplesInput extends S.Class<GraphTriplesInput>("GraphTriplesInput")({
|
||||
flowId: S.String,
|
||||
collection: S.String,
|
||||
limit: S.Finite,
|
||||
}, { description: "Workbench graph triples query atom input." }) {}
|
||||
|
||||
export interface ExplainTriplesInput {
|
||||
readonly events: ExplainEvent[];
|
||||
readonly flowId: string;
|
||||
readonly collection: string;
|
||||
}
|
||||
export class ExplainTriplesInput extends S.Class<ExplainTriplesInput>("ExplainTriplesInput")({
|
||||
events: S.Array(ClientExplainEvent).pipe(S.mutable),
|
||||
flowId: S.String,
|
||||
collection: S.String,
|
||||
}, { description: "Workbench explain triples query atom input." }) {}
|
||||
|
||||
const atomFamilyKeySeparator = "\u001f";
|
||||
const explainGraphSeparator = "\u001e";
|
||||
|
|
@ -2139,13 +2104,13 @@ export const deleteMcpToolAtom = commandAtom<string, void>("deleteMcpTool", Effe
|
|||
|
||||
const chunkedUploadThreshold = 1_000_000;
|
||||
|
||||
export interface UploadDocumentInput {
|
||||
readonly base64: string;
|
||||
readonly mimeType: string;
|
||||
readonly title: string;
|
||||
readonly comments: string;
|
||||
readonly tags: string[];
|
||||
}
|
||||
export class UploadDocumentInput extends S.Class<UploadDocumentInput>("UploadDocumentInput")({
|
||||
base64: S.String,
|
||||
mimeType: S.String,
|
||||
title: S.String,
|
||||
comments: S.String,
|
||||
tags: S.Array(S.String).pipe(S.mutable),
|
||||
}, { description: "Workbench document upload command payload." }) {}
|
||||
|
||||
const uploadDocumentEffect = Effect.fn("trustgraph.workbench.uploadDocument.effect")(function*(
|
||||
input: UploadDocumentInput,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { Triple, Term } from "@trustgraph/client";
|
||||
import { Match } from "effect";
|
||||
import * as S from "effect/Schema";
|
||||
import type { ForceGraphProps, NodeObject, LinkObject } from "react-force-graph-2d";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -27,10 +28,23 @@ export interface GraphLink extends LinkObject {
|
|||
label: string;
|
||||
}
|
||||
|
||||
export interface GraphData {
|
||||
nodes: GraphNode[];
|
||||
links: GraphLink[];
|
||||
}
|
||||
const GraphNodeValue: S.Codec<GraphNode, GraphNode> = S.Struct({
|
||||
id: S.String,
|
||||
label: S.String,
|
||||
color: S.optionalKey(S.String),
|
||||
degree: S.Finite,
|
||||
});
|
||||
|
||||
const GraphLinkValue: S.Codec<GraphLink, GraphLink> = S.Struct({
|
||||
source: S.String,
|
||||
target: S.String,
|
||||
label: S.String,
|
||||
});
|
||||
|
||||
export class GraphData extends S.Class<GraphData>("GraphData")({
|
||||
nodes: S.Array(GraphNodeValue).pipe(S.mutable),
|
||||
links: S.Array(GraphLinkValue).pipe(S.mutable),
|
||||
}, { description: "Renderable graph nodes and links derived from triples." }) {}
|
||||
|
||||
export const DEFAULT_GRAPH_NODE_COLOR = "#82b582";
|
||||
|
||||
|
|
|
|||
|
|
@ -11,14 +11,14 @@ import {
|
|||
settingsAtom,
|
||||
} from "@/atoms/workbench";
|
||||
import type { BaseApi } from "@trustgraph/client";
|
||||
import type { MockWorkbenchFixture } from "@/qa/mock-api";
|
||||
import { makeMockBaseApi, qaSettingsFromFixture, } from "@/qa/mock-api";
|
||||
import { MockWorkbenchFixture, makeMockBaseApi, qaSettingsFromFixture, } from "@/qa/mock-api";
|
||||
import { Schema as S } from "effect";
|
||||
|
||||
export interface WorkbenchQaWindowConfig {
|
||||
readonly enabled?: boolean;
|
||||
readonly fixture?: MockWorkbenchFixture;
|
||||
readonly flowId?: string;
|
||||
}
|
||||
export class WorkbenchQaWindowConfig extends S.Class<WorkbenchQaWindowConfig>("WorkbenchQaWindowConfig")({
|
||||
enabled: S.optionalKey(S.Boolean),
|
||||
fixture: S.optionalKey(MockWorkbenchFixture),
|
||||
flowId: S.optionalKey(S.String),
|
||||
}, { description: "Browser-provided workbench QA boot configuration." }) {}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
|
|
|
|||
|
|
@ -4,42 +4,97 @@ import { Clock, Effect, Match, Option, Schema as S } from "effect";
|
|||
|
||||
type ConfigValues = Record<string, Record<string, unknown>>;
|
||||
|
||||
export interface MockWorkbenchFixture {
|
||||
readonly settings?: {
|
||||
readonly user?: string;
|
||||
readonly apiKey?: string;
|
||||
readonly gatewayUrl?: string;
|
||||
readonly collection?: string;
|
||||
readonly featureSwitches?: Record<string, boolean>;
|
||||
};
|
||||
readonly flows?: {
|
||||
readonly activeIds?: string[];
|
||||
readonly definitions?: Record<string, Record<string, unknown>>;
|
||||
readonly blueprints?: Record<string, Record<string, unknown>>;
|
||||
};
|
||||
readonly config?: {
|
||||
readonly prompt?: Record<string, unknown>;
|
||||
readonly valuesByType?: ConfigValues;
|
||||
};
|
||||
readonly library?: {
|
||||
readonly documents?: DocumentMetadata[];
|
||||
readonly processing?: ProcessingMetadata[];
|
||||
readonly metadataById?: Record<string, DocumentMetadata>;
|
||||
};
|
||||
readonly knowledge?: {
|
||||
readonly kgCores?: string[];
|
||||
readonly deCores?: string[];
|
||||
readonly loadedKgCores?: string[];
|
||||
};
|
||||
readonly collections?: Array<Record<string, unknown>>;
|
||||
readonly graph?: {
|
||||
readonly triplesByFlowCollection?: Record<string, Triple[]>;
|
||||
readonly explainTriplesByGraph?: Record<string, Triple[]>;
|
||||
};
|
||||
readonly chat?: {
|
||||
readonly delayFrames?: number;
|
||||
};
|
||||
}
|
||||
const UnknownRecord = S.Record(S.String, S.Unknown);
|
||||
const ConfigValuesRecord = S.Record(S.String, UnknownRecord);
|
||||
|
||||
const ClientTerm: S.Codec<Triple["s"], Triple["s"]> = S.suspend(() =>
|
||||
S.Union([
|
||||
S.Struct({ t: S.Literal("i"), i: S.String }),
|
||||
S.Struct({ t: S.Literal("b"), d: S.String }),
|
||||
S.Struct({
|
||||
t: S.Literal("l"),
|
||||
v: S.String,
|
||||
dt: S.optionalKey(S.String),
|
||||
ln: S.optionalKey(S.String),
|
||||
}),
|
||||
S.Struct({
|
||||
t: S.Literal("t"),
|
||||
tr: S.optionalKey(ClientTriple),
|
||||
}),
|
||||
])
|
||||
);
|
||||
|
||||
const ClientTriple: S.Codec<Triple, Triple> = S.suspend(() =>
|
||||
S.Struct({
|
||||
s: ClientTerm,
|
||||
p: ClientTerm,
|
||||
o: ClientTerm,
|
||||
g: S.optionalKey(S.String),
|
||||
})
|
||||
);
|
||||
|
||||
const DocumentMetadataValue: S.Codec<DocumentMetadata, DocumentMetadata> = S.Struct({
|
||||
id: S.optionalKey(S.String),
|
||||
time: S.optionalKey(S.Finite),
|
||||
kind: S.optionalKey(S.String),
|
||||
title: S.optionalKey(S.String),
|
||||
comments: S.optionalKey(S.String),
|
||||
metadata: S.optionalKey(S.Array(ClientTriple).pipe(S.mutable)),
|
||||
user: S.optionalKey(S.String),
|
||||
tags: S.optionalKey(S.Array(S.String).pipe(S.mutable)),
|
||||
parentId: S.optionalKey(S.String),
|
||||
documentType: S.optionalKey(S.String),
|
||||
"parent-id": S.optionalKey(S.String),
|
||||
"document-type": S.optionalKey(S.String),
|
||||
});
|
||||
|
||||
const ProcessingMetadataValue: S.Codec<ProcessingMetadata, ProcessingMetadata> = S.Struct({
|
||||
id: S.optionalKey(S.String),
|
||||
"document-id": S.optionalKey(S.String),
|
||||
documentId: S.optionalKey(S.String),
|
||||
time: S.optionalKey(S.Finite),
|
||||
flow: S.optionalKey(S.String),
|
||||
user: S.optionalKey(S.String),
|
||||
collection: S.optionalKey(S.String),
|
||||
tags: S.optionalKey(S.Array(S.String).pipe(S.mutable)),
|
||||
});
|
||||
|
||||
export class MockWorkbenchFixture extends S.Class<MockWorkbenchFixture>("MockWorkbenchFixture")({
|
||||
settings: S.optionalKey(S.Struct({
|
||||
user: S.optionalKey(S.String),
|
||||
apiKey: S.optionalKey(S.String),
|
||||
gatewayUrl: S.optionalKey(S.String),
|
||||
collection: S.optionalKey(S.String),
|
||||
featureSwitches: S.optionalKey(S.Record(S.String, S.Boolean)),
|
||||
})),
|
||||
flows: S.optionalKey(S.Struct({
|
||||
activeIds: S.optionalKey(S.Array(S.String).pipe(S.mutable)),
|
||||
definitions: S.optionalKey(S.Record(S.String, UnknownRecord)),
|
||||
blueprints: S.optionalKey(S.Record(S.String, UnknownRecord)),
|
||||
})),
|
||||
config: S.optionalKey(S.Struct({
|
||||
prompt: S.optionalKey(UnknownRecord),
|
||||
valuesByType: S.optionalKey(ConfigValuesRecord),
|
||||
})),
|
||||
library: S.optionalKey(S.Struct({
|
||||
documents: S.optionalKey(S.Array(DocumentMetadataValue).pipe(S.mutable)),
|
||||
processing: S.optionalKey(S.Array(ProcessingMetadataValue).pipe(S.mutable)),
|
||||
metadataById: S.optionalKey(S.Record(S.String, DocumentMetadataValue)),
|
||||
})),
|
||||
knowledge: S.optionalKey(S.Struct({
|
||||
kgCores: S.optionalKey(S.Array(S.String).pipe(S.mutable)),
|
||||
deCores: S.optionalKey(S.Array(S.String).pipe(S.mutable)),
|
||||
loadedKgCores: S.optionalKey(S.Array(S.String).pipe(S.mutable)),
|
||||
})),
|
||||
collections: S.optionalKey(S.Array(UnknownRecord).pipe(S.mutable)),
|
||||
graph: S.optionalKey(S.Struct({
|
||||
triplesByFlowCollection: S.optionalKey(S.Record(S.String, S.Array(ClientTriple).pipe(S.mutable))),
|
||||
explainTriplesByGraph: S.optionalKey(S.Record(S.String, S.Array(ClientTriple).pipe(S.mutable))),
|
||||
})),
|
||||
chat: S.optionalKey(S.Struct({
|
||||
delayFrames: S.optionalKey(S.Finite),
|
||||
})),
|
||||
}, { description: "Seed fixture for deterministic workbench QA runs." }) {}
|
||||
|
||||
interface UploadSession {
|
||||
readonly metadata: DocumentMetadata;
|
||||
|
|
|
|||
Binary file not shown.
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue