├── data ├── runs.json ├── tools.ts └── samples.json ├── templates ├── .gitignore.template └── dspyground.config.ts.template ├── src ├── app │ ├── favicon.ico │ ├── prompts │ │ └── page.tsx │ ├── api │ │ ├── prompt │ │ │ └── route.ts │ │ ├── schema │ │ │ └── route.ts │ │ ├── models │ │ │ └── route.ts │ │ ├── metrics-prompt │ │ │ └── route.ts │ │ ├── factory-reset │ │ │ └── route.ts │ │ ├── preferences │ │ │ └── route.ts │ │ ├── runs │ │ │ └── route.ts │ │ ├── chat │ │ │ └── route.ts │ │ ├── transcribe-feedback │ │ │ └── route.ts │ │ ├── sample-groups │ │ │ └── route.ts │ │ └── samples │ │ │ └── route.ts │ ├── layout.tsx │ └── globals.css ├── lib │ ├── utils.ts │ ├── optimizer-types.ts │ ├── config-loader.ts │ └── metrics.ts ├── components │ ├── ui │ │ ├── skeleton.tsx │ │ ├── label.tsx │ │ ├── separator.tsx │ │ ├── collapsible.tsx │ │ ├── textarea.tsx │ │ ├── sonner.tsx │ │ ├── input.tsx │ │ ├── switch.tsx │ │ ├── avatar.tsx │ │ ├── hover-card.tsx │ │ ├── theme-toggle.tsx │ │ ├── badge.tsx │ │ ├── tabs.tsx │ │ ├── tooltip.tsx │ │ ├── button.tsx │ │ ├── alert-dialog.tsx │ │ ├── dialog.tsx │ │ ├── sheet.tsx │ │ ├── input-group.tsx │ │ ├── squares-background.tsx │ │ ├── optimize-live-chart.tsx │ │ ├── select.tsx │ │ ├── prompt-editor-dialog.tsx │ │ ├── dropdown-menu.tsx │ │ ├── metric-prompt-editor-dialog.tsx │ │ └── feedback-dialog.tsx │ ├── sidebar-trigger-button.tsx │ ├── ai-elements │ │ ├── response.tsx │ │ ├── message.tsx │ │ ├── conversation.tsx │ │ ├── code-block.tsx │ │ └── tool.tsx │ └── app-sidebar.tsx └── hooks │ └── use-mobile.ts ├── postcss.config.mjs ├── public ├── vercel.svg ├── window.svg ├── file.svg ├── globe.svg └── next.svg ├── .env.example ├── components.json ├── cli ├── index.ts ├── init.ts └── dev.ts ├── eslint.config.mjs ├── next.config.ts ├── tsconfig.cli.json ├── tsup.config.ts ├── .npmignore ├── tsconfig.json ├── scripts ├── clean-sensitive.mjs └── prepare-standalone.mjs ├── .gitignore └── package.json /data/runs.json: -------------------------------------------------------------------------------- 1 | { 2 | "runs": [] 3 | } -------------------------------------------------------------------------------- /templates/.gitignore.template: -------------------------------------------------------------------------------- 1 | # DSPyGround local data 2 | .dspyground/ 3 | 4 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Scale3-Labs/dspyground/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ className, ...props }: React.ComponentProps<"div">) { 4 | return ( 5 |
10 | ) 11 | } 12 | 13 | export { Skeleton } 14 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Required: AI Gateway API key for prompt optimization 2 | AI_GATEWAY_API_KEY=your_ai_gateway_api_key_here 3 | 4 | # Optional: Used only for enabling voice mode evals 5 | OPENAI_API_KEY=your_openai_api_key 6 | OPENAI_BASE_URL=set_this_if_you_are_using_whisper_from_another_provider 7 | 8 | # Optional: Next.js configuration 9 | NEXT_PUBLIC_APP_URL=http://localhost:3000 -------------------------------------------------------------------------------- /src/app/prompts/page.tsx: -------------------------------------------------------------------------------- 1 | export default function PromptsPage() { 2 | return ( 3 |
4 |
5 |

Prompts

6 |

Coming soon...

7 |
8 |
9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/sidebar-trigger-button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { SidebarTrigger, useSidebar } from "@/components/ui/sidebar"; 4 | 5 | export function SidebarTriggerButton() { 6 | const { open } = useSidebar(); 7 | 8 | // Only show the trigger when sidebar is closed 9 | if (open) return null; 10 | 11 | return ; 12 | } 13 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/app/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "iconLibrary": "lucide", 14 | "aliases": { 15 | "components": "@/components", 16 | "utils": "@/lib/utils", 17 | "ui": "@/components/ui", 18 | "lib": "@/lib", 19 | "hooks": "@/hooks" 20 | }, 21 | "registries": {} 22 | } 23 | -------------------------------------------------------------------------------- /cli/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { Command } from "commander"; 4 | import { devCommand } from "./dev.js"; 5 | import { initCommand } from "./init.js"; 6 | 7 | const program = new Command(); 8 | 9 | program 10 | .name("dspyground") 11 | .description("DSPyGround - Optimize and test your AI agents") 12 | .version("0.1.0"); 13 | 14 | program 15 | .command("init") 16 | .description("Initialize DSPyGround in the current directory") 17 | .action(initCommand); 18 | 19 | program 20 | .command("dev") 21 | .description("Start the DSPyGround development server") 22 | .action(devCommand); 23 | 24 | program.parse(); 25 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | { 15 | ignores: [ 16 | "node_modules/**", 17 | ".next/**", 18 | "out/**", 19 | "build/**", 20 | "next-env.d.ts", 21 | ], 22 | }, 23 | ]; 24 | 25 | export default eslintConfig; 26 | -------------------------------------------------------------------------------- /src/hooks/use-mobile.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | const MOBILE_BREAKPOINT = 768 4 | 5 | export function useIsMobile() { 6 | const [isMobile, setIsMobile] = React.useState(undefined) 7 | 8 | React.useEffect(() => { 9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) 10 | const onChange = () => { 11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 12 | } 13 | mql.addEventListener("change", onChange) 14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 15 | return () => mql.removeEventListener("change", onChange) 16 | }, []) 17 | 18 | return !!isMobile 19 | } 20 | -------------------------------------------------------------------------------- /src/components/ai-elements/response.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | import { type ComponentProps, memo } from "react"; 5 | import { Streamdown } from "streamdown"; 6 | 7 | type ResponseProps = ComponentProps; 8 | 9 | export const Response = memo( 10 | ({ className, ...props }: ResponseProps) => ( 11 | *:first-child]:mt-0 [&>*:last-child]:mb-0", 14 | className 15 | )} 16 | {...props} 17 | /> 18 | ), 19 | (prevProps, nextProps) => prevProps.children === nextProps.children 20 | ); 21 | 22 | Response.displayName = "Response"; 23 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | import path from "path"; 3 | 4 | const nextConfig: NextConfig = { 5 | output: "standalone", 6 | outputFileTracingRoot: path.join(__dirname, "../../"), 7 | eslint: { 8 | // Don't fail build on pre-existing lint errors 9 | ignoreDuringBuilds: true, 10 | }, 11 | typescript: { 12 | // Don't fail build on pre-existing type errors 13 | ignoreBuildErrors: true, 14 | }, 15 | webpack: (config, { isServer }) => { 16 | if (isServer) { 17 | // Allow loading modules from user's project 18 | config.externals = [...(config.externals || [])]; 19 | } 20 | return config; 21 | }, 22 | }; 23 | 24 | export default nextConfig; 25 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function Label({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | ) 22 | } 23 | 24 | export { Label } 25 | -------------------------------------------------------------------------------- /tsconfig.cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ES2020", 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "strict": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "noEmit": false, 13 | "outDir": "dist/cli", 14 | "declaration": true, 15 | "declarationMap": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "incremental": false 18 | }, 19 | "include": [ 20 | "cli/**/*" 21 | ], 22 | "exclude": [ 23 | "node_modules", 24 | "dist" 25 | ] 26 | } -------------------------------------------------------------------------------- /src/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SeparatorPrimitive from "@radix-ui/react-separator" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function Separator({ 9 | className, 10 | orientation = "horizontal", 11 | decorative = true, 12 | ...props 13 | }: React.ComponentProps) { 14 | return ( 15 | 25 | ) 26 | } 27 | 28 | export { Separator } 29 | -------------------------------------------------------------------------------- /src/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" 4 | 5 | function Collapsible({ 6 | ...props 7 | }: React.ComponentProps) { 8 | return 9 | } 10 | 11 | function CollapsibleTrigger({ 12 | ...props 13 | }: React.ComponentProps) { 14 | return ( 15 | 19 | ) 20 | } 21 | 22 | function CollapsibleContent({ 23 | ...props 24 | }: React.ComponentProps) { 25 | return ( 26 | 30 | ) 31 | } 32 | 33 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 34 | -------------------------------------------------------------------------------- /src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { 6 | return ( 7 |