@theoremjs/studio 0.3.4-next.413.gb7a2b75 → 0.3.4-next.415.g3597d47
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 +1 -1
- package/mod.d.ts +1 -1
- package/mod.js +1 -1
- package/package.json +4 -4
- package/page/assets/Banner-8YJcS8-V.js +2 -0
- package/page/assets/CollapsibleGroup-PavPZ114.js +1 -0
- package/page/assets/ConsoleFrame-DU_VbYY8.js +3 -0
- package/page/assets/{ContextMenu-COkl-sLz.js → ContextMenu-BM8iCtOh.js} +2 -2
- package/page/assets/{ToolResultCharts-UmFnfSOp.js → ToolResultCharts-2ZnwTSBM.js} +1 -1
- package/page/assets/TraceInspector-BMPAqts9.js +1 -0
- package/page/assets/{TraceOverview-BiCh-eqR.js → TraceOverview-CAcG99rb.js} +1 -1
- package/page/assets/{browser-engine-BY6gYiHv.js → browser-engine-CZIMsNT9.js} +1 -1
- package/page/assets/{browser-transport-B4Or1xM7.js → browser-transport-1C-yqvF_.js} +4 -4
- package/page/assets/{export-agent-D8hLzzFn.js → export-agent-CtTlaPls.js} +1 -1
- package/page/assets/{guardrail-tester-C04dSfNv.js → guardrail-tester-DSqEFs8L.js} +2 -2
- package/page/assets/{index-Br0mAcH5.js → index-BCMKIa3t.js} +68 -68
- package/page/assets/index-Cbs7B46A.css +1 -0
- package/page/assets/labels-provider-BSiD6Lrb.js +3 -0
- package/page/assets/{studio-code-pane-B0BB_yGF.js → studio-code-pane-PK_Sla3w.js} +1 -1
- package/page/assets/{studio-console-BLHAfqNd.js → studio-console-CfDp9X-T.js} +1 -1
- package/page/assets/{studio-decision-Cfc9arc7.js → studio-decision-B4KZCD5V.js} +1 -1
- package/page/assets/{studio-embedding-CGSaA2dS.js → studio-embedding-CB4lSSWM.js} +1 -1
- package/page/assets/{studio-host-console-BidxEK6_.js → studio-host-console-C3PBxw_t.js} +2 -2
- package/page/assets/{studio-labels-CWLqOvKg.js → studio-labels-Jw1KQ1X1.js} +1 -1
- package/page/assets/{studio-live-C8XxDpXT.js → studio-live-BfBbeQVS.js} +2 -2
- package/page/assets/{studio-map-C2zIkdcp.js → studio-map-D3GkuREn.js} +1 -1
- package/page/assets/{studio-trace-replay-CpbB-zpo.js → studio-trace-replay-BSDZA9i6.js} +1 -1
- package/page/assets/{use-one-call-BJhdZLBt.js → use-one-call-CY-cA5FL.js} +1 -1
- package/page/assets/{SidePanel-OYp0bLNO.js → use-trace-records-BkAYJz0M.js} +1 -1
- package/page/assets/workspace-MlrGUH2-.js +1 -0
- package/page/index.html +10 -10
- package/ui/inspector-context.d.ts +5 -0
- package/ui/inspector-context.js +2 -0
- package/ui/profile-editor.js +10 -3
- package/ui/studio-run.js +2 -12
- package/ui/studio-screen.js +141 -158
- package/ui/studio.css +29 -13
- package/ui/use-phone.d.ts +2 -0
- package/ui/use-phone.js +13 -0
- package/workspace.d.ts +7 -0
- package/workspace.js +48 -0
- package/page/assets/Banner-CIcZcE2z.js +0 -2
- package/page/assets/CollapsibleGroup-CL8h1IzH.js +0 -1
- package/page/assets/ConsoleFrame-Ix0RXm_R.js +0 -3
- package/page/assets/TraceInspector-DWw6BGA5.js +0 -1
- package/page/assets/index-Oo9otEIT.css +0 -1
- package/page/assets/labels-provider-BqgbdxdT.js +0 -3
- package/page/assets/workspace-SpjcVXbF.js +0 -1
package/ui/studio-screen.js
CHANGED
|
@@ -21,11 +21,11 @@ import { Token } from '@astryxdesign/core/Token';
|
|
|
21
21
|
import { Tooltip } from '@astryxdesign/core/Tooltip';
|
|
22
22
|
import { TreeList } from '@astryxdesign/core/TreeList';
|
|
23
23
|
import { VStack } from '@astryxdesign/core/VStack';
|
|
24
|
-
import { IconActivity, IconAdjustmentsHorizontal, IconArrowBarToDown, IconArrowLeft, IconBrowserShare, IconCheck, IconCloud, IconCode, IconCopy, IconCopyPlus,
|
|
24
|
+
import { IconActivity, IconAdjustmentsHorizontal, IconArrowBarToDown, IconArrowLeft, IconBrowserShare, IconCheck, IconCloud, IconCode, IconCopy, IconCopyPlus, IconFile, IconFileImport, IconFileSmile, IconFileZip, IconKey, IconMenu2, IconPlayerPlay, IconPlaylistX, IconPlus, IconRotateClockwise, IconSearch, IconSitemap, IconSparkles, IconTool, IconX, } from '@tabler/icons-react';
|
|
25
25
|
import { profileGraphFacet } from '@theoremjs/agents';
|
|
26
26
|
import { InPlace, PaneFailure, RaisedPane, useDisclosureMotion, } from '@theoremjs/react/ui';
|
|
27
27
|
import { canonical } from "../canonical.js";
|
|
28
|
-
import { addAgent, addArchitectExample, addProvider, agentNodeId, compileWorkspace, createBlankDraft, createConsoleExampleDraft, createDecisionExampleDraft, createExampleDraft, createGeminiEmbeddingExampleDraft, createLiveExampleDraft, createNarratorExampleDraft, createStudioRunId, draftFacets, duplicateAgent, editedSince, excludeFacet, includableFacets, includeFacet, libraryDraft, localProviderAddress, mergedWithFiles, modelBindingNodeId, modelRoute, modelSharers, modelSlots, nodeOrigins, onStudioLocal, openOutcome, originLabel, originPlace, originWay, providerIdOf, providerNodeId, providerUse, rebased, removeAgent, removeLibraryTool, removeProvider, renameProvider, reopened, resetAgent,
|
|
28
|
+
import { addAgent, addArchitectExample, addProvider, agentNodeId, compileWorkspace, createBlankDraft, createConsoleExampleDraft, createDecisionExampleDraft, createExampleDraft, createGeminiEmbeddingExampleDraft, createLiveExampleDraft, createNarratorExampleDraft, createStudioRunId, draftFacets, duplicateAgent, editedSince, excludeFacet, includableFacets, includeFacet, libraryDraft, localProviderAddress, mergedWithFiles, modelBindingNodeId, modelRoute, modelSharers, modelSlots, nodeOrigins, onStudioLocal, openOutcome, originLabel, originPlace, originWay, providerIdOf, providerNodeId, providerUse, rebased, removeAgent, removeLibraryTool, removeProvider, renameProvider, reopened, resetAgent, resetAgentNode, resetLibraryTool, resetProvider, sampleToolInput, saveStudioRunPayload, scopedNodeId, setToolAllowed, sharedAsk, sharedAt, sharedEntries, studioNodeRef, studioSource, studioTree, toolSpecKeyOf, withAgentDraft, withFilesChosen, withProvider, workspaceNodeRef, workspaceRunAgent, workspaceTree, writeAsk, } from "../mod.js";
|
|
29
29
|
import { studioSurface } from "../surface.js";
|
|
30
30
|
import './studio.css';
|
|
31
31
|
import { createTraceFeed } from '@theoremjs/web/client';
|
|
@@ -35,7 +35,7 @@ import { ActingAsButton } from "./acting-as-button.js";
|
|
|
35
35
|
import { AddProviderButton } from "./add-provider.js";
|
|
36
36
|
import { IconTypeSafe, modelFamilyMark } from "./brand-icons.js";
|
|
37
37
|
import { useConfirmWrite } from "./confirm-write.js";
|
|
38
|
-
import { ConfirmWrite, ISSUE_ROW_ATTRIBUTE, LeavePage, ListBadges, LocalConnection, ProbedAgent, ProjectAsks, QuestionLink, RowOrigins, RunsOn, WorkspaceContext, } from "./inspector-context.js";
|
|
38
|
+
import { ConfirmWrite, GuardrailTest, ISSUE_ROW_ATTRIBUTE, LeavePage, ListBadges, LocalConnection, ProbedAgent, ProjectAsks, QuestionLink, RowOrigins, RunsOn, WorkspaceContext, } from "./inspector-context.js";
|
|
39
39
|
import { ActingAsContext, chatKeyOf, useActingAsState } from "./lib/acting-as.js";
|
|
40
40
|
import { FACET_ICON } from "./lib/facet-icons.js";
|
|
41
41
|
import { movePanel } from "./lib/panel-motion.js";
|
|
@@ -65,6 +65,7 @@ import { StudioHostContext, useStudioHost, } from "./studio-host.js";
|
|
|
65
65
|
import { NAV_WIDTH } from "./studio-nav.js";
|
|
66
66
|
import { StudioRunner } from "./studio-runner.js";
|
|
67
67
|
import { ProjectSave, ProjectTokens, useProjectSave } from "./studio-save.js";
|
|
68
|
+
import { usePhone } from "./use-phone.js";
|
|
68
69
|
// The tester, Settings, the map and the code view load when they first open, or once the page is idle.
|
|
69
70
|
const testerPanel = () => import("./guardrail-tester.js");
|
|
70
71
|
const settingsPanel = () => import("./studio-settings.js");
|
|
@@ -101,26 +102,31 @@ function actionButton(label, icon, onPress) {
|
|
|
101
102
|
return (_jsx(IconButton, { label: label, variant: "ghost", size: "sm", icon: _jsx(Icon, { icon: icon, size: "sm" }), tooltip: label, onClick: (event) => {
|
|
102
103
|
event.stopPropagation();
|
|
103
104
|
onPress();
|
|
104
|
-
} }));
|
|
105
|
+
} }, label));
|
|
105
106
|
}
|
|
106
107
|
function rowAction(label, icon, onPress) {
|
|
107
108
|
return _jsx("span", { className: "studio-tree-action", children: actionButton(label, icon, onPress) });
|
|
108
109
|
}
|
|
110
|
+
/** A row's hover actions: Reset, while it differs from its start, then the others, the x last. */
|
|
111
|
+
function rowActions(label, reset, ...actions) {
|
|
112
|
+
const shown = [reset && actionButton(`Reset ${label}`, IconRotateClockwise, reset), ...actions];
|
|
113
|
+
return shown.some(Boolean) ? _jsx("span", { className: "studio-tree-action", children: shown }) : undefined;
|
|
114
|
+
}
|
|
109
115
|
function treeItem(tree, node, isTop = false) {
|
|
110
116
|
const { draft, scope, selectedId, onSelect, setDraft } = tree;
|
|
111
117
|
const facet = node.ref.facet;
|
|
112
118
|
const id = scope(node.id);
|
|
113
119
|
const isOptional = isTop && facet !== 'toolSpec' && profileGraphFacet(facet)?.optional === true;
|
|
120
|
+
const actions = rowActions(node.label, tree.resetOf(node.ref, node.label), isOptional &&
|
|
121
|
+
actionButton(`Remove ${node.label}`, IconX, () => {
|
|
122
|
+
setDraft((current) => excludeFacet(current, facet));
|
|
123
|
+
}));
|
|
114
124
|
return {
|
|
115
125
|
id,
|
|
116
126
|
label: node.label,
|
|
117
127
|
startContent: _jsx(Icon, { icon: nodeIcon(draft, node.ref), size: "sm", color: "secondary" }),
|
|
118
|
-
endContent:
|
|
119
|
-
|
|
120
|
-
setDraft((current) => excludeFacet(current, facet));
|
|
121
|
-
})
|
|
122
|
-
: undefined,
|
|
123
|
-
className: isOptional ? 'studio-tree-row' : undefined,
|
|
128
|
+
endContent: actions,
|
|
129
|
+
className: actions ? 'studio-tree-row' : undefined,
|
|
124
130
|
isSelected: id === selectedId,
|
|
125
131
|
isExpanded: node.children.length > 0,
|
|
126
132
|
onClick: () => {
|
|
@@ -171,7 +177,7 @@ function agentItem(tree, isOpen, actions) {
|
|
|
171
177
|
};
|
|
172
178
|
}
|
|
173
179
|
function agentItems(state) {
|
|
174
|
-
const { workspace, focus, selectedId, onSelect, update, setDraft } = state;
|
|
180
|
+
const { workspace, focus, selectedId, onSelect, update, setDraft, reset } = state;
|
|
175
181
|
const isOnly = workspace.agents.length === 1;
|
|
176
182
|
return workspace.agents.map((agent) => {
|
|
177
183
|
const draft = libraryDraft(workspace, agent.key) ?? createBlankDraft();
|
|
@@ -182,16 +188,17 @@ function agentItems(state) {
|
|
|
182
188
|
selectedId,
|
|
183
189
|
onSelect,
|
|
184
190
|
setDraft,
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
+
resetOf: (ref, label) => reset(resetAgentNode(workspace, agent.key, ref), `Reset ${label} to how it started.`),
|
|
192
|
+
}, agent.key === focus, rowActions(name, reset(resetAgent(workspace, agent.key), `Reset ${name} to how it started.`), actionButton(`Duplicate ${name}`, IconCopyPlus, () => {
|
|
193
|
+
update((current) => duplicateAgent(current, agent.key));
|
|
194
|
+
}), !isOnly &&
|
|
195
|
+
actionButton(`Remove ${name}`, IconX, () => {
|
|
196
|
+
update((current) => removeAgent(current, agent.key));
|
|
197
|
+
})));
|
|
191
198
|
});
|
|
192
199
|
}
|
|
193
200
|
/** The tool library, every agent picks its tools from these: those whose name or description holds `query`. */
|
|
194
|
-
function toolItems({ workspace, selectedId, onSelect, update }, query) {
|
|
201
|
+
function toolItems({ workspace, selectedId, onSelect, update, reset }, query) {
|
|
195
202
|
const needle = query.trim().toLowerCase();
|
|
196
203
|
return workspaceTree(workspace).tools.flatMap((node) => {
|
|
197
204
|
const tool = workspace.toolSpecs.find((spec) => spec.key === toolSpecKeyOf(node.id));
|
|
@@ -203,10 +210,11 @@ function toolItems({ workspace, selectedId, onSelect, update }, query) {
|
|
|
203
210
|
id: node.id,
|
|
204
211
|
label: node.label,
|
|
205
212
|
startContent: (_jsx(Icon, { icon: tool ? toolTypeIcon(tool.toolType) : IconTool, size: "sm", color: "secondary" })),
|
|
206
|
-
endContent:
|
|
213
|
+
endContent: rowActions(node.label, tool &&
|
|
214
|
+
reset(resetLibraryTool(workspace, tool.key), `Reset ${node.label} to how it started.`), actionButton(`Remove ${node.label}`, IconX, () => {
|
|
207
215
|
if (tool)
|
|
208
216
|
update((current) => removeLibraryTool(current, tool.key));
|
|
209
|
-
}),
|
|
217
|
+
})),
|
|
210
218
|
className: 'studio-tree-row',
|
|
211
219
|
isSelected: node.id === selectedId,
|
|
212
220
|
onClick: () => {
|
|
@@ -226,7 +234,7 @@ function declaredAt({ setting }) {
|
|
|
226
234
|
* provider's row opens its own settings; an agent's opens its first model there.
|
|
227
235
|
*/
|
|
228
236
|
function providerItems(tree) {
|
|
229
|
-
const { workspace, selectedId, onSelect, update, ownsProviders } = tree;
|
|
237
|
+
const { workspace, selectedId, onSelect, update, ownsProviders, reset } = tree;
|
|
230
238
|
return workspace.providers.map((provider) => {
|
|
231
239
|
const id = providerNodeId(provider.id);
|
|
232
240
|
// One a model runs on stays: its row lists the agents to move first.
|
|
@@ -246,19 +254,19 @@ function providerItems(tree) {
|
|
|
246
254
|
},
|
|
247
255
|
];
|
|
248
256
|
});
|
|
257
|
+
const actions = rowActions(provider.id, reset(resetProvider(workspace, provider.id), `Reset ${provider.id} to how it started.`), isFree &&
|
|
258
|
+
actionButton(`Remove ${provider.id}`, IconX, () => {
|
|
259
|
+
update((current) => removeProvider(current, provider.id));
|
|
260
|
+
const [first] = workspace.agents;
|
|
261
|
+
if (selectedId === id && first)
|
|
262
|
+
onSelect(agentNodeId(first.key));
|
|
263
|
+
}));
|
|
249
264
|
return {
|
|
250
265
|
id,
|
|
251
266
|
label: provider.id,
|
|
252
267
|
startContent: _jsx(Icon, { icon: IconCloud, size: "sm", color: "secondary" }),
|
|
253
|
-
endContent:
|
|
254
|
-
|
|
255
|
-
update((current) => removeProvider(current, provider.id));
|
|
256
|
-
const [first] = workspace.agents;
|
|
257
|
-
if (selectedId === id && first)
|
|
258
|
-
onSelect(agentNodeId(first.key));
|
|
259
|
-
})
|
|
260
|
-
: undefined,
|
|
261
|
-
className: isFree ? 'studio-tree-row' : undefined,
|
|
268
|
+
endContent: actions,
|
|
269
|
+
className: actions ? 'studio-tree-row' : undefined,
|
|
262
270
|
isSelected: selectedId === id,
|
|
263
271
|
onClick: () => {
|
|
264
272
|
onSelect(id);
|
|
@@ -437,7 +445,7 @@ function WorkspaceTreeLists({ tree, draft, onAddAgent, listRef, }) {
|
|
|
437
445
|
} }), list === 'agents' ? _jsx(AddAgentMenu, { onAddAgent: onAddAgent }) : null, list === 'tools' && (_jsx(ToolSearchInput, { query: query, onChange: (next) => {
|
|
438
446
|
setQuery(next);
|
|
439
447
|
revealSelected();
|
|
440
|
-
} })), list === 'tools' ? _jsx(AddToolButton, { tree: tree, draft: draft }) : null, list === 'providers' && tree.ownsProviders ? (_jsx(AddProviderButton, { taken: tree.workspace.providers.map((provider) => provider.id), onAdd: (provider) => {
|
|
448
|
+
} })), list === 'tools' ? _jsx(AddToolButton, { tree: tree, draft: draft }) : null, list === 'providers' && tree.keys ? _jsx(ManageKeysButton, { keys: tree.keys }) : null, list === 'providers' && tree.ownsProviders ? (_jsx(AddProviderButton, { taken: tree.workspace.providers.map((provider) => provider.id), onAdd: (provider) => {
|
|
441
449
|
tree.update((current) => addProvider(current, provider));
|
|
442
450
|
tree.onSelect(providerNodeId(provider.id));
|
|
443
451
|
} })) : null, _jsx(StackItem, { size: "fill", children: _jsx(WorkspaceListBody, { tree: tree, list: list, tools: tools, query: query, listRef: listRef }) })] }));
|
|
@@ -839,17 +847,24 @@ function useLeftIssues(issues, page, revealed) {
|
|
|
839
847
|
const shown = useMemo(() => issues.filter((issue) => left.has(issueKey(issue))), [issues, left]);
|
|
840
848
|
return { shown, leavePage };
|
|
841
849
|
}
|
|
842
|
-
/**
|
|
843
|
-
function
|
|
850
|
+
/** Open in a new tab, for the agent being chatted with; off while the workspace has issues. */
|
|
851
|
+
function OpenInNewTabButton({ compiled, chattedId, blocked, connection, }) {
|
|
852
|
+
const project = useProject();
|
|
853
|
+
const phone = usePhone();
|
|
854
|
+
return (_jsx(Button, { label: "Open in a new tab", isIconOnly: phone, variant: "ghost", icon: _jsx(Icon, { icon: IconBrowserShare, size: "sm" }), isDisabled: !compiled, tooltip: blocked ??
|
|
855
|
+
'Run this agent on a full page of its own, in a new tab, the way a person using it would see it', onClick: () => {
|
|
856
|
+
const run = compiled && workspaceRunAgent(compiled, chattedId);
|
|
857
|
+
if (run)
|
|
858
|
+
openInNewTab({ ...runPayload(run), ...connection }, project);
|
|
859
|
+
} }));
|
|
860
|
+
}
|
|
861
|
+
/** Get code: a .zip, Copy, or Copy for LLM, for every agent; off while the workspace has issues. */
|
|
862
|
+
function ExportMenu({ store, compile, copy, }) {
|
|
844
863
|
const project = useProject();
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
} }), _jsx(ExportMenu, { code: () => projectCode(project, store.getWorkspace()), compiled: compiled, chatted: chatted, blocked: blocked, copy: copy })] }));
|
|
850
|
-
}
|
|
851
|
-
/** Get code: a .zip, Copy, or Copy for LLM. */
|
|
852
|
-
function ExportMenu({ code, compiled, chatted, blocked, copy, }) {
|
|
864
|
+
const compiled = compile.compiled.ok ? compile.compiled : undefined;
|
|
865
|
+
const { chatted, blocked } = compile;
|
|
866
|
+
/** What the project's files hold that the code names, read when the builder asks for the code. */
|
|
867
|
+
const code = () => projectCode(project, store.getWorkspace());
|
|
853
868
|
return (_jsx(DropdownMenu, { button: {
|
|
854
869
|
label: 'Get code',
|
|
855
870
|
variant: 'ghost',
|
|
@@ -1559,45 +1574,6 @@ function ViewToggleButton({ view }) {
|
|
|
1559
1574
|
},
|
|
1560
1575
|
] }));
|
|
1561
1576
|
}
|
|
1562
|
-
/**
|
|
1563
|
-
* Reset for what is open: a library tool, a provider, or else the agent. It goes back to how it joined the
|
|
1564
|
-
* workspace, and nothing else changes. `run` is absent while it is still as it started. `all` does
|
|
1565
|
-
* it for everything: every agent and tool back to its start, or on a project every unsaved edit
|
|
1566
|
-
* gone once the builder says yes. It is absent while there is nothing to put back.
|
|
1567
|
-
*/
|
|
1568
|
-
function useReset({ workspace, focus, update }) {
|
|
1569
|
-
const toast = useToast();
|
|
1570
|
-
const save = useProjectSave();
|
|
1571
|
-
const toolKey = toolSpecKeyOf(workspace.selected);
|
|
1572
|
-
const provider = providerIdOf(workspace.selected);
|
|
1573
|
-
const what = provider !== undefined ? 'provider' : toolKey === undefined ? 'agent' : 'tool';
|
|
1574
|
-
const next = useMemo(() => {
|
|
1575
|
-
if (provider !== undefined)
|
|
1576
|
-
return resetProvider(workspace, provider);
|
|
1577
|
-
return toolKey === undefined
|
|
1578
|
-
? resetAgent(workspace, focus)
|
|
1579
|
-
: resetLibraryTool(workspace, toolKey);
|
|
1580
|
-
}, [workspace, focus, toolKey, provider]);
|
|
1581
|
-
const everything = useMemo(() => resetAll(workspace), [workspace]);
|
|
1582
|
-
const put = (to, body) => to === workspace
|
|
1583
|
-
? undefined
|
|
1584
|
-
: () => {
|
|
1585
|
-
update(() => to);
|
|
1586
|
-
const dismiss = toast({
|
|
1587
|
-
body,
|
|
1588
|
-
endContent: (_jsx(Button, { label: "Undo", variant: "ghost", size: "sm", onClick: () => {
|
|
1589
|
-
update(() => workspace);
|
|
1590
|
-
dismiss();
|
|
1591
|
-
} })),
|
|
1592
|
-
});
|
|
1593
|
-
};
|
|
1594
|
-
return {
|
|
1595
|
-
what,
|
|
1596
|
-
run: put(next, `Reset the ${what} to how it started.`),
|
|
1597
|
-
all: save ? save.discard : put(everything, 'Reset every agent and tool to how it started.'),
|
|
1598
|
-
inProject: save !== null,
|
|
1599
|
-
};
|
|
1600
|
-
}
|
|
1601
1577
|
/** Opens the project's files again in place of every unsaved edit. Undo brings the edits back. */
|
|
1602
1578
|
function useDiscardAll({ store, filesChanged }) {
|
|
1603
1579
|
const toast = useToast();
|
|
@@ -1621,47 +1597,13 @@ function useDiscardAll({ store, filesChanged }) {
|
|
|
1621
1597
|
});
|
|
1622
1598
|
}, [store, filesChanged, toast]);
|
|
1623
1599
|
}
|
|
1624
|
-
/** The editor column's header: its title, the next issue,
|
|
1625
|
-
function EditorToolbar({ heading, compile, selected, view, onIssue,
|
|
1626
|
-
return (_jsx(Section, { variant: "transparent", padding: 3, dividers: ['bottom'], children: _jsxs(HStack, { gap: 1, vAlign: "center", children: [_jsx(SheetButton, { label: "Sections", icon: IconMenu2, sheet: "tree", setSheet: setSheet }), _jsx(StackItem, { size: "fill", children: heading && (_jsx("span", { title: heading, children: _jsx(Heading, { level: 4, maxLines: 1, children: heading }) })) }), _jsx(StackItem, { size: "static", children: _jsxs(HStack, { gap: 1, vAlign: "center", children: [_jsx(IssueToken, { compile: compile, selected: selected, onIssue: onIssue }), project] }) }), _jsx(
|
|
1627
|
-
label: 'Reset',
|
|
1628
|
-
variant: 'ghost',
|
|
1629
|
-
isIconOnly: true,
|
|
1630
|
-
icon: _jsx(Icon, { icon: IconRotateClockwise, size: "sm" }),
|
|
1631
|
-
tooltip: reset.run || reset.all
|
|
1632
|
-
? `Reset this ${reset.what}, or everything`
|
|
1633
|
-
: 'Everything is as it started',
|
|
1634
|
-
isDisabled: !reset.run && !reset.all,
|
|
1635
|
-
}, menuWidth: "15rem", hasChevron: false, placement: "below", alignment: "end", items: [
|
|
1636
|
-
{
|
|
1637
|
-
id: 'this',
|
|
1638
|
-
label: `Reset this ${reset.what}`,
|
|
1639
|
-
description: oneLine('Back to how it started. Undo brings your changes back.'),
|
|
1640
|
-
icon: _jsx(Icon, { icon: IconRotateClockwise, size: "sm" }),
|
|
1641
|
-
isDisabled: !reset.run,
|
|
1642
|
-
onClick: reset.run,
|
|
1643
|
-
},
|
|
1644
|
-
{
|
|
1645
|
-
id: 'all',
|
|
1646
|
-
label: reset.inProject ? 'Discard all edits' : 'Reset everything',
|
|
1647
|
-
description: oneLine(reset.inProject
|
|
1648
|
-
? 'Every unsaved edit goes, and the studio opens your files as they are.'
|
|
1649
|
-
: 'Every agent and tool, back to how it started.'),
|
|
1650
|
-
icon: _jsx(Icon, { icon: IconRestore, size: "sm" }),
|
|
1651
|
-
isDisabled: !reset.all,
|
|
1652
|
-
onClick: reset.all,
|
|
1653
|
-
},
|
|
1654
|
-
] }), _jsx(ViewToggleButton, { view: view }), _jsx(SheetButton, { label: "Preview", icon: IconPlayerPlay, sheet: "preview", setSheet: setSheet })] }) }));
|
|
1600
|
+
/** The editor column's header: its title, the next issue, what to start from, Get code, and the view toggle. */
|
|
1601
|
+
function EditorToolbar({ heading, compile, selected, view, onIssue, project, getCode, setSheet, }) {
|
|
1602
|
+
return (_jsx(Section, { variant: "transparent", padding: 3, dividers: ['bottom'], children: _jsxs(HStack, { gap: 1, vAlign: "center", children: [_jsx(SheetButton, { label: "Sections", icon: IconMenu2, sheet: "tree", setSheet: setSheet }), _jsx(StackItem, { size: "fill", children: heading && (_jsx("span", { title: heading, children: _jsx(Heading, { level: 4, maxLines: 1, children: heading }) })) }), _jsx(StackItem, { size: "static", children: _jsxs(HStack, { gap: 1, vAlign: "center", children: [_jsx(IssueToken, { compile: compile, selected: selected, onIssue: onIssue }), project] }) }), getCode, _jsx(ViewToggleButton, { view: view }), _jsx(SheetButton, { label: "Preview", icon: IconPlayerPlay, sheet: "preview", setSheet: setSheet })] }) }));
|
|
1655
1603
|
}
|
|
1656
|
-
/**
|
|
1657
|
-
function
|
|
1658
|
-
|
|
1659
|
-
if (hasSettings)
|
|
1660
|
-
return null;
|
|
1661
|
-
return (_jsx(IconButton, { label: "Keys", variant: "ghost", icon: _jsx(Icon, { icon: IconKey, size: "sm" }), "aria-pressed": view.keysOpen, tooltip: "Add your own model keys to run this agent with", onClick: () => {
|
|
1662
|
-
view.setKeysOpen((open) => !open);
|
|
1663
|
-
view.setEditorView('editor');
|
|
1664
|
-
} }));
|
|
1604
|
+
/** Opens the Keys page, above the providers list, in the same form as Add provider. */
|
|
1605
|
+
function ManageKeysButton({ keys }) {
|
|
1606
|
+
return (_jsx(Button, { label: "Manage keys", variant: "ghost", size: "sm", width: "100%", icon: _jsx(Icon, { icon: IconKey, size: "sm" }), style: { height: 'calc(var(--size-element-sm) - 4px)' }, "aria-pressed": keys.open, onClick: keys.toggle }));
|
|
1665
1607
|
}
|
|
1666
1608
|
/** The Keys panel, in the editor's column. */
|
|
1667
1609
|
function KeysBody({ connection, state, }) {
|
|
@@ -1716,7 +1658,7 @@ function useOpenKeys(setKeysOpen) {
|
|
|
1716
1658
|
function SettingsPane({ state, connection, project, setConversation, }) {
|
|
1717
1659
|
const { kernelVersion } = useStudioHost();
|
|
1718
1660
|
const { store } = state;
|
|
1719
|
-
const
|
|
1661
|
+
const save = useProjectSave();
|
|
1720
1662
|
const toast = useToast();
|
|
1721
1663
|
const quiet = useSyncExternalStore(store.subscribe, store.isQuiet, store.isQuiet);
|
|
1722
1664
|
const shared = useMemo(() => ({
|
|
@@ -1730,7 +1672,7 @@ function SettingsPane({ state, connection, project, setConversation, }) {
|
|
|
1730
1672
|
setConversation((count) => count + 1);
|
|
1731
1673
|
toast({ body: "Cleared this tab's chats." });
|
|
1732
1674
|
}, [setConversation, toast]);
|
|
1733
|
-
return (_jsx(Suspense, { fallback: null, children: _jsx(StudioSettings, { project: project, version: kernelVersion, watch: state.watchFiles, shared: shared, actions: { clearChats, discard:
|
|
1675
|
+
return (_jsx(Suspense, { fallback: null, children: _jsx(StudioSettings, { project: project, version: kernelVersion, watch: state.watchFiles, shared: shared, actions: { clearChats, discard: save?.discard }, keys: _jsx(KeySlots, { connection: connection, state: state }) }) }));
|
|
1734
1676
|
}
|
|
1735
1677
|
/** The open node's editor, keyed by node so each one opens at its top. */
|
|
1736
1678
|
function EditorBody({ state, connection, selected, editing, issues, listBadges, editorRef, open, }) {
|
|
@@ -1760,30 +1702,26 @@ function ClearHistoryButton({ run }) {
|
|
|
1760
1702
|
}
|
|
1761
1703
|
/** The types whose conversation a trace can hold, and so open and replay. */
|
|
1762
1704
|
const NO_CONVERSATION = new Set(['decision', 'host', 'embedding', 'live']);
|
|
1763
|
-
/**
|
|
1764
|
-
function
|
|
1765
|
-
const
|
|
1766
|
-
|
|
1767
|
-
const stamp = new Date().toISOString().replaceAll(':', '-').slice(0, 19);
|
|
1768
|
-
download(`${name}-${stamp}.trace.json`, new Blob([JSON.stringify(records, null, 2)], { type: 'application/json' }));
|
|
1769
|
-
} }));
|
|
1770
|
-
}
|
|
1771
|
-
/** Import trace, for a type whose conversation a trace holds, and Download trace, outside an imported one. */
|
|
1772
|
-
function TraceFileButtons({ payload, tracing, feed, profileId, }) {
|
|
1773
|
-
if (payload == null)
|
|
1705
|
+
/** Replay exchange, for a type whose conversation a trace holds. */
|
|
1706
|
+
function ReplayExchangeButton({ payload, tracing, }) {
|
|
1707
|
+
const phone = usePhone();
|
|
1708
|
+
if (payload == null || NO_CONVERSATION.has(payload.profile.type))
|
|
1774
1709
|
return null;
|
|
1775
|
-
|
|
1776
|
-
|
|
1710
|
+
return (_jsx(Button, { label: tracing.open ? 'Close replay' : 'Replay exchange', isIconOnly: phone, variant: tracing.open ? 'secondary' : 'ghost', tooltip: tracing.open
|
|
1711
|
+
? 'Back to the live conversation'
|
|
1712
|
+
: 'Open a recorded conversation (a .trace.json file) to see what happened, then replay its messages through this agent as it is now and compare', icon: _jsx(Icon, { icon: IconFileImport, size: "sm" }), "aria-pressed": tracing.open, onClick: tracing.toggle }));
|
|
1777
1713
|
}
|
|
1778
1714
|
/** The preview's header: back to the editor, the agent to chat with, history, trace, and Export. */
|
|
1779
|
-
function PreviewHeader({ store, chatAgents, chatWith, compile, run, connection, trace, testing, tracing,
|
|
1780
|
-
const testLabel = testing.open ? 'Close guardrail test' : 'Test guardrails';
|
|
1715
|
+
function PreviewHeader({ store, chatAgents, chatWith, compile, run, connection, trace, testing, tracing, setSheet, }) {
|
|
1781
1716
|
const traceLabel = trace.open ? 'Hide trace' : 'View trace';
|
|
1717
|
+
const phone = usePhone();
|
|
1782
1718
|
const { compiled } = compile;
|
|
1783
|
-
return (_jsx(Section, { variant: "transparent", paddingInline: 3, paddingBlock: 0, children: _jsxs(HStack, { gap: 2, vAlign: "center", children: [_jsx(SheetButton, { label: "Back to the editor", icon: IconArrowLeft, sheet: null, setSheet: setSheet }), _jsx(StackItem, { size: "fill", children: _jsx(ChatPicker, { agents: chatAgents, chatWith: chatWith, onChange: store.chatWith }) }), _jsx(StackItem, { size: "static", children: _jsxs(HStack, { gap: 1, vAlign: "center", children: [_jsx(ActingAsButton, {}), compile.payload && run.isUsed && !testing
|
|
1719
|
+
return (_jsx(Section, { variant: "transparent", paddingInline: 3, paddingBlock: 0, children: _jsxs(HStack, { gap: 2, vAlign: "center", children: [_jsx(SheetButton, { label: "Back to the editor", icon: IconArrowLeft, sheet: null, setSheet: setSheet }), _jsx(StackItem, { size: "fill", children: _jsx(ChatPicker, { agents: chatAgents, chatWith: chatWith, onChange: store.chatWith }) }), _jsx(StackItem, { size: "static", children: _jsxs(HStack, { gap: 1, vAlign: "center", children: [_jsx(ActingAsButton, {}), compile.payload && run.isUsed && !testing && !tracing.open && (_jsx(ClearHistoryButton, { run: run })), !testing && _jsx(ReplayExchangeButton, { payload: compile.payload, tracing: tracing }), (compile.traced || tracing.open) && !testing ? (_jsx(Button, { label: traceLabel, isIconOnly: phone, variant: trace.open ? 'secondary' : 'ghost', tooltip: trace.open
|
|
1720
|
+
? 'Close the trace'
|
|
1721
|
+
: 'See how each reply was made, step by step: every model and tool call, guardrail check and gate, with its timing', icon: _jsx(Icon, { icon: IconActivity, size: "sm" }), "aria-pressed": trace.open, onClick: trace.toggle })) : null, _jsx(OpenInNewTabButton, { compiled: compiled.ok ? compiled : undefined, chattedId: compile.chattedId, blocked: compile.blocked, connection: {
|
|
1784
1722
|
connectionMode: connection.mode,
|
|
1785
1723
|
localBaseUrl: connection.local.baseUrl,
|
|
1786
|
-
} })
|
|
1724
|
+
} })] }) })] }) }));
|
|
1787
1725
|
}
|
|
1788
1726
|
/**
|
|
1789
1727
|
* Why the agent to chat with does not run: the errors in its config, or the workspace's when its
|
|
@@ -1899,10 +1837,11 @@ function useSendAfterResume(chatRef, runKey) {
|
|
|
1899
1837
|
* The compiled agent to chat with, under its header; while none compiles, why. It sits out renders
|
|
1900
1838
|
* that change nothing it shows: typing in the editor, opening a node, Keys.
|
|
1901
1839
|
*/
|
|
1902
|
-
const PreviewPane = memo(function PreviewPane({ store, chatAgents, chatWith, compile, run, connection, chatRef,
|
|
1840
|
+
const PreviewPane = memo(function PreviewPane({ store, chatAgents, chatWith, compile, run, connection, chatRef, setSheet, onIssue, testing, }) {
|
|
1903
1841
|
const [traceOpen, setTraceOpen] = useState(false);
|
|
1904
|
-
const [
|
|
1905
|
-
|
|
1842
|
+
const [imported, setTracing] = useState(false);
|
|
1843
|
+
// why: The guardrail test covers an imported trace too; closing the test shows it again.
|
|
1844
|
+
const tracing = imported && !testing;
|
|
1906
1845
|
const feedOf = useRunFeeds(run.generation);
|
|
1907
1846
|
const profileId = chatAgents.find((agent) => agent.value === chatWith)?.label;
|
|
1908
1847
|
const sendNext = useSendAfterResume(chatRef, run.runKey);
|
|
@@ -1917,18 +1856,12 @@ const PreviewPane = memo(function PreviewPane({ store, chatAgents, chatWith, com
|
|
|
1917
1856
|
toggle: () => {
|
|
1918
1857
|
setTraceOpen((open) => !open);
|
|
1919
1858
|
},
|
|
1920
|
-
}, testing: {
|
|
1921
|
-
open: testing,
|
|
1922
|
-
toggle: () => {
|
|
1923
|
-
setTesting((open) => !open);
|
|
1924
|
-
setTracing(false);
|
|
1925
|
-
},
|
|
1926
|
-
}, tracing: {
|
|
1859
|
+
}, testing: testing, tracing: {
|
|
1927
1860
|
open: tracing,
|
|
1928
1861
|
toggle: () => {
|
|
1929
1862
|
setTracing((open) => !open);
|
|
1930
1863
|
},
|
|
1931
|
-
},
|
|
1864
|
+
}, setSheet: setSheet }), _jsx(StackItem, { size: "fill", children: _jsx(PreviewBody, { compile: compile, run: run, connection: connection, chatRef: chatRef, chatWith: chatWith, profileId: profileId, traceOpen: traceOpen, testing: testing, tracing: traceView, feedOf: feedOf, onIssue: onIssue }) })] }) }));
|
|
1932
1865
|
});
|
|
1933
1866
|
/** Tells the host what is on screen as it changes, and that it closed. */
|
|
1934
1867
|
function useScreenReport(onReport, { agent, type, issues, section }) {
|
|
@@ -2046,8 +1979,20 @@ function Studio({ opened }) {
|
|
|
2046
1979
|
const closeMap = useCallback(() => {
|
|
2047
1980
|
setMapOpen(false);
|
|
2048
1981
|
}, [setMapOpen]);
|
|
2049
|
-
|
|
2050
|
-
|
|
1982
|
+
const [testing, setTesting] = useState(false);
|
|
1983
|
+
const guardrailTest = useMemo(() => compile.payload
|
|
1984
|
+
? {
|
|
1985
|
+
open: testing,
|
|
1986
|
+
toggle: () => {
|
|
1987
|
+
// why: On a phone the test shows in the preview sheet, so opening it goes there.
|
|
1988
|
+
if (!testing)
|
|
1989
|
+
setSheet('preview');
|
|
1990
|
+
setTesting(!testing);
|
|
1991
|
+
},
|
|
1992
|
+
}
|
|
1993
|
+
: null, [compile.payload, testing, setSheet]);
|
|
1994
|
+
return (_jsx(ProjectContext.Provider, { value: project, children: _jsx(ActingAsContext.Provider, { value: actingAs, children: _jsx(ConfirmWrite, { value: asking.confirm, children: _jsxs(ProjectAsks, { value: state.asks ?? NO_ASKS, children: [asking.dialog, _jsx(SharedChange, { store: state.store }), _jsx(SaveScope, { project: project, state: state, blocked: Boolean(compile.blocked), children: _jsx(QuestionLink, { value: questionLink, children: _jsx(GuardrailTest.Provider, { value: guardrailTest, children: _jsx(Layout, { ref: frame.layoutCallbackRef, className: frameClass(sheet, view.mapOpen, view.settingsOpen), padding: 0, start: _jsx(SidePanel, { frame: frame, tree: tree, draft: draft, onAddAgent: addAgentFrom, setSheet: setSheet, settings: view.settingsOpen &&
|
|
1995
|
+
project && (_jsx(SettingsPane, { state: state, connection: connection, project: project, setConversation: run.setConversation })), map: view.mapOpen && (_jsx(Suspense, { fallback: null, children: _jsx(StudioMap, { workspace: state.workspace, shared: state.shared, selected: view.keysOpen ? '' : selected, onOpen: view.open, onClose: closeMap }) })), children: _jsx(EditorColumn, { heading: view.keysOpen ? 'Keys' : headingOf(tree.list, editing, title), state: state, connection: connection, view: view, compile: compile, selected: selected, editing: editing, frame: frame, issueReveal: issueReveal, goToIssue: goToIssue, copy: copy, setSheet: setSheet }) }), content: _jsx(PreviewPane, { store: store, chatAgents: chatAgents, chatWith: state.workspace.chatWith, compile: compile, run: run, connection: connection, chatRef: chatRef, setSheet: setSheet, onIssue: goToIssue, testing: testing && Boolean(compile.payload) }) }) }) }) })] }) }) }) }));
|
|
2051
1996
|
}
|
|
2052
1997
|
/** Save for the screen under it, when the studio is open on a project. */
|
|
2053
1998
|
function SaveScope({ project, state, blocked, children, }) {
|
|
@@ -2074,10 +2019,34 @@ function useAddAgent(store, update, open) {
|
|
|
2074
2019
|
/** What the tree reads of the page; opening a node closes the phone's sheet, and Keys selects nothing. */
|
|
2075
2020
|
function useWorkspaceTree(state, view, selected, setSheet) {
|
|
2076
2021
|
const { workspace, focus, update, setDraft } = state;
|
|
2077
|
-
const { open } = view;
|
|
2078
|
-
const selectedId =
|
|
2022
|
+
const { open, keysOpen, setKeysOpen, setEditorView } = view;
|
|
2023
|
+
const selectedId = keysOpen ? '' : selected;
|
|
2079
2024
|
const { list, setList } = useListShown(selectedId);
|
|
2080
2025
|
const ownsProviders = useProject() !== null;
|
|
2026
|
+
const { hasSettings } = useStudioHost();
|
|
2027
|
+
const toast = useToast();
|
|
2028
|
+
const reset = useCallback((to, body) => to === workspace
|
|
2029
|
+
? undefined
|
|
2030
|
+
: () => {
|
|
2031
|
+
update(() => to);
|
|
2032
|
+
const dismiss = toast({
|
|
2033
|
+
body,
|
|
2034
|
+
endContent: (_jsx(Button, { label: "Undo", variant: "ghost", size: "sm", onClick: () => {
|
|
2035
|
+
update(() => workspace);
|
|
2036
|
+
dismiss();
|
|
2037
|
+
} })),
|
|
2038
|
+
});
|
|
2039
|
+
}, [workspace, update, toast]);
|
|
2040
|
+
const keys = useMemo(() => hasSettings
|
|
2041
|
+
? null
|
|
2042
|
+
: {
|
|
2043
|
+
open: keysOpen,
|
|
2044
|
+
toggle: () => {
|
|
2045
|
+
setKeysOpen((was) => !was);
|
|
2046
|
+
setEditorView('editor');
|
|
2047
|
+
setSheet(null);
|
|
2048
|
+
},
|
|
2049
|
+
}, [hasSettings, keysOpen, setKeysOpen, setEditorView, setSheet]);
|
|
2081
2050
|
return useMemo(() => ({
|
|
2082
2051
|
workspace,
|
|
2083
2052
|
focus,
|
|
@@ -2091,7 +2060,22 @@ function useWorkspaceTree(state, view, selected, setSheet) {
|
|
|
2091
2060
|
},
|
|
2092
2061
|
update,
|
|
2093
2062
|
setDraft,
|
|
2094
|
-
|
|
2063
|
+
reset,
|
|
2064
|
+
keys,
|
|
2065
|
+
}), [
|
|
2066
|
+
workspace,
|
|
2067
|
+
focus,
|
|
2068
|
+
selectedId,
|
|
2069
|
+
list,
|
|
2070
|
+
ownsProviders,
|
|
2071
|
+
setList,
|
|
2072
|
+
open,
|
|
2073
|
+
setSheet,
|
|
2074
|
+
update,
|
|
2075
|
+
setDraft,
|
|
2076
|
+
reset,
|
|
2077
|
+
keys,
|
|
2078
|
+
]);
|
|
2095
2079
|
}
|
|
2096
2080
|
/** The resizable side panel: the tree beside the editor column (`children`), and its handle. */
|
|
2097
2081
|
function SidePanel({ frame, tree, draft, onAddAgent, setSheet, settings, map, children, }) {
|
|
@@ -2120,13 +2104,12 @@ function OriginBanner({ origin, partly, onOpen, }) {
|
|
|
2120
2104
|
return (_jsx(Section, { variant: "transparent", padding: 3, children: _jsx(Banner, { status: "info", title: originLabel(origin), description: `${effect} ${way}`, endContent: place && _jsx(Button, { label: "Open", variant: "ghost", size: "sm", onClick: onOpen }) }) }));
|
|
2121
2105
|
}
|
|
2122
2106
|
/** The editor column: its toolbar over the Keys panel, the open node's editor, or its code. */
|
|
2123
|
-
function EditorColumn({ heading, state, connection, view, compile, selected, editing, frame, issueReveal, goToIssue, setSheet, }) {
|
|
2107
|
+
function EditorColumn({ heading, state, connection, view, compile, selected, editing, frame, issueReveal, goToIssue, copy, setSheet, }) {
|
|
2124
2108
|
const { editorRef, revealed } = issueReveal;
|
|
2125
2109
|
// Keys stands over the open node, so it doesn't say what sets it.
|
|
2126
2110
|
const over = view.keysOpen;
|
|
2127
2111
|
const shared = over ? undefined : sharedAt(state.shared, selected);
|
|
2128
2112
|
const { shown, leavePage } = useLeftIssues(compile.editorIssues, selected, revealed);
|
|
2129
|
-
const reset = useReset(state);
|
|
2130
2113
|
const project = useProject();
|
|
2131
2114
|
const openOrigin = useOpenOrigin(project);
|
|
2132
2115
|
const rowOrigins = useMemo(() => {
|
|
@@ -2135,7 +2118,7 @@ function EditorColumn({ heading, state, connection, view, compile, selected, edi
|
|
|
2135
2118
|
}, [state.origins, state.workspace, selected, openOrigin]);
|
|
2136
2119
|
const whole = rowOrigins?.scope.section;
|
|
2137
2120
|
const said = over ? undefined : (whole ?? rowOrigins?.scope.partly);
|
|
2138
|
-
return (_jsxs(VStack, { height: "100%", children: [_jsx(EditorToolbar, { heading: heading, compile: compile, selected: selected, view: view, onIssue: goToIssue,
|
|
2121
|
+
return (_jsxs(VStack, { height: "100%", children: [_jsx(EditorToolbar, { heading: heading, compile: compile, selected: selected, view: view, onIssue: goToIssue, getCode: _jsx(ExportMenu, { store: state.store, compile: compile, copy: copy }), project: project && (_jsxs(_Fragment, { children: [state.conflict && (_jsx(Token, { label: conflictsLabel(state.conflict.merge.conflicts.length), color: "orange", description: "Choose between studio edits and source code", onClick: () => {
|
|
2139
2122
|
state.showConflict(true);
|
|
2140
2123
|
} })), _jsx(ProjectTokens, { project: project, unloaded: state.files?.unloaded })] })), setSheet: setSheet }), shared && (_jsx(SharedLine, { entry: shared, workspace: state.workspace, selected: selected, onOpen: project
|
|
2141
2124
|
? () => {
|
package/ui/studio.css
CHANGED
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/*
|
|
24
|
-
* A studio tree row's add
|
|
25
|
-
* so the tree reads as names at rest. Without hover (touch),
|
|
24
|
+
* A studio tree row's buttons (reset, add, remove) show on hover or keyboard focus, fading in,
|
|
25
|
+
* so the tree reads as names at rest. Without hover (touch), they always show.
|
|
26
26
|
*/
|
|
27
27
|
.studio-tree-row {
|
|
28
28
|
position: relative;
|
|
@@ -51,26 +51,37 @@
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/*
|
|
54
|
-
* The
|
|
55
|
-
* label fades out beneath
|
|
54
|
+
* The buttons sit over the label's end rather than beside it, so they never widen the row; the
|
|
55
|
+
* label fades out beneath them instead of being pushed along. Each button past the first fades a
|
|
56
|
+
* button's width further in.
|
|
56
57
|
*/
|
|
58
|
+
.studio-tree-row {
|
|
59
|
+
--studio-tree-fade: 0px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.studio-tree-row:has(.studio-tree-action > :nth-child(2)) {
|
|
63
|
+
--studio-tree-fade: var(--spacing-8);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.studio-tree-row:has(.studio-tree-action > :nth-child(3)) {
|
|
67
|
+
--studio-tree-fade: calc(var(--spacing-8) * 2);
|
|
68
|
+
}
|
|
69
|
+
|
|
57
70
|
@media (hover: none) {
|
|
58
71
|
.studio-tree-row > button:not([data-tree-toggle]) {
|
|
59
|
-
mask-image: linear-gradient(
|
|
72
|
+
mask-image: linear-gradient(
|
|
73
|
+
to left,
|
|
74
|
+
transparent calc(var(--spacing-6) + var(--studio-tree-fade)),
|
|
75
|
+
#000 calc(var(--spacing-10) + var(--studio-tree-fade))
|
|
76
|
+
);
|
|
60
77
|
}
|
|
61
78
|
}
|
|
62
79
|
|
|
63
80
|
.studio-tree-row:is(:hover, :has(:focus-visible)) > button:not([data-tree-toggle]) {
|
|
64
|
-
mask-image: linear-gradient(to left, transparent var(--spacing-6), #000 var(--spacing-10));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/* A row with two buttons fades a button's width further in. */
|
|
68
|
-
.studio-tree-row:has(.studio-tree-action > :nth-child(2)):is(:hover, :has(:focus-visible))
|
|
69
|
-
> button:not([data-tree-toggle]) {
|
|
70
81
|
mask-image: linear-gradient(
|
|
71
82
|
to left,
|
|
72
|
-
transparent calc(var(--spacing-6) + var(--
|
|
73
|
-
#000 calc(var(--spacing-10) + var(--
|
|
83
|
+
transparent calc(var(--spacing-6) + var(--studio-tree-fade)),
|
|
84
|
+
#000 calc(var(--spacing-10) + var(--studio-tree-fade))
|
|
74
85
|
);
|
|
75
86
|
}
|
|
76
87
|
|
|
@@ -221,6 +232,11 @@ button.inspector-origin:hover {
|
|
|
221
232
|
}
|
|
222
233
|
|
|
223
234
|
@media (width >= 768px) {
|
|
235
|
+
/* The page is already inset, so the preview's panes meet its end and bottom as the editor does. */
|
|
236
|
+
.studio-preview {
|
|
237
|
+
--theorem-pane-edge: 0px;
|
|
238
|
+
}
|
|
239
|
+
|
|
224
240
|
.studio-frame:is(.studio-map-open, .studio-settings-open) .studio-side {
|
|
225
241
|
width: 100%;
|
|
226
242
|
max-width: none;
|
package/ui/use-phone.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react';
|
|
2
|
+
/** The width below which the studio's text buttons keep only their icon. */
|
|
3
|
+
const PHONE = '(width < 768px)';
|
|
4
|
+
/** Whether the page is phone-wide. */
|
|
5
|
+
export function usePhone() {
|
|
6
|
+
return useSyncExternalStore((change) => {
|
|
7
|
+
const query = globalThis.matchMedia(PHONE);
|
|
8
|
+
query.addEventListener('change', change);
|
|
9
|
+
return () => {
|
|
10
|
+
query.removeEventListener('change', change);
|
|
11
|
+
};
|
|
12
|
+
}, () => globalThis.matchMedia(PHONE).matches, () => false);
|
|
13
|
+
}
|
package/workspace.d.ts
CHANGED
|
@@ -112,6 +112,13 @@ export declare function startedHere(workspace: StudioWorkspace): StudioWorkspace
|
|
|
112
112
|
* Nothing else changes. An agent already at its start returns the same workspace.
|
|
113
113
|
*/
|
|
114
114
|
export declare function resetAgent(workspace: StudioWorkspace, key: string): StudioWorkspace;
|
|
115
|
+
/**
|
|
116
|
+
* Puts one row of an agent's tree back to its start: a section, or one model under Models. The
|
|
117
|
+
* agent's other sections keep their edits. The agent's own row puts the whole agent back, as
|
|
118
|
+
* `resetAgent` does, and Tools brings back a tool that left the library as it does too. A row
|
|
119
|
+
* already at its start returns the same workspace.
|
|
120
|
+
*/
|
|
121
|
+
export declare function resetAgentNode(workspace: StudioWorkspace, key: string, ref: StudioNodeRef): StudioWorkspace;
|
|
115
122
|
/**
|
|
116
123
|
* Puts every agent, every library tool, and every provider back to its start, in one step. A tool that left the
|
|
117
124
|
* library comes back when an agent started out allowing it. An agent or a tool added here stays: it
|