diff --git a/engine/baml-lib/baml-core/src/ir/repr.rs b/engine/baml-lib/baml-core/src/ir/repr.rs index ea80d810f..070fa4b39 100644 --- a/engine/baml-lib/baml-core/src/ir/repr.rs +++ b/engine/baml-lib/baml-core/src/ir/repr.rs @@ -10,8 +10,7 @@ use internal_baml_parser_database::{ ClassWalker, ClientWalker, ConfigurationWalker, EnumValueWalker, EnumWalker, FieldWalker, FunctionWalker, TemplateStringWalker, VariantWalker, }, - ParserDatabase, PromptAst, RetryPolicyStrategy, ToStringAttributes, - WithStaticRenames, + ParserDatabase, PromptAst, RetryPolicyStrategy, ToStringAttributes, WithStaticRenames, }; use internal_baml_schema_ast::ast::{self, FieldArity, WithName, WithSpan}; @@ -1102,6 +1101,14 @@ pub struct TestCase { } impl WithRepr for ConfigurationWalker<'_> { + fn attributes(&self, _db: &ParserDatabase) -> NodeAttributes { + NodeAttributes { + meta: IndexMap::new(), + overrides: IndexMap::new(), + span: Some(self.span().clone()), + } + } + fn repr(&self, db: &ParserDatabase) -> Result { Ok(TestCase { name: self.name().to_string(), diff --git a/engine/baml-lib/baml-core/src/ir/walker.rs b/engine/baml-lib/baml-core/src/ir/walker.rs index c769c878f..3b0bd80a9 100644 --- a/engine/baml-lib/baml-core/src/ir/walker.rs +++ b/engine/baml-lib/baml-core/src/ir/walker.rs @@ -265,6 +265,10 @@ impl<'a> Walker<'a, (&'a Function, &'a TestCase)> { &self.item.1.elem } + pub fn span(&self) -> Option<&crate::Span> { + self.item.1.attributes.span.as_ref() + } + pub fn test_case_params( &self, env_values: &HashMap, diff --git a/engine/baml-runtime/src/lib.rs b/engine/baml-runtime/src/lib.rs index b01844bbf..fa59378fd 100644 --- a/engine/baml-runtime/src/lib.rs +++ b/engine/baml-runtime/src/lib.rs @@ -50,6 +50,9 @@ pub use internal_baml_jinja::{ChatMessagePart, RenderedPrompt}; #[cfg(feature = "internal")] pub use runtime_interface::InternalRuntimeInterface; +#[cfg(feature = "internal")] +pub use internal_baml_core as internal_core; + #[cfg(not(feature = "internal"))] pub(crate) use internal_baml_jinja::{ChatMessagePart, RenderedPrompt}; #[cfg(not(feature = "internal"))] diff --git a/engine/baml-schema-wasm/src/runtime_wasm/mod.rs b/engine/baml-schema-wasm/src/runtime_wasm/mod.rs index 95c8b8f25..a84141794 100644 --- a/engine/baml-schema-wasm/src/runtime_wasm/mod.rs +++ b/engine/baml-schema-wasm/src/runtime_wasm/mod.rs @@ -257,6 +257,8 @@ pub struct WasmFunction { pub test_cases: Vec, #[wasm_bindgen(readonly)] pub test_snippet: String, + #[wasm_bindgen(readonly)] + pub signature: String, } #[wasm_bindgen(getter_with_clone, inspectable)] @@ -268,6 +270,31 @@ pub struct WasmSpan { pub start: usize, #[wasm_bindgen(readonly)] pub end: usize, + #[wasm_bindgen(readonly)] + pub start_line: usize, +} + +impl From<&baml_runtime::internal_core::internal_baml_diagnostics::Span> for WasmSpan { + fn from(span: &baml_runtime::internal_core::internal_baml_diagnostics::Span) -> Self { + let (start, end) = span.line_and_column(); + WasmSpan { + file_path: span.file.path().to_string(), + start: span.start, + end: span.end, + start_line: start.0, + } + } +} + +impl Default for WasmSpan { + fn default() -> Self { + WasmSpan { + file_path: "".to_string(), + start: 0, + end: 0, + start_line: 0, + } + } } #[wasm_bindgen(getter_with_clone, inspectable)] @@ -279,6 +306,8 @@ pub struct WasmTestCase { pub inputs: Vec, #[wasm_bindgen(readonly)] pub error: Option, + #[wasm_bindgen(readonly)] + pub span: WasmSpan, } #[wasm_bindgen(getter_with_clone, inspectable)] @@ -743,21 +772,27 @@ impl WasmRuntime { ); let wasm_span = match f.span() { - Some(span) => WasmSpan { - file_path: span.file.path().to_string(), - start: span.start, - end: span.end, - }, - None => WasmSpan { - file_path: "".to_string(), - start: 0, - end: 0, - }, + Some(span) => span.into(), + None => WasmSpan::default(), }; WasmFunction { name: f.name().to_string(), span: wasm_span, + signature: { + let inputs = f + .inputs() + .right() + .map(|func_params| { + func_params + .iter() + .map(|(k, t)| format!("{}: {}", k, t)) + .collect::>() + .join(", ") + }) + .unwrap_or_default(); + format!("({}) -> {}", inputs, f.output().to_string()) + }, test_snippet: snippet, test_cases: f .walk_tests() @@ -812,10 +847,16 @@ impl WasmRuntime { } }); + let wasm_span = match tc.span() { + Some(span) => span.into(), + None => WasmSpan::default(), + }; + WasmTestCase { name: tc.test_case().name.clone(), inputs: params, error, + span: wasm_span, } }) .collect(), diff --git a/typescript/fiddle-frontend/app/[project_id]/_components/ProjectView.tsx b/typescript/fiddle-frontend/app/[project_id]/_components/ProjectView.tsx index 766b47774..6aec18ccd 100644 --- a/typescript/fiddle-frontend/app/[project_id]/_components/ProjectView.tsx +++ b/typescript/fiddle-frontend/app/[project_id]/_components/ProjectView.tsx @@ -12,7 +12,6 @@ import { CustomErrorBoundary, EventListener, FunctionPanel, - FunctionSelector, //useSelections, } from '@baml/playground-common' import { updateFileAtom } from '@baml/playground-common/baml_wasm_web/EventListener' @@ -42,10 +41,11 @@ import { CodeMirrorEditor } from './CodeMirrorEditor' import { GithubStars } from './GithubStars' import { InitialTour, PostTestRunTour } from './Tour' -import SettingsDialog, { ShowSettingsButton, showSettingsAtom } from '@baml/playground-common/shared/SettingsDialog' +import SettingsDialog, { ShowSettingsButton } from '@baml/playground-common/shared/SettingsDialog' import FileViewer from './Tree/FileViewer' import { AppStateProvider } from '@baml/playground-common/shared/AppStateContext' // Import the AppStateProvider +import { ViewSelector } from '@baml/playground-common/shared/Selectors' const ProjectViewImpl = ({ project }: { project: BAMLProject }) => { const setEditorFiles = useSetAtom(updateFileAtom) @@ -220,10 +220,7 @@ const ProjectViewImpl = ({ project }: { project: BAMLProject }) => { }} className='flex flex-row h-full overflow-clip' > - +
{
- + {!isMobile && (
@@ -337,13 +334,10 @@ const PlaygroundView = () => {
- -
- +
+
+
{/* */} diff --git a/typescript/fiddle-frontend/app/[project_id]/_components/Tree/FileViewer.tsx b/typescript/fiddle-frontend/app/[project_id]/_components/Tree/FileViewer.tsx index 78529e39d..96cb6f698 100644 --- a/typescript/fiddle-frontend/app/[project_id]/_components/Tree/FileViewer.tsx +++ b/typescript/fiddle-frontend/app/[project_id]/_components/Tree/FileViewer.tsx @@ -137,8 +137,8 @@ const FileViewer = () => { ) return ( -
-
{createFileFolder}
+
+
{createFileFolder}
{/* { value={term} onChange={(e) => setTerm(e.target.value)} /> */} -
+
) => { style={style} ref={dragHandle} > -
node.isInternal && node.toggle()}> +
node.isInternal && node.toggle()}> {node.isLeaf ? ( <> - + {renderIcon(node.id)} ) : ( <> - {node.isOpen ? : } + + {node.isOpen ? : } + {/* */} @@ -137,7 +139,12 @@ const Node = ({ node, style, dragHandle, tree }: NodeRendererProps) => { autoFocus /> ) : ( - + {node.data.name} )} @@ -146,7 +153,7 @@ const Node = ({ node, style, dragHandle, tree }: NodeRendererProps) => { {node.id !== 'baml_src' && (
-
+
)) )} diff --git a/typescript/playground-common/src/shared/AppStateContext.tsx b/typescript/playground-common/src/shared/AppStateContext.tsx index f63c2a8ec..bc8270328 100644 --- a/typescript/playground-common/src/shared/AppStateContext.tsx +++ b/typescript/playground-common/src/shared/AppStateContext.tsx @@ -7,6 +7,8 @@ interface AppStateContextProps { setShowWhitespace: React.Dispatch> showCurlRequest: boolean setShowCurl: React.Dispatch> + showTestResults: boolean + setShowTestResults: React.Dispatch> } const AppStateContext = createContext(undefined) @@ -15,6 +17,7 @@ export const AppStateProvider: React.FC<{ children: React.ReactNode }> = ({ chil const [showTokens, setShowTokens] = useState(false) const [showWhitespace, setShowWhitespace] = useState(false) const [showCurlRequest, setShowCurl] = useState(false) + const [showTestResults, setShowTestResults] = useState(true) return ( = ({ chil setShowWhitespace, showCurlRequest, setShowCurl, + showTestResults, + setShowTestResults, }} > {children} diff --git a/typescript/playground-common/src/shared/CheckboxHeader.tsx b/typescript/playground-common/src/shared/CheckboxHeader.tsx index 4a16e000b..28bafae78 100644 --- a/typescript/playground-common/src/shared/CheckboxHeader.tsx +++ b/typescript/playground-common/src/shared/CheckboxHeader.tsx @@ -1,5 +1,9 @@ import { useAppState } from './AppStateContext' import { Checkbox } from '../components/ui/checkbox' +import { useAtomValue } from 'jotai' +import { selectedTestCaseAtom } from '../baml_wasm_web/EventListener' +import Link from './Link' +import { ShowSettingsButton } from './SettingsDialog' const PromptCheckbox = ({ children, @@ -19,7 +23,16 @@ const PromptCheckbox = ({ } export const CheckboxHeader = () => { - const { showTokens, setShowTokens, showWhitespace, setShowWhitespace, showCurlRequest, setShowCurl } = useAppState() + const { + showTokens, + setShowTokens, + showWhitespace, + setShowWhitespace, + showCurlRequest, + setShowCurl, + showTestResults, + setShowTestResults, + } = useAppState() return (
@@ -32,6 +45,10 @@ export const CheckboxHeader = () => { Raw cURL + + Test Results + +
) } diff --git a/typescript/playground-common/src/shared/FunctionPanel.tsx b/typescript/playground-common/src/shared/FunctionPanel.tsx index f671befed..02b37a340 100644 --- a/typescript/playground-common/src/shared/FunctionPanel.tsx +++ b/typescript/playground-common/src/shared/FunctionPanel.tsx @@ -107,7 +107,7 @@ const PromptPreview: React.FC = () => { ) if (part.is_image()) return ( - + ) @@ -132,6 +132,7 @@ const PromptPreview: React.FC = () => { const FunctionPanel: React.FC = () => { const selectedFunc = useAtomValue(selectedFunctionAtom) + const { showTestResults } = useAppState() if (!selectedFunc) { const bamlFunctionSnippet = ` @@ -187,13 +188,17 @@ enum Topic { {/* */}
- - - - + {showTestResults && ( + <> + + + + + + )}
diff --git a/typescript/playground-common/src/shared/ImplPanel.tsx b/typescript/playground-common/src/shared/ImplPanel.tsx index 2bbc8c08b..eccac2a51 100644 --- a/typescript/playground-common/src/shared/ImplPanel.tsx +++ b/typescript/playground-common/src/shared/ImplPanel.tsx @@ -98,7 +98,6 @@ const CodeLine: React.FC<{ const isTokenized = Array.isArray(line[0]) if (Array.isArray(line)) { - console.log('line', line) return (
{lineNumberSpan} diff --git a/typescript/playground-common/src/shared/Link.tsx b/typescript/playground-common/src/shared/Link.tsx index 0ca46ee72..9581d7a8c 100644 --- a/typescript/playground-common/src/shared/Link.tsx +++ b/typescript/playground-common/src/shared/Link.tsx @@ -2,15 +2,18 @@ import type { StringSpan } from '@baml/common' import { VSCodeLink } from '@vscode/webview-ui-toolkit/react' import { cn } from '../lib/utils' import { vscode } from '../utils/vscode' +import { File } from 'lucide-react' const Link: React.FC<{ item: StringSpan; display?: string; className?: string }> = ({ item, display, className }) => ( { vscode.postMessage({ command: 'jumpToFile', data: item }) }} > - {display ?? item.value} +
+ {display ?? item.value} +
) diff --git a/typescript/playground-common/src/shared/Selectors.tsx b/typescript/playground-common/src/shared/Selectors.tsx index c63a331f6..447ae8101 100644 --- a/typescript/playground-common/src/shared/Selectors.tsx +++ b/typescript/playground-common/src/shared/Selectors.tsx @@ -1,16 +1,14 @@ -import type { SFunction } from '@baml/common' -import { VSCodeDropdown, VSCodeLink, VSCodeOption } from '@vscode/webview-ui-toolkit/react' import { useAtom, useAtomValue } from 'jotai' -import { Check, ChevronsUpDown } from 'lucide-react' +import { ChevronDown, ChevronRight, Compass } from 'lucide-react' import type React from 'react' -import { useContext, useState } from 'react' -import { availableFunctionsAtom, selectedFunctionAtom } from '../baml_wasm_web/EventListener' +import { useState } from 'react' +import { availableFunctionsAtom, selectedFunctionAtom, selectedTestCaseAtom } from '../baml_wasm_web/EventListener' import { Button } from '../components/ui/button' -import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '../components/ui/command' import { Popover, PopoverContent, PopoverTrigger } from '../components/ui/popover' import SearchBarWithSelector from '../lib/searchbar' - import Link from './Link' +import { Dialog, DialogContent, DialogTrigger } from '../components/ui/dialog' +import { Snippets } from './Snippets' const FunctionDropdown: React.FC = () => { const [open, setOpen] = useState(false) @@ -19,69 +17,218 @@ const FunctionDropdown: React.FC = () => { const functionName = selected?.name + if (functions.length === 0) { + return <>Create a function + } + return ( - - - - - - ({ - value: func.name, - label: func.test_cases.length > 0 ? `${func.name} (${func.test_cases.length} tests)` : undefined, - }))} - onChange={(value) => { - setSelected(value) - setOpen(false) - }} - /> - - +
+ + Function + {selected && } + + + +
+ {functionName ?? 'Select a function...'} + +
+
+ + ({ + value: func.name, + label: func.test_cases.length > 0 ? `${func.name} (${func.test_cases.length} tests)` : undefined, + content: ( +
+ {func.signature} +
+ ), + }))} + onChange={(value) => { + setSelected(value) + setOpen(false) + }} + /> +
+
+
) } -export const FunctionSelector: React.FC = () => { +const TestDropdown: React.FC = () => { + const [open, setOpen] = useState(false) + const tests = useAtomValue(selectedFunctionAtom)?.test_cases + const [selected, setSelected] = useAtom(selectedTestCaseAtom) + + if (tests === undefined) { + return null + } + + if (tests.length === 0) { + return <>Create a test + } + + if (!selected) { + return <>Select a test... + } + return ( -
-
- {/* */} +
+ + Test + {selected && } + + + +
+ {selected.name} + +
+
+ + ({ + value: test.name, + content: ( +
+ {test.inputs.map((i) => ( +
+ {i.name} + {i.value} +
+ ))} +
+ ), + }))} + onChange={(value) => { + setSelected(value) + setOpen(false) + }} + /> +
+
+
+ ) +} + +const JumpToFunction: React.FC = () => { + const selected = useAtomValue(selectedFunctionAtom) + + if (!selected) { + return null + } + + return ( + .baml'}:${selected.span.start_line + 1}`, + }} + className='text-xs text-muted-foreground decoration-0 py-0' + /> + ) +} + +const JumpToTestCase: React.FC = () => { + const selected = useAtomValue(selectedTestCaseAtom) + + if (!selected) { + return null + } + + return ( + .baml'}:${selected.span.start_line + 1}`, + }} + className='text-xs text-muted-foreground decoration-0' + /> + ) +} + +export const ViewSelector: React.FC = () => { + return ( +
+
- {/* Function - '} - onChange={(event) => - setSelection( - undefined, - (event as React.FormEvent).currentTarget.value, - undefined, - undefined, - undefined, - ) - } - > - {function_names.map((func) => ( - - {func} - - ))} - */} -
- {/* {func && ( -
- - {'('} - {') → '}{' '} - {func.output.arg_type === 'positional' && } +
+
- )} */} + +
+
+ + + + + + + + +
+ + // + // + // + // + // + // + // + // + // + // ) } + +// export const FunctionSelector: React.FC = () => { +// return ( +//
+//
+// {/* */} + +// +// {/* Function +// '} +// onChange={(event) => +// setSelection( +// undefined, +// (event as React.FormEvent).currentTarget.value, +// undefined, +// undefined, +// undefined, +// ) +// } +// > +// {function_names.map((func) => ( +// +// {func} +// +// ))} +// */} +//
+// {/* {func && ( +//
+// +// {'('} +// {') → '}{' '} +// {func.output.arg_type === 'positional' && } +//
+// )} */} +//
+// ) +// } diff --git a/typescript/playground-common/src/shared/SettingsDialog.tsx b/typescript/playground-common/src/shared/SettingsDialog.tsx index d6ca003be..4b3284ca4 100644 --- a/typescript/playground-common/src/shared/SettingsDialog.tsx +++ b/typescript/playground-common/src/shared/SettingsDialog.tsx @@ -170,10 +170,7 @@ const EnvvarInput: React.FC<{ envvar: EnvVar }> = ({ envvar }) => { ) } -export const ShowSettingsButton: React.FC<{ buttonClassName: string; iconClassName: string }> = ({ - buttonClassName, - iconClassName, -}) => { +export const ShowSettingsButton: React.FC<{ iconClassName: string }> = ({ iconClassName }) => { const setShowSettings = useSetAtom(showSettingsAtom) const requiredButUnset = useAtomValue(requiredButUnsetAtom) const requiredButUnsetCount = requiredButUnset.length @@ -183,10 +180,13 @@ export const ShowSettingsButton: React.FC<{ buttonClassName: string; iconClassNa } const button = ( - - - - - - - -
-
- +
+ +