Feat: add storybook setup (#10721)
* feat(storybook): add Storybook setup and Button component stories - Add Storybook configuration files (.storybook/main.ts, preview.ts, css.d.ts) - Add Button component stories with interaction testing - Add Storybook dependencies and scripts to package.json - Support dark mode in stories via decorator - Include play functions for automated interaction testing * ci(storybook): add GitHub Pages deployment workflow - Add automated deployment workflow for Storybook - Deploy on push to main when Storybook files change - Support manual trigger via workflow_dispatch - Use official GitHub Actions for Pages deployment * fix(storybook): align Storybook versions and fix TypeScript issues - Update all @storybook/* packages from ^8.4.7 to ^8.6.14 to match main storybook version - Fix CSS type declarations in css.d.ts (Record<string, string> instead of Record<string, never>) - Remove @ts-ignore comments from preview.ts (CSS imports now properly typed) - Fix darkMode type issue in button.stories.tsx with proper ArgTypes type assertion * feat(storybook): replace Button stories with Dropdown component stories - Remove Button stories (shadcn already has documentation) - Add comprehensive Dropdown component stories showcasing: - Default, with value, combobox mode - With metadata, disabled, loading states - Many options, searchable filtering - Dark mode support - Add store initialization decorator for Storybook - Include interaction testing with play functions * feat(storybook): add SettingsPage stories and remove dropdown stories - Remove dropdown component stories - Add SettingsPage stories with router, store, and dark mode decorators - Include variants: default, with general settings, and dark mode * feat(storybook): fix SettingsPage stories to show full page and add play functions - Fix router setup to properly render SettingsPage with nested routes - Add Routes configuration for all settings sub-pages (General, MCP Servers, Global Variables, Shortcuts, Messages) - Add play functions to test page visibility and navigation - Add stories for different routes: Default, WithGeneralSettings, NavigateToShortcuts, NavigateToGlobalVariables, DarkMode * revert(storybook): restore SettingsPage stories to original working version - Revert to simpler router setup without Routes configuration - Remove play functions and complex routing - Restore original three stories: Default, WithGeneralSettings, DarkMode * feat(storybook): add stories for ShortcutsPage and GlobalVariablesPage with tables - Add ShortcutsPage stories showing the shortcuts table - Add GlobalVariablesPage stories showing the global variables table - Include store setup for shortcuts data - Add play functions to verify table visibility - Support dark mode for both pages * fix(storybook): add QueryClientProvider to GlobalVariablesPage stories - Add QueryClientProvider decorator to support React Query hooks - Configure QueryClient with retry disabled for Storybook * fix(storybook): remove WithGeneralSettings story to fix nested router error - Remove WithGeneralSettings story that was causing nested Router error - Keep Default and DarkMode stories only * feat(storybook): enhance SettingsPage stories with multiple states and logic variations - Add stories showcasing different store configurations (autoLogin, hasStore) - Demonstrate conditional General settings visibility logic - Add interactive sidebar navigation story - Show full configuration with all features - Include play functions to verify state-based behavior - Showcase how page adapts to different user/auth states * fix(storybook): initialize Zustand stores synchronously in SettingsPage stories - Set store state before component render instead of in useEffect - Ensures stores are accessible when SettingsPage component mounts - Fixes state access errors in Storybook * feat(storybook): add story to verify store state accessibility - Add VerifyStoreState story that demonstrates accessing Zustand store state - Verify store values match expected configuration - Show that state is accessible from play functions * fix(storybook): remove router from SettingsPage stories to fix errors - Remove MemoryRouter decorator that was causing errors - Keep store setup and dark mode decorators - Stories now work without router dependency * fix(storybook): add router back to SettingsPage stories for useNavigate support - Add MemoryRouter back to support useCustomNavigate hook in PageLayout - Router is needed for navigation hooks to work properly - Keep router at decorator level to avoid nested router errors * fix(storybook): fix router decorator order in SettingsPage stories - Move router decorator to be outermost (last in array) - Decorators run bottom-to-top, so router should wrap everything - Ensures useNavigate context is available to all components * feat(storybook): add PlaygroundPage story as example for complex page stories - Add PlaygroundPage story demonstrating how to create stories for complex pages - Include darkMode toggle control as example for interactive story controls - Set up decorators for query client, router, and theme switching - Hide publish elements (Theme buttons, Built with Langflow) in story view - Center chat title header in story view - Configure Storybook preview and CSS types This story serves as a reference for creating stories for full page components rather than simple UI components, which are already documented in shadcn docs. * chore(storybook): remove SettingsPage stories Keep only PlaygroundPage story as the example for complex page stories. * chore: restore pyproject.toml to match main branch * chore: restore pyproject.toml to match main branch * Revert "chore: restore pyproject.toml to match main branch" This reverts commit a2b75a4028f1f046bc84a8d7999d53a2cb720fc9. * chore: remove src/frontend/pyproject.toml as it doesn't exist in main * fix gitignore and add make commands * update package-json * chore(storybook): migrate from v8.6.14 to v10.1.0 - Update all Storybook packages to v10.1.0 - Replace @storybook/addon-essentials with @storybook/addon-docs - Remove @storybook/addon-interactions (moved to core) - Remove @storybook/blocks and @storybook/test (consolidated) - Fix import in PlaygroundPage.stories.tsx to use @storybook/react - Update tsconfig.json moduleResolution to 'bundler' for better compatibility - Add explicit types configuration for @storybook/react * fix: update package-lock.json to sync with package.json * fix: regenerate package-lock.json with all optional dependencies --------- Co-authored-by: Olfa Maslah <olfamaslah@Olfas-MacBook-Pro.local> Co-authored-by: Cristhian Zanforlin <criszl@MacBook-Pro-di-Cristhian.local> Co-authored-by: Olfa Maslah <olfamaslah@macbookpro.war.can.ibm.com>
This commit is contained in:
@@ -2,7 +2,8 @@
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:prettier/recommended"
|
||||
"plugin:prettier/recommended",
|
||||
"plugin:storybook/recommended"
|
||||
],
|
||||
"plugins": [
|
||||
"react",
|
||||
@@ -87,4 +88,4 @@
|
||||
"node/prefer-promises/dns": "error",
|
||||
"node/prefer-promises/fs": "error"
|
||||
}
|
||||
}
|
||||
}
|
||||
49
.github/workflows/deploy-storybook.yml
vendored
Normal file
49
.github/workflows/deploy-storybook.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: Deploy Storybook to GitHub Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'src/frontend/**/*.stories.*'
|
||||
- 'src/frontend/.storybook/**'
|
||||
- 'src/frontend/package.json'
|
||||
- '.github/workflows/deploy-storybook.yml'
|
||||
workflow_dispatch: # Allow manual trigger
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy Storybook
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: src/frontend/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: cd src/frontend && npm ci
|
||||
|
||||
- name: Build Storybook
|
||||
run: cd src/frontend && npm run build-storybook
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v4
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: src/frontend/storybook-static
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
FRONTEND_DIR = src/frontend
|
||||
NPM = npm
|
||||
|
||||
.PHONY: install_frontend install_frontendci install_frontendc frontend_deps_check build_frontend run_frontend frontend frontendc format_frontend tests_frontend test_frontend test_frontend_watch test_frontend_coverage test_frontend_verbose test_frontend_ci test_frontend_clean test_frontend_file test_frontend_pattern test_frontend_snapshots test_frontend_config test_frontend_bail test_frontend_silent test_frontend_coverage_open help_frontend
|
||||
.PHONY: install_frontend install_frontendci install_frontendc frontend_deps_check build_frontend run_frontend frontend frontendc format_frontend tests_frontend test_frontend test_frontend_watch test_frontend_coverage test_frontend_verbose test_frontend_ci test_frontend_clean test_frontend_file test_frontend_pattern test_frontend_snapshots test_frontend_config test_frontend_bail test_frontend_silent test_frontend_coverage_open help_frontend storybook storybook_build storybook_network
|
||||
|
||||
######################
|
||||
# FRONTEND DEPENDENCIES
|
||||
@@ -166,6 +166,23 @@ test_frontend_coverage_open: test_frontend_coverage ## run tests with coverage a
|
||||
echo "Coverage report generated at: $(FRONTEND_DIR)/coverage/lcov-report/index.html"; \
|
||||
fi
|
||||
|
||||
######################
|
||||
# STORYBOOK
|
||||
######################
|
||||
|
||||
storybook: frontend_deps_check ## run Storybook development server and open in browser
|
||||
@echo "Starting Storybook development server on http://localhost:6006..."
|
||||
@cd $(FRONTEND_DIR) && $(NPM) run storybook
|
||||
|
||||
storybook_build: frontend_deps_check ## build static Storybook
|
||||
@echo "Building static Storybook..."
|
||||
@cd $(FRONTEND_DIR) && $(NPM) run build-storybook
|
||||
@echo "Storybook built to $(FRONTEND_DIR)/storybook-static"
|
||||
|
||||
storybook_network: frontend_deps_check ## run Storybook accessible on network (0.0.0.0:6006)
|
||||
@echo "Starting Storybook development server accessible on network..."
|
||||
@cd $(FRONTEND_DIR) && $(NPM) run storybook:network
|
||||
|
||||
######################
|
||||
# FRONTEND HELP
|
||||
######################
|
||||
@@ -212,5 +229,10 @@ help_frontend: ## show frontend help
|
||||
@echo " $(GREEN)make test_frontend_snapshots$(NC) - Update Jest snapshots"
|
||||
@echo " $(GREEN)make test_frontend_config$(NC) - Show Jest configuration"
|
||||
@echo ''
|
||||
@echo "$(GREEN)Storybook:$(NC)"
|
||||
@echo " $(GREEN)make storybook$(NC) - Run Storybook dev server and open in browser"
|
||||
@echo " $(GREEN)make storybook_build$(NC) - Build static Storybook"
|
||||
@echo " $(GREEN)make storybook_network$(NC) - Run Storybook accessible on network"
|
||||
@echo ''
|
||||
@echo "$(GREEN)═══════════════════════════════════════════════════════════════════$(NC)"
|
||||
@echo ''
|
||||
@@ -135,7 +135,7 @@ dependencies = [
|
||||
"fastparquet>=2024.11.0,<2025.0.0",
|
||||
"traceloop-sdk>=0.43.1,<1.0.0",
|
||||
"vlmrun[all]>=0.2.0",
|
||||
"cuga==0.1.11",
|
||||
"cuga==0.1.10",
|
||||
"agent-lifecycle-toolkit~=0.4.1",
|
||||
"astrapy>=2.1.0,<3.0.0",
|
||||
"aioboto3>=15.2.0,<16.0.0"
|
||||
|
||||
4
src/frontend/.gitignore
vendored
4
src/frontend/.gitignore
vendored
@@ -11,6 +11,10 @@
|
||||
# production
|
||||
/build
|
||||
|
||||
# storybook
|
||||
/storybook-static
|
||||
*storybook.log
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
|
||||
5
src/frontend/.storybook/css.d.ts
vendored
Normal file
5
src/frontend/.storybook/css.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
// Type declarations for CSS imports in Storybook
|
||||
declare module "*.css" {
|
||||
const content: Record<string, string>;
|
||||
export default content;
|
||||
}
|
||||
41
src/frontend/.storybook/main.ts
Normal file
41
src/frontend/.storybook/main.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
// This file has been automatically migrated to valid ESM format by Storybook.
|
||||
import { fileURLToPath } from "node:url";
|
||||
import type { StorybookConfig } from "@storybook/react-vite";
|
||||
import path, { dirname } from "path";
|
||||
import { mergeConfig } from "vite";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
||||
addons: ["@storybook/addon-links", "@storybook/addon-docs"],
|
||||
framework: {
|
||||
name: "@storybook/react-vite",
|
||||
options: {},
|
||||
},
|
||||
async viteFinal(config) {
|
||||
return mergeConfig(config, {
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "../src"),
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
typescript: {
|
||||
check: false,
|
||||
reactDocgen: "react-docgen-typescript",
|
||||
reactDocgenTypescriptOptions: {
|
||||
shouldExtractLiteralValuesFromEnum: true,
|
||||
propFilter: (prop) => {
|
||||
if (prop.parent) {
|
||||
return !prop.parent.fileName.includes("node_modules");
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
87
src/frontend/.storybook/preview.ts
Normal file
87
src/frontend/.storybook/preview.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import type { Preview } from "@storybook/react-vite";
|
||||
import * as React from "react";
|
||||
import { useEffect } from "react";
|
||||
// Import all CSS files to match the app's styling
|
||||
import "../src/style/classes.css";
|
||||
import "../src/style/index.css";
|
||||
import "../src/App.css";
|
||||
import "../src/style/applies.css";
|
||||
import { TooltipProvider } from "../src/components/ui/tooltip";
|
||||
import { useDarkStore } from "../src/stores/darkStore";
|
||||
|
||||
// Global decorator to provide TooltipProvider for all stories
|
||||
const withTooltipProvider = (Story: React.ComponentType) => {
|
||||
const TooltipWrapper = (): React.ReactElement => {
|
||||
const storyElement = React.createElement(Story);
|
||||
return React.createElement(TooltipProvider, {
|
||||
skipDelayDuration: 0,
|
||||
children: storyElement,
|
||||
});
|
||||
};
|
||||
return React.createElement(TooltipWrapper);
|
||||
};
|
||||
|
||||
// Global decorator to handle dark mode for all stories
|
||||
const withTheme = (
|
||||
Story: React.ComponentType,
|
||||
context: {
|
||||
args?: { darkMode?: boolean };
|
||||
initialArgs?: { darkMode?: boolean };
|
||||
parameters?: { darkMode?: boolean };
|
||||
globals?: { theme?: string };
|
||||
},
|
||||
) => {
|
||||
const ThemeWrapper = (): React.ReactElement => {
|
||||
// Check for dark mode in args, parameters, or globals
|
||||
const dark =
|
||||
context.args?.darkMode === true ||
|
||||
context.initialArgs?.darkMode === true ||
|
||||
context.parameters?.darkMode === true ||
|
||||
context.globals?.theme === "dark";
|
||||
|
||||
useEffect(() => {
|
||||
const bodyElement = document.getElementById("body") || document.body;
|
||||
const htmlElement = document.documentElement;
|
||||
|
||||
if (dark) {
|
||||
bodyElement.classList.add("dark");
|
||||
htmlElement.classList.add("dark");
|
||||
useDarkStore.setState({ dark: true });
|
||||
} else {
|
||||
bodyElement.classList.remove("dark");
|
||||
htmlElement.classList.remove("dark");
|
||||
useDarkStore.setState({ dark: false });
|
||||
}
|
||||
}, [dark]);
|
||||
|
||||
// Sync immediately on render
|
||||
const bodyElement = document.getElementById("body") || document.body;
|
||||
const htmlElement = document.documentElement;
|
||||
if (dark) {
|
||||
bodyElement.classList.add("dark");
|
||||
htmlElement.classList.add("dark");
|
||||
} else {
|
||||
bodyElement.classList.remove("dark");
|
||||
htmlElement.classList.remove("dark");
|
||||
}
|
||||
|
||||
return React.createElement(Story);
|
||||
};
|
||||
|
||||
return React.createElement(ThemeWrapper);
|
||||
};
|
||||
|
||||
const preview: Preview = {
|
||||
decorators: [withTooltipProvider, withTheme],
|
||||
parameters: {
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default preview;
|
||||
6889
src/frontend/package-lock.json
generated
6889
src/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -100,10 +100,16 @@
|
||||
"serve": "vite preview",
|
||||
"format": "npx @biomejs/biome format --write",
|
||||
"lint": "npx @biomejs/biome lint",
|
||||
"lint:changed": "bash scripts/lint-changed.sh",
|
||||
"lint:types": "npx @biomejs/biome lint --diagnostic-level=error",
|
||||
"lint:types:staged": "npx @biomejs/biome lint --staged --diagnostic-level=error",
|
||||
"lint:types:changed": "bash scripts/lint-changed.sh",
|
||||
"check-format": "npx @biomejs/biome check",
|
||||
"type-check": "tsc --noEmit --pretty --project tsconfig.json && vite"
|
||||
"type-check": "tsc --noEmit --pretty --project tsconfig.json && vite",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"storybook:network": "storybook dev -p 6006 --host 0.0.0.0",
|
||||
"build-storybook": "storybook build",
|
||||
"chromatic": "chromatic --project-token=${CHROMATIC_PROJECT_TOKEN}"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
@@ -121,6 +127,10 @@
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.1.1",
|
||||
"@jest/types": "^30.0.1",
|
||||
"@storybook/addon-docs": "^10.1.0",
|
||||
"@storybook/addon-links": "^10.1.0",
|
||||
"@storybook/react": "^10.1.0",
|
||||
"@storybook/react-vite": "^10.1.0",
|
||||
"@modelcontextprotocol/server-everything": "^0.6.2",
|
||||
"@playwright/test": "^1.56.0",
|
||||
"@swc/cli": "^0.5.2",
|
||||
@@ -142,6 +152,7 @@
|
||||
"jest-environment-jsdom": "^30.0.2",
|
||||
"jest-junit": "^16.0.0",
|
||||
"postcss": "^8.4.38",
|
||||
"storybook": "^10.1.0",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"tailwindcss-dotted-background": "^1.1.0",
|
||||
"ts-jest": "^29.4.0",
|
||||
|
||||
336
src/frontend/src/pages/Playground/PlaygroundPage.stories.tsx
Normal file
336
src/frontend/src/pages/Playground/PlaygroundPage.stories.tsx
Normal file
@@ -0,0 +1,336 @@
|
||||
import type { Meta, StoryContext, StoryObj } from "@storybook/react";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
import { v5 as uuidv5 } from "uuid";
|
||||
import { useDarkStore } from "@/stores/darkStore";
|
||||
import useFlowStore from "@/stores/flowStore";
|
||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||
import { useMessagesStore } from "@/stores/messagesStore";
|
||||
import { useUtilityStore } from "@/stores/utilityStore";
|
||||
import type { AllNodeType, FlowType } from "@/types/flow";
|
||||
import type { FlowStoreType } from "@/types/zustand/flow";
|
||||
import PlaygroundPage from "./index";
|
||||
|
||||
type StoryArgs = { darkMode?: boolean; flowId?: string };
|
||||
|
||||
const withDarkMode = (
|
||||
Story: React.ComponentType,
|
||||
context: StoryContext<StoryArgs>,
|
||||
) => {
|
||||
const DarkModeWrapper = () => {
|
||||
const dark = context.args?.darkMode ?? false;
|
||||
const prevDark = useRef<boolean | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
// Only update if dark mode actually changed
|
||||
if (prevDark.current === dark) return;
|
||||
prevDark.current = dark;
|
||||
|
||||
const body = document.getElementById("body") || document.body;
|
||||
const currentDark = useDarkStore.getState().dark;
|
||||
|
||||
if (dark && !currentDark) {
|
||||
body.classList.add("dark");
|
||||
useDarkStore.setState({ dark: true });
|
||||
} else if (!dark && currentDark) {
|
||||
body.classList.remove("dark");
|
||||
useDarkStore.setState({ dark: false });
|
||||
}
|
||||
}, [dark]);
|
||||
return <Story />;
|
||||
};
|
||||
return <DarkModeWrapper />;
|
||||
};
|
||||
|
||||
const withRouter = (
|
||||
Story: React.ComponentType,
|
||||
context: StoryContext<StoryArgs>,
|
||||
) => {
|
||||
const flowId = context.args?.flowId || "test-flow-id";
|
||||
return (
|
||||
<MemoryRouter initialEntries={[`/playground/${flowId}`]}>
|
||||
<Story />
|
||||
</MemoryRouter>
|
||||
);
|
||||
};
|
||||
|
||||
const withQueryClient = (
|
||||
Story: React.ComponentType,
|
||||
context: StoryContext<StoryArgs>,
|
||||
) => {
|
||||
const QueryClientWrapper = () => {
|
||||
const flowId = context.args?.flowId || "test-flow-id";
|
||||
const queryClient = useMemo(
|
||||
() =>
|
||||
new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: { retry: false, staleTime: Infinity },
|
||||
mutations: { retry: false },
|
||||
},
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const clientId = "test-client-id";
|
||||
const computedFlowId = uuidv5(`${clientId}_${flowId}`, uuidv5.DNS);
|
||||
|
||||
queryClient.setQueryData(
|
||||
[
|
||||
"useGetMessagesQuery",
|
||||
{
|
||||
mode: "union",
|
||||
id: computedFlowId,
|
||||
params: { session_id: computedFlowId },
|
||||
},
|
||||
],
|
||||
{ data: { messages: [] } },
|
||||
);
|
||||
queryClient.setQueryData(
|
||||
["useGetSessionsFromFlowQuery", { id: flowId }],
|
||||
{
|
||||
data: { sessions: [computedFlowId] },
|
||||
},
|
||||
);
|
||||
}, [flowId, queryClient]);
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Story />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
return <QueryClientWrapper />;
|
||||
};
|
||||
|
||||
const withHidePublishElements = (Story: React.ComponentType) => {
|
||||
const HideElementsWrapper = () => {
|
||||
useEffect(() => {
|
||||
const style = document.createElement("style");
|
||||
style.id = "hide-publish-elements";
|
||||
style.textContent = `
|
||||
.absolute.bottom-2.left-0.flex.w-full.flex-col.gap-8 { display: none !important; }
|
||||
.absolute.bottom-6.left-4.hidden.transition-all.md\\:block { display: none !important; }
|
||||
div.flex.h-full.w-full.flex-col.justify-between.px-4.pb-4.pt-2 > div.flex.h-10.shrink-0.items-center.text-base.font-semibold > div.truncate.text-center.font-semibold {
|
||||
position: absolute !important; left: 50% !important; transform: translateX(-50%) !important;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
return () => {
|
||||
const existingStyle = document.getElementById("hide-publish-elements");
|
||||
if (existingStyle) document.head.removeChild(existingStyle);
|
||||
};
|
||||
}, []);
|
||||
return <Story />;
|
||||
};
|
||||
return <HideElementsWrapper />;
|
||||
};
|
||||
|
||||
const withPlaygroundPageSetup = (
|
||||
Story: React.ComponentType,
|
||||
context: StoryContext<StoryArgs>,
|
||||
) => {
|
||||
const PlaygroundPageWrapper = () => {
|
||||
const flowId = context.args?.flowId || "test-flow-id";
|
||||
const setupDone = useRef<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
// Prevent re-setup if already done for this flowId
|
||||
if (setupDone.current === flowId) return;
|
||||
setupDone.current = flowId;
|
||||
const mockNodes: AllNodeType[] = [
|
||||
{
|
||||
id: "chat-input-1",
|
||||
type: "genericNode",
|
||||
position: { x: 0, y: 0 },
|
||||
data: {
|
||||
type: "ChatInput",
|
||||
id: "chat-input-1",
|
||||
node: {
|
||||
display_name: "Chat Input",
|
||||
description: "",
|
||||
documentation: "",
|
||||
tool_mode: false,
|
||||
frozen: false,
|
||||
template: {
|
||||
input_value: {
|
||||
type: "str",
|
||||
required: false,
|
||||
placeholder: "",
|
||||
list: false,
|
||||
show: true,
|
||||
readonly: false,
|
||||
value: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "chat-output-1",
|
||||
type: "genericNode",
|
||||
position: { x: 0, y: 0 },
|
||||
data: {
|
||||
type: "ChatOutput",
|
||||
id: "chat-output-1",
|
||||
node: {
|
||||
display_name: "Chat Output",
|
||||
description: "",
|
||||
documentation: "",
|
||||
tool_mode: false,
|
||||
frozen: false,
|
||||
template: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const mockFlow: FlowType = {
|
||||
id: flowId,
|
||||
name: "Playground",
|
||||
description: "A test chat flow",
|
||||
data: {
|
||||
nodes: mockNodes,
|
||||
edges: [],
|
||||
viewport: { x: 0, y: 0, zoom: 1 },
|
||||
},
|
||||
is_component: false,
|
||||
access_type: "PUBLIC",
|
||||
updated_at: new Date().toISOString(),
|
||||
};
|
||||
|
||||
useFlowsManagerStore.setState({
|
||||
currentFlowId: flowId,
|
||||
currentFlow: mockFlow,
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
const mockBuildFlow: FlowStoreType["buildFlow"] = async ({
|
||||
input_value,
|
||||
session,
|
||||
files,
|
||||
startNodeId,
|
||||
stopNodeId,
|
||||
silent,
|
||||
stream,
|
||||
eventDelivery,
|
||||
}) => {
|
||||
if (!input_value?.trim()) return;
|
||||
const realFlowId =
|
||||
useFlowsManagerStore.getState().currentFlowId || flowId;
|
||||
const clientId =
|
||||
useUtilityStore.getState().clientId || "test-client-id";
|
||||
const playgroundPage = useFlowStore.getState().playgroundPage;
|
||||
const computedFlowId = playgroundPage
|
||||
? uuidv5(`${clientId}_${realFlowId}`, uuidv5.DNS)
|
||||
: realFlowId;
|
||||
const sessionId = session || computedFlowId;
|
||||
|
||||
useFlowStore.setState({ isBuilding: true });
|
||||
useMessagesStore.getState().addMessage({
|
||||
id: `msg-user-${Date.now()}`,
|
||||
text: input_value,
|
||||
sender: "User",
|
||||
sender_name: "User",
|
||||
flow_id: computedFlowId,
|
||||
session_id: sessionId,
|
||||
timestamp: new Date().toISOString(),
|
||||
files: files || [],
|
||||
edit: false,
|
||||
background_color: "",
|
||||
text_color: "",
|
||||
content_blocks: [],
|
||||
properties: {},
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
useMessagesStore.getState().addMessage({
|
||||
id: `msg-bot-${Date.now()}`,
|
||||
text: `This is a simulated response to: "${input_value}"`,
|
||||
sender: "Machine",
|
||||
sender_name: "Assistant",
|
||||
flow_id: computedFlowId,
|
||||
session_id: sessionId,
|
||||
timestamp: new Date().toISOString(),
|
||||
files: [],
|
||||
edit: false,
|
||||
background_color: "",
|
||||
text_color: "",
|
||||
content_blocks: [],
|
||||
properties: {},
|
||||
});
|
||||
useFlowStore.setState({ isBuilding: false });
|
||||
}, 500);
|
||||
};
|
||||
|
||||
useFlowStore.getState().setNodes(mockNodes);
|
||||
useFlowStore.setState({
|
||||
currentFlow: mockFlow,
|
||||
edges: [],
|
||||
isBuilding: false,
|
||||
playgroundPage: true,
|
||||
buildFlow: mockBuildFlow,
|
||||
});
|
||||
|
||||
useMessagesStore.setState({ messages: [], displayLoadingMessage: false });
|
||||
useUtilityStore.setState({
|
||||
clientId: "test-client-id",
|
||||
chatValueStore: "",
|
||||
currentSessionId: flowId,
|
||||
});
|
||||
}, [flowId]);
|
||||
|
||||
return <Story />;
|
||||
};
|
||||
return <PlaygroundPageWrapper />;
|
||||
};
|
||||
|
||||
const meta: Meta<typeof PlaygroundPage> = {
|
||||
title: "Pages/PlaygroundPage",
|
||||
component: PlaygroundPage,
|
||||
decorators: [
|
||||
withQueryClient,
|
||||
(Story) => (
|
||||
<div style={{ height: "100vh", width: "100vw" }}>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
withHidePublishElements,
|
||||
withPlaygroundPageSetup,
|
||||
withRouter,
|
||||
withDarkMode,
|
||||
],
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
"The PlaygroundPage component displays a chat interface for testing flows.",
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Playground: Story = {
|
||||
argTypes: {
|
||||
darkMode: {
|
||||
control: "boolean",
|
||||
description: "Toggle dark mode",
|
||||
table: { category: "Theme" },
|
||||
},
|
||||
flowId: {
|
||||
control: "text",
|
||||
description: "Flow ID for the playground",
|
||||
table: { category: "Configuration" },
|
||||
},
|
||||
},
|
||||
args: {
|
||||
flowId: "test-flow-id",
|
||||
darkMode: false,
|
||||
} as StoryArgs & Record<string, unknown>,
|
||||
};
|
||||
@@ -10,7 +10,8 @@
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "bundler",
|
||||
"types": ["@storybook/react"],
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
|
||||
Reference in New Issue
Block a user