Skip to content

Commit 1105008

Browse files
committed
feat(v7-k3): suggest_adapters UI call-site in GotchasTab
Honest-closure: suggest_adapters RPC was registered in dispatcher since the original visual builder commit but had zero UI call-sites — the adapter chain helper that bridges incompatible producer→consumer brick pairs was unreachable from the canvas. - GotchasTab: AdapterChain + AdapterStep types; new suggest-adapters panel at top of the tab with two free-text inputs (producer/consumer brick ids) + Suggest button. Renders chain steps as an ordered list with rule + description per step; reason + producer/consumer shapes shown above. Empty-chain branch shows '✓ no adapter needed' so the user knows the bridge is trivial. - Sidebar: onSuggestAdapters prop pass-through to GotchasTab. - App.tsx: callback builds graph+dim_env via buildVerifyParams and fires rpc.call('suggest_adapters', {graph, dim_env, producer, consumer, max_steps:4}).
1 parent d26dfb9 commit 1105008

3 files changed

Lines changed: 137 additions & 2 deletions

File tree

vbgui/src/App.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,6 +1084,27 @@ export function App(): JSX.Element {
10841084
} as never };
10851085
}));
10861086
}}
1087+
onSuggestAdapters={async (producer, consumer) => {
1088+
// V7-K3: suggest_adapters RPC — UI sends current
1089+
// graph + dim_env + producer/consumer pair, backend
1090+
// returns the adapter chain that would bridge them.
1091+
const snap = wireSpecRef.current;
1092+
const verify = buildVerifyParams(
1093+
snap.nodes, snap.edges, snap.spec,
1094+
snap.availableSideChannels);
1095+
return rpc.call<{
1096+
producer: string; consumer: string;
1097+
producer_shape: number[]; consumer_shape: number[];
1098+
chain: Array<{ rule: string; description: string;
1099+
params?: Record<string, unknown> }>;
1100+
reason: string;
1101+
}>("suggest_adapters", {
1102+
graph: verify.graph,
1103+
dim_env: verify.dim_env,
1104+
producer, consumer,
1105+
max_steps: 4,
1106+
});
1107+
}}
10871108
/>
10881109
</>
10891110
)}

vbgui/src/components/Sidebar.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ export interface SidebarProps {
3838
onShardingChange: (s: ShardingState) => void;
3939
onShardingAccept: (idx: number) => void;
4040
onGotchaAutoFix?: (id: string) => void;
41+
/** V7-K3: GotchasTab forwards suggest_adapters calls up to App. */
42+
onSuggestAdapters?: (producer: string, consumer: string) =>
43+
Promise<import("./sidebar/GotchasTab").AdapterChain>;
4144
/** Optional rpc client — enables tooltip/explain integration in
4245
* OptimTab and downstream tabs. App passes useRpc() here. */
4346
rpc?: import("@/lib/rpc").RpcClient | null;
@@ -50,6 +53,7 @@ export interface SidebarProps {
5053
/** H07: parent dispatches the per-brick params mutation when the
5154
* user clicks Apply on an auto-inferred row in DimensionsTab. */
5255
onDimensionsApply?: (entry: InferenceEntryClient) => void;
56+
tokenizerSource?: string | null;
5357
}
5458

5559
const TAB_LABELS: { key: SidebarTab; label: string }[] = [
@@ -110,6 +114,8 @@ export function Sidebar(p: SidebarProps): JSX.Element {
110114
availableChannels={p.availableSideChannels}
111115
selectedTrainChannels={p.selectedTrainSideChannels}
112116
gotchas={p.gotchas}
117+
rpc={p.rpc ?? null}
118+
tokenizerSource={p.tokenizerSource ?? null}
113119
onApply={p.onSideChannelsApply}
114120
onTrainChannelsChange={
115121
p.onTrainSideChannelsChange} />
@@ -120,7 +126,9 @@ export function Sidebar(p: SidebarProps): JSX.Element {
120126
onChange={p.onShardingChange} />
121127
)}
122128
{active === "gotchas" && (
123-
<GotchasTab gotchas={p.gotchas} onAutoFix={p.onGotchaAutoFix} />
129+
<GotchasTab gotchas={p.gotchas}
130+
onAutoFix={p.onGotchaAutoFix}
131+
onSuggestAdapters={p.onSuggestAdapters} />
124132
)}
125133
{active === "dimensions" && (
126134
<DimensionsTab log={p.inferenceLog ?? []}

vbgui/src/components/sidebar/GotchasTab.tsx

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,29 @@
1+
import { useState } from "react";
12
import type { GotchaState } from "@/state/spec";
23

4+
export interface AdapterStep {
5+
rule: string;
6+
description: string;
7+
params?: Record<string, unknown>;
8+
}
9+
10+
export interface AdapterChain {
11+
producer: string;
12+
consumer: string;
13+
producer_shape: number[];
14+
consumer_shape: number[];
15+
chain: AdapterStep[];
16+
reason: string;
17+
}
18+
319
export interface GotchasTabProps {
420
gotchas: GotchaState[];
521
onAutoFix?: (id: string) => void;
22+
/** V7-K3: suggest_adapters callback — UI sends producer/consumer
23+
* brick names, backend returns the adapter chain that would bridge
24+
* them. Rendered inline so users can see how to fix an edge gap. */
25+
onSuggestAdapters?: (producer: string,
26+
consumer: string) => Promise<AdapterChain>;
627
}
728

829
const COLOR: Record<GotchaState["severity"], string> = {
@@ -42,10 +63,95 @@ function groupBySeverity(gs: GotchaState[]): Record<string, GotchaState[]> {
4263
return out;
4364
}
4465

45-
export function GotchasTab({ gotchas, onAutoFix }: GotchasTabProps): JSX.Element {
66+
export function GotchasTab({
67+
gotchas, onAutoFix, onSuggestAdapters,
68+
}: GotchasTabProps): JSX.Element {
4669
const grouped = groupBySeverity(gotchas);
70+
const [adapterProducer, setAdapterProducer] = useState<string>("");
71+
const [adapterConsumer, setAdapterConsumer] = useState<string>("");
72+
const [adapterChain, setAdapterChain] = useState<AdapterChain | null>(null);
73+
const [adapterError, setAdapterError] = useState<string | null>(null);
74+
const [adapterLoading, setAdapterLoading] = useState<boolean>(false);
4775
return (
4876
<div data-testid="gotchas-tab" style={panel}>
77+
{onSuggestAdapters && (
78+
<section data-testid="gotchas-suggest-adapters-panel"
79+
style={{ background: "#f3f4f6", padding: 8, borderRadius: 4,
80+
fontSize: 11 }}>
81+
<div style={{ fontWeight: 600, marginBottom: 4 }}>
82+
Suggest adapter chain (V7-K3)
83+
</div>
84+
<div style={{ display: "flex", gap: 4, marginBottom: 4 }}>
85+
<input data-testid="gotchas-suggest-adapters-producer"
86+
placeholder="producer brick id"
87+
value={adapterProducer}
88+
onChange={(e) => setAdapterProducer(e.target.value)}
89+
style={{ flex: 1, fontFamily: "monospace",
90+
fontSize: 11 }} />
91+
<span></span>
92+
<input data-testid="gotchas-suggest-adapters-consumer"
93+
placeholder="consumer brick id"
94+
value={adapterConsumer}
95+
onChange={(e) => setAdapterConsumer(e.target.value)}
96+
style={{ flex: 1, fontFamily: "monospace",
97+
fontSize: 11 }} />
98+
<button data-testid="gotchas-suggest-adapters-run"
99+
disabled={adapterLoading || !adapterProducer
100+
|| !adapterConsumer}
101+
onClick={async () => {
102+
setAdapterLoading(true);
103+
setAdapterError(null);
104+
try {
105+
const r = await onSuggestAdapters(
106+
adapterProducer, adapterConsumer);
107+
setAdapterChain(r);
108+
} catch (e) {
109+
setAdapterError(
110+
e instanceof Error ? e.message : String(e));
111+
} finally {
112+
setAdapterLoading(false);
113+
}
114+
}}>
115+
{adapterLoading ? "…" : "Suggest"}
116+
</button>
117+
</div>
118+
{adapterError && (
119+
<div data-testid="gotchas-suggest-adapters-error"
120+
style={{ color: "#dc2626" }}>{adapterError}</div>
121+
)}
122+
{adapterChain && (
123+
<div data-testid="gotchas-suggest-adapters-result"
124+
style={{ fontFamily: "monospace" }}>
125+
<div data-testid="gotchas-suggest-adapters-shapes">
126+
{adapterChain.producer}{" "}
127+
[{adapterChain.producer_shape.join("×")}] →{" "}
128+
{adapterChain.consumer}{" "}
129+
[{adapterChain.consumer_shape.join("×")}]
130+
</div>
131+
<div data-testid="gotchas-suggest-adapters-reason"
132+
style={{ color: "#6b7280", marginTop: 2 }}>
133+
{adapterChain.reason}
134+
</div>
135+
{adapterChain.chain.length === 0 ? (
136+
<div data-testid="gotchas-suggest-adapters-chain-empty"
137+
style={{ color: "#16a34a", marginTop: 2 }}>
138+
✓ no adapter needed
139+
</div>
140+
) : (
141+
<ol data-testid="gotchas-suggest-adapters-chain"
142+
style={{ margin: "4px 0 0 18px", padding: 0 }}>
143+
{adapterChain.chain.map((step, i) => (
144+
<li key={i}
145+
data-testid={`gotchas-suggest-adapters-step-${i}`}>
146+
<strong>{step.rule}</strong>: {step.description}
147+
</li>
148+
))}
149+
</ol>
150+
)}
151+
</div>
152+
)}
153+
</section>
154+
)}
49155
{gotchas.length === 0 && (
50156
<p style={{ color: "#9ca3af" }}>No gotchas fired.</p>
51157
)}

0 commit comments

Comments
 (0)