model config and provider fixes and inline dialog
This commit is contained in:
parent
7350e52f2f
commit
2a9a332ce3
10 changed files with 506 additions and 41 deletions
284
docs/canvas-lab-architecture.md
Normal file
284
docs/canvas-lab-architecture.md
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
# Canvas Lab Architecture (Current)
|
||||
|
||||
Root:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab`
|
||||
|
||||
This doc explains current architecture, how nodes map to payload/import, and how to add new blocks safely.
|
||||
|
||||
## 1) High-level flow
|
||||
|
||||
1. UI renders canvas + dialogs in:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/canvas-lab-page.tsx`
|
||||
2. Add-block sheet uses registry metadata to create config objects:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/components/block-sheet.tsx`
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/blocks/registry.tsx`
|
||||
3. Zustand store owns nodes/edges/configs and all mutation logic:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/stores/canvas-lab.ts`
|
||||
4. Graph connection logic updates references + semantic edges:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/utils/graph.ts`
|
||||
5. Export (preview/copy) converts in-memory graph/config to API payload:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/utils/payload.ts`
|
||||
6. Import reconstructs configs, nodes, edges from JSON:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/utils/import/importer.ts`
|
||||
|
||||
## 2) Core types (single source of truth)
|
||||
|
||||
File:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/types/index.ts`
|
||||
|
||||
`NodeConfig` is the main union the whole feature uses:
|
||||
|
||||
```ts
|
||||
export type NodeConfig =
|
||||
| SamplerConfig
|
||||
| LlmConfig
|
||||
| ExpressionConfig
|
||||
| ModelProviderConfig
|
||||
| ModelConfig;
|
||||
```
|
||||
|
||||
Canvas node UI data (`CanvasNodeData`) is derived from config via `nodeDataFromConfig`.
|
||||
|
||||
## 3) Entrypoint wiring
|
||||
|
||||
File:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/canvas-lab-page.tsx`
|
||||
|
||||
Key wiring:
|
||||
|
||||
```ts
|
||||
const NODE_TYPES: NodeTypes = { builder: CanvasNode };
|
||||
const EDGE_TYPES: EdgeTypes = { canvas: CanvasEdge, semantic: CanvasEdge };
|
||||
```
|
||||
|
||||
`CanvasLabPage` pulls actions/state from store and passes add handlers into `BlockSheet`:
|
||||
|
||||
```ts
|
||||
<BlockSheet
|
||||
onAddSampler={addSamplerNode}
|
||||
onAddLlm={addLlmNode}
|
||||
onAddModelProvider={addModelProviderNode}
|
||||
onAddModelConfig={addModelConfigNode}
|
||||
onAddExpression={addExpressionNode}
|
||||
/>
|
||||
```
|
||||
|
||||
Preview/copy route through `buildCanvasPayload`, import route through `importCanvasPayload`.
|
||||
|
||||
## 4) Registry-driven block system
|
||||
|
||||
File:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/blocks/registry.tsx`
|
||||
|
||||
Registry defines each block in one place:
|
||||
- sheet title/icon/description
|
||||
- config factory (`createConfig`)
|
||||
- config dialog (`renderDialog`)
|
||||
|
||||
Example (model blocks):
|
||||
|
||||
```ts
|
||||
{
|
||||
kind: "llm",
|
||||
type: "model_provider",
|
||||
createConfig: (id, existing) => makeModelProviderConfig(id, existing),
|
||||
renderDialog: ({ config, onUpdate }) =>
|
||||
config.kind === "model_provider" ? (
|
||||
<ModelProviderDialog config={config} onUpdate={(patch) => onUpdate(config.id, patch)} />
|
||||
) : null,
|
||||
}
|
||||
```
|
||||
|
||||
Important: `getBlockDefinitionForConfig` must map every new `config.kind`, else dialog won't render.
|
||||
|
||||
## 5) Config factories + node label mapping
|
||||
|
||||
File:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/utils/index.ts`
|
||||
|
||||
Responsibilities:
|
||||
- create default config objects (`makeSamplerConfig`, `makeLlmConfig`, `makeModelProviderConfig`, `makeModelConfig`, `makeExpressionConfig`)
|
||||
- map `NodeConfig -> CanvasNodeData` via `nodeDataFromConfig`
|
||||
|
||||
Example mapping:
|
||||
|
||||
```ts
|
||||
if (config.kind === "model_provider") {
|
||||
return {
|
||||
title: "Model Provider",
|
||||
kind: "model_provider",
|
||||
subtype: config.provider_type || "Provider",
|
||||
blockType: "model_provider",
|
||||
name: config.name,
|
||||
layoutDirection,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
This is what controls visible node title/subtitle in the canvas.
|
||||
|
||||
## 6) Store responsibilities
|
||||
|
||||
File:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/stores/canvas-lab.ts`
|
||||
|
||||
Store owns:
|
||||
- graph state (`nodes`, `edges`)
|
||||
- config map (`configs[id]`)
|
||||
- add/update/remove/connect operations
|
||||
- layout direction + apply layout
|
||||
|
||||
Add-node pattern (all block types follow same shape):
|
||||
|
||||
```ts
|
||||
const definition = getBlockDefinition("llm", "model_config");
|
||||
const config = definition.createConfig(id, existing);
|
||||
return buildNodeUpdate(state, config, state.layoutDirection);
|
||||
```
|
||||
|
||||
When model config `provider` field changes, store auto-syncs semantic edge to matching provider name.
|
||||
|
||||
## 7) Edge semantics + connection behavior
|
||||
|
||||
File:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/utils/graph.ts`
|
||||
|
||||
Semantic edge classifier:
|
||||
|
||||
```ts
|
||||
function isSemanticEdge(source: NodeConfig, target: NodeConfig): boolean {
|
||||
if (source.kind === "model_provider" && target.kind === "model_config") return true;
|
||||
if (source.kind === "model_config" && target.kind === "llm") return true;
|
||||
return source.kind === "sampler" && source.sampler_type === "category"
|
||||
&& target.kind === "sampler" && target.sampler_type === "subcategory";
|
||||
}
|
||||
```
|
||||
|
||||
Connection side effects:
|
||||
- `model_provider -> model_config`: set `model_config.provider = source.name`
|
||||
- `model_config -> llm`: set `llm.model_alias = source.name`
|
||||
- regular data edges into LLM/expression append `{{ source_name }}` refs
|
||||
|
||||
Edge rendering (dotted semantic edges):
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/components/canvas-edge.tsx`
|
||||
|
||||
```ts
|
||||
const nextStyle = type === "semantic"
|
||||
? { ...style, strokeDasharray: "4 4" }
|
||||
: style;
|
||||
```
|
||||
|
||||
## 8) Rename/remove propagation
|
||||
|
||||
File:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/stores/canvas-lab-helpers.ts`
|
||||
|
||||
Centralized consistency updates:
|
||||
- rename updates:
|
||||
- Jinja refs in `llm.prompt/system_prompt/output_format`
|
||||
- expression `expr`
|
||||
- subcategory parent
|
||||
- `model_config.provider`
|
||||
- `llm.model_alias`
|
||||
- removal clears same references
|
||||
|
||||
This keeps graph fields stable when upstream nodes renamed/deleted.
|
||||
|
||||
## 9) Payload building (node graph -> API)
|
||||
|
||||
File:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/utils/payload.ts`
|
||||
|
||||
`buildCanvasPayload(configs, nodes, edges)` outputs:
|
||||
|
||||
```ts
|
||||
{
|
||||
recipe: {
|
||||
model_providers: [...],
|
||||
model_configs: [...],
|
||||
columns: [...],
|
||||
processors: [],
|
||||
},
|
||||
run: { rows: 5, preview: true, output_formats: ["jsonl"] },
|
||||
ui: { nodes: [...], edges: [...] }
|
||||
}
|
||||
```
|
||||
|
||||
How relation is enforced:
|
||||
- collect `model_alias` values used by LLM columns
|
||||
- ensure each alias exists in `recipe.model_configs`
|
||||
- validate `model_config.provider` points to existing provider
|
||||
- require endpoint/provider_type only for providers that are actually referenced
|
||||
|
||||
This is why unused provider/config blocks can exist without blocking preview.
|
||||
|
||||
## 10) Import pipeline (API -> node graph)
|
||||
|
||||
Entry file:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/utils/import/importer.ts`
|
||||
|
||||
Order of reconstruction:
|
||||
1. parse `recipe.model_providers` -> `ModelProviderConfig`
|
||||
2. parse `recipe.model_configs` -> `ModelConfig`
|
||||
3. parse `recipe.columns` -> sampler/llm/expression
|
||||
4. build nodes with positions
|
||||
5. build edges
|
||||
|
||||
Edge inference file:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/utils/import/edges.ts`
|
||||
|
||||
If UI edges missing, infer semantic edges from fields:
|
||||
- `subcategory_parent`
|
||||
- `model_config.provider`
|
||||
- `llm.model_alias`
|
||||
|
||||
## 11) Dialog routing and edit UIs
|
||||
|
||||
Config dialog shell:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/dialogs/config-dialog.tsx`
|
||||
|
||||
It calls:
|
||||
`renderBlockDialog(config, categoryOptions, onUpdate)`
|
||||
|
||||
Model dialogs:
|
||||
- provider:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/dialogs/models/model-provider-dialog.tsx`
|
||||
- model config:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/dialogs/models/model-config-dialog.tsx`
|
||||
|
||||
`ModelConfigDialog` and `LlmDialog` use shadcn `Combobox` fed from store configs:
|
||||
- model config `provider` suggests model-provider node names
|
||||
- llm `model_alias` suggests model-config aliases
|
||||
|
||||
## 12) How to add a new block (checklist)
|
||||
|
||||
Minimal path for a new block type:
|
||||
|
||||
1. Add/extend type in:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/types/index.ts`
|
||||
2. Add default factory + node label mapping in:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/utils/index.ts`
|
||||
3. Add block definition in:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/blocks/registry.tsx`
|
||||
4. Add dialog component and route it via `renderDialog` in registry.
|
||||
5. Add store add-action if block should be special-cased from sheet:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/stores/canvas-lab.ts`
|
||||
6. Add payload serialization/validation in:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/utils/payload.ts`
|
||||
7. Add import parsing + inferred edges in:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/utils/import/parsers.ts`
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/utils/import/importer.ts`
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/utils/import/edges.ts`
|
||||
8. If connection has semantic meaning, extend:
|
||||
`/Volumes/Expansion/projects/new-ui-prototype/studio/frontend/src/features/canvas-lab/utils/graph.ts`
|
||||
|
||||
## 13) Practical mental model
|
||||
|
||||
- `NodeConfig` is source-of-truth business state.
|
||||
- `CanvasNodeData` is derived display state.
|
||||
- registry = block metadata + factories + dialog routing.
|
||||
- store = mutation orchestration.
|
||||
- graph utils = connection semantics.
|
||||
- payload/import utils = external contract boundary.
|
||||
|
||||
If one piece changes, keep all six in sync.
|
||||
Loading…
Add table
Add a link
Reference in a new issue