// ArtifactSystem.stories.tsx import React, { useEffect } from 'react'; import type { Meta, StoryObj } from '@storybook/react'; import { ArtifactProvider, useArtifact } from './context/ArtifactContext'; import ArtifactHandler from './components/ArtifactHandler/ArtifactHandler'; import ArtifactDrawer from './components/ArtifactDrawer/ArtifactDrawer'; import { ArtifactAPIBridge } from './utils/ArtifactAPI'; import Chat from '../Chat/Chat'; import { Message, Memori, Tenant, } from '@memori.ai/memori-api-client/dist/types'; import { sanitizeText } from '../../helpers/sanitizer'; import { ArtifactData } from './types/artifact.types'; // Note: Window.MemoriArtifactAPI types are defined in MemoriWidget.tsx // Mock data for Chat component const mockMemori: Memori = { memoriID: 'test-memori-id', name: 'Test Memori', culture: 'en-US', coverURL: '', enableBoardOfExperts: false, } as Memori; const mockTenant: Tenant = { name: 'test-tenant', } as Tenant; // Mock functions for Chat component const mockPushMessage = (message: Message) => { console.log('Push message:', message); }; const mockSimulateUserPrompt = (text: string, translatedText?: string) => { console.log('Simulate user prompt:', text, translatedText); }; const mockOnChangeUserMessage = (userMessage: string) => { console.log('Change user message:', userMessage); }; const mockSendMessage = (msg: string, media?: any[]) => { console.log('Send message:', msg, media); }; const mockSetEnableFocusChatInput = (enableFocusChatInput: boolean) => { console.log('Set enable focus chat input:', enableFocusChatInput); }; const mockStopAudio = () => { console.log('Stop audio'); }; const mockStartListening = () => { console.log('Start listening'); }; const mockStopListening = () => { console.log('Stop listening'); }; const mockSetSendOnEnter = (sendOnEnter: 'keypress' | 'click') => { console.log('Set send on enter:', sendOnEnter); }; const mockSetAttachmentsMenuOpen = (attachmentsMenuOpen: 'link' | 'media') => { console.log('Set attachments menu open:', attachmentsMenuOpen); }; // Story decorator to provide context const withArtifactProvider = (Story: any) => ( ); const meta: Meta = { title: 'Artifact System', component: ArtifactHandler, decorators: [withArtifactProvider], parameters: { layout: 'fullscreen', }, }; export default meta; type Story = StoryObj; // Sample artifacts for different mime types const htmlArtifact = `L'utente mi chiede di generare una semplice pagina HTML. UserΓ² il sistema Artifact per creare una pagina HTML pulita e semplice, seguendo le linee guida fornite. Devo: 1. Usare il formato corretto: 2. Creare una pagina HTML completa con DOCTYPE 3. Il tag deve essere DOPO 4. Mantenerla semplice come richiesto 5. Includere un po' di stile per renderla gradevole ma non eccessivaCiao! Ti creo subito una pagina HTML semplice e pulita: La mia pagina semplice

Benvenuto nella mia pagina!

Questa Γ¨ una pagina HTML semplice e pulita.

Contiene alcuni elementi di base:

  • Un titolo principale
  • Paragrafi di testo
  • Una lista
  • Un po' di stile CSS
Nota: Puoi modificare liberamente questo codice per personalizzare la tua pagina!

Grazie per aver visitato la pagina! 😊

Ecco una pagina HTML semplice con: - Struttura pulita e ben organizzata - CSS inline per uno styling leggero - Layout responsive centrato - Elementi di base: titoli, paragrafi, lista - Un tocco di colore con la sezione evidenziata Puoi vedere l'anteprima nel drawer che si Γ¨ aperto e modificare il codice come preferisci!`; const htmlArtifactWithScrolling = ` Memori & AIsuru

Memori & AIsuru

Dove l'intelligenza artificiale incontra la memoria del futuro

Scopri di piΓΉ

Caratteristiche Innovative

🧠

Intelligenza Avanzata

Sistema di AI all'avanguardia che apprende e si adatta alle tue esigenze specifiche, offrendo soluzioni personalizzate e intelligenti.

πŸ’Ύ

Memoria Infinita

CapacitΓ  di archiviazione e recupero dati senza limiti, con algoritmi ottimizzati per prestazioni eccezionali.

⚑

VelocitΓ  Estrema

Elaborazione in tempo reale con latenza minimale, garantendo risposte immediate e fluide in ogni situazione.

Brand Palette

Light Teal
#9CDCD9
Bright Cyan
#00AEC7
Purple
#8246AF
Dark Purple
#653165
Navy
#005587

Tipografia Lexend

Lexend Thin - Eleganza e leggerezza
Lexend Light - Chiarezza ottimale
Lexend Regular - Testo standard leggibile
Lexend Medium - Enfasi equilibrata
Lexend Semibold - Importanza marcata
Lexend Black - Impatto Massimo
`; const htmlArtifactScrollTest = ` Scroll Test

Scroll Test Page

Before the fix this iframe overflow forced the container to scroll.

${Array.from({ length: 6 }) .map( (_, idx) => `

Section ${idx + 1}

This is tall content block ${idx + 1}. It exists purely to make the page taller than the drawer viewport so we can ensure the iframe owns the scrollable area.

Metric A

${(idx + 1) * 120} units

Metric B

${(idx + 1) * 45}%

Metric C

${(idx + 1) * 300} pts

` ) .join('')}
End of scroll test content
`; const svgArtifact = ` SVG `; const markdownArtifact = ` # Project Documentation ## Overview This is a comprehensive guide for the project. ## Features - **Easy to use**: Simple interface - **Fast**: Optimized performance - **Reliable**: Well tested ## Getting Started 1. Install dependencies 2. Run the application 3. Open your browser ## Code Example \`\`\`javascript function hello() { console.log("Hello World!"); } \`\`\` ## Conclusion This project helps you build amazing applications. `; const markdownCustomArtifact = ` # Custom Markdown Rendering Test ## Text formatting Use **bold**, *italic*, and \`inline code\` in the same sentence. ## Checklist - [x] Parse headings - [x] Render lists - [ ] Verify custom table support ## Table | Feature | Expected | | --- | --- | | Link rendering | Opens in a new tab | | Code block | Preserves indentation | | Blockquote | Styled correctly | ## Blockquote > This is a quoted note to verify markdown styling in preview mode. ## Code block \`\`\`ts type User = { id: string; name: string; active: boolean; }; const toLabel = (user: User) => \`\${user.name} (\${user.id})\`; \`\`\` ## Links - [Memori website](https://memori.ai) - [Example docs](https://example.com/docs) `; const cssArtifact = ` /* Modern CSS Grid Layout */ .container { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; padding: 20px; max-width: 1200px; margin: 0 auto; } .card { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 12px; padding: 20px; color: white; box-shadow: 0 4px 20px rgba(0,0,0,0.1); transition: transform 0.3s ease; } .card:hover { transform: translateY(-5px); box-shadow: 0 8px 30px rgba(0,0,0,0.2); } .card h3 { margin-top: 0; font-size: 1.5em; } .card p { line-height: 1.6; opacity: 0.9; } @media (max-width: 768px) { .container { grid-template-columns: 1fr; padding: 10px; } } `; const javascriptArtifact = ` // Interactive Todo List Application class TodoApp { constructor() { this.todos = []; this.init(); } init() { this.createHTML(); this.bindEvents(); } createHTML() { const app = document.getElementById('app'); app.innerHTML = \`

My Todo List

    \`; } bindEvents() { document.getElementById('addBtn').addEventListener('click', () => this.addTodo()); document.getElementById('todoInput').addEventListener('keypress', (e) => { if (e.key === 'Enter') this.addTodo(); }); } addTodo() { const input = document.getElementById('todoInput'); const text = input.value.trim(); if (text) { const todo = { id: Date.now(), text: text, completed: false }; this.todos.push(todo); this.renderTodos(); input.value = ''; } } toggleTodo(id) { const todo = this.todos.find(t => t.id === id); if (todo) { todo.completed = !todo.completed; this.renderTodos(); } } deleteTodo(id) { this.todos = this.todos.filter(t => t.id !== id); this.renderTodos(); } renderTodos() { const list = document.getElementById('todoList'); list.innerHTML = this.todos.map(todo => \`
  • \${todo.text}
  • \`).join(''); } } // Initialize the app const app = new TodoApp();
    `; const jsonArtifact = ` { "name": "user-profile-api", "version": "1.0.0", "description": "REST API for user profile management", "main": "server.js", "scripts": { "start": "node server.js", "dev": "nodemon server.js", "test": "jest", "build": "webpack --mode production" }, "dependencies": { "express": "^4.18.0", "mongoose": "^6.0.0", "bcryptjs": "^2.4.3", "jsonwebtoken": "^8.5.1", "cors": "^2.8.5", "helmet": "^5.0.0", "morgan": "^1.10.0", "express-rate-limit": "^6.0.0" }, "devDependencies": { "nodemon": "^2.0.15", "jest": "^27.5.1", "supertest": "^6.2.2" }, "engines": { "node": ">=14.0.0" }, "repository": { "type": "git", "url": "https://github.com/example/user-profile-api.git" }, "keywords": ["api", "express", "mongodb", "authentication", "rest"], "author": "Your Name", "license": "MIT" } `; // Stories export const HTMLArtifact: Story = { args: {}, render: () => ( ), }; export const HTMLArtifactWithScrolling: Story = { args: {}, render: () => ( ), }; export const HTMLScrollTest: Story = { args: {}, render: () => ( ), }; export const MarkdownDocumentation: Story = { args: {}, render: () => ( ), }; export const MarkdownCustomRendering: Story = { args: {}, render: () => ( ), }; export const CSSStyles: Story = { args: {}, render: () => ( ), }; export const JavaScriptApp: Story = { args: {}, render: () => ( ), }; export const JSONConfiguration: Story = { args: {}, render: () => ( ), }; export const MultipleArtifacts: Story = { args: {}, render: () => ( ), }; export const ThreeArtifactsInOneMessage: Story = { args: {}, render: () => ( Dashboard

    Sales Dashboard

    Revenue

    $0

    Customers

    0

    Growth

    0%

    /* Dashboard Styles */ .dashboard-container { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; max-width: 1200px; margin: 0 auto; padding: 40px 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; } h1 { color: white; text-align: center; font-size: 2.5rem; margin-bottom: 40px; text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); } .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 24px; margin-top: 30px; } .stat-card { background: white; border-radius: 16px; padding: 32px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); transition: transform 0.3s ease, box-shadow 0.3s ease; text-align: center; } .stat-card:hover { transform: translateY(-8px); box-shadow: 0 15px 40px rgba(0, 0, 0, 0.3); } .stat-card h3 { color: #667eea; font-size: 1.2rem; margin-bottom: 16px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; } .stat-card p { color: #333; font-size: 2.5rem; font-weight: bold; margin: 0; } // Dashboard Data Management class Dashboard { constructor() { this.data = { revenue: 0, customers: 0, growth: 0 }; this.init(); } init() { // Simulate loading data this.loadData(); // Update every 5 seconds setInterval(() => this.updateData(), 5000); } loadData() { // Simulate API call this.data = { revenue: 124500, customers: 1234, growth: 23.5 }; this.render(); } updateData() { // Simulate real-time updates this.data.revenue += Math.floor(Math.random() * 1000); this.data.customers += Math.floor(Math.random() * 10); this.data.growth += (Math.random() - 0.5) * 2; this.render(); } render() { const revenueEl = document.getElementById('revenue'); const customersEl = document.getElementById('customers'); const growthEl = document.getElementById('growth'); if (revenueEl) { revenueEl.textContent = '$' + this.data.revenue.toLocaleString(); } if (customersEl) { customersEl.textContent = this.data.customers.toLocaleString(); } if (growthEl) { growthEl.textContent = '+' + this.data.growth.toFixed(1) + '%'; } } } // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { new Dashboard(); }); } else { new Dashboard(); } Each artifact serves a specific purpose: - **HTML**: The structure and content - **CSS**: Beautiful styling with gradients and animations - **JavaScript**: Interactive data management and updates You can click on each card to view and modify the code!`, fromUser: false, timestamp: new Date().toISOString(), }, ]} pushMessage={mockPushMessage} simulateUserPrompt={mockSimulateUserPrompt} onChangeUserMessage={mockOnChangeUserMessage} sendMessage={mockSendMessage} setEnableFocusChatInput={mockSetEnableFocusChatInput} stopAudio={mockStopAudio} startListening={mockStartListening} stopListening={mockStopListening} setSendOnEnter={mockSetSendOnEnter} setAttachmentsMenuOpen={mockSetAttachmentsMenuOpen} showInputs={false} isChatlogPanel={false} /> ), }; export const FiveArtifactsMixedTypes: Story = { args: {}, render: () => ( My Project
    body { margin: 0; padding: 20px; font-family: Arial, sans-serif; background: #f5f5f5; } #app { max-width: 800px; margin: 0 auto; background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } const app = document.getElementById('app'); app.innerHTML = '

    Hello World!

    '; console.log('App initialized');
    { "name": "my-project", "version": "1.0.0", "description": "A simple web project", "main": "app.js", "scripts": { "start": "serve .", "build": "webpack" }, "dependencies": { "serve": "^14.0.0" } } # My Project ## Overview A simple web application starter template. ## Getting Started 1. Install dependencies: \`npm install\` 2. Start the server: \`npm start\` 3. Open http://localhost:3000 ## Features - Clean HTML structure - Modern CSS styling - JavaScript functionality - Package management with npm ## License MIT All files are ready! You now have a complete project structure with HTML, CSS, JavaScript, configuration, and documentation.`, fromUser: false, timestamp: new Date().toISOString(), }, ]} pushMessage={mockPushMessage} simulateUserPrompt={mockSimulateUserPrompt} onChangeUserMessage={mockOnChangeUserMessage} sendMessage={mockSendMessage} setEnableFocusChatInput={mockSetEnableFocusChatInput} stopAudio={mockStopAudio} startListening={mockStartListening} stopListening={mockStopListening} setSendOnEnter={mockSetSendOnEnter} setAttachmentsMenuOpen={mockSetAttachmentsMenuOpen} showInputs={false} isChatlogPanel={false} /> ), }; export const SVGArtifact: Story = { args: {}, render: () => ( ), }; export const ConversationFlow: Story = { args: {}, render: () => ( ), }; export const NoArtifacts: Story = { args: {}, render: () => ( ), }; // ======================================== // ArtifactAPI Bridge Stories // ======================================== export const APIBridge_CreateSimpleArtifact: Story = { args: {}, render: () => { const TestComponent = () => { const { state } = useArtifact(); useEffect(() => { // Wait a bit for the API to be available setTimeout(() => { console.log( 'MemoriArtifactAPI available:', !!window.MemoriArtifactAPI ); }, 100); }, []); const createHTMLArtifact = () => { window.MemoriArtifactAPI?.createAndOpenArtifact( '

    πŸŽ‰ Created from API!

    This artifact was created using window.MemoriArtifactAPI.createAndOpenArtifact()

    You can use this API to create artifacts programmatically from any JavaScript code.

    ', 'html', 'API Test Artifact' ); }; const createMarkdownArtifact = () => { window.MemoriArtifactAPI?.createAndOpenArtifact( `# Artifact Created via API ## This is a test This artifact was created using the global API: \`\`\`javascript window.MemoriArtifactAPI.createAndOpenArtifact(content, 'markdown', 'Title'); \`\`\` ### Features: - βœ… Programmatic control - βœ… Multiple MIME types - βœ… Easy integration`, 'markdown', 'API Markdown Example' ); }; const createJavaScriptArtifact = () => { window.MemoriArtifactAPI?.createAndOpenArtifact( `// Example function created via API function greet(name) { console.log(\`Hello, \${name}!\`); return \`Welcome, \${name}\`; } // Call it const message = greet('Developer'); console.log(message);`, 'javascript', 'JavaScript via API' ); }; const checkState = () => { const state = window.MemoriArtifactAPI?.getState(); console.log('Current artifact state:', state); alert(JSON.stringify(state, null, 2)); }; return ( <> {/* Conditionally render ArtifactDrawer to avoid hooks error */} {state.isDrawerOpen && }

    πŸ§ͺ Artifact API Test Lab

    Test the global window.MemoriArtifactAPI by clicking the buttons below. Open the browser console to see the API in action.

    πŸ’‘ Console Commands

    Try these in the browser console:

                    {`// Create an artifact
    window.MemoriArtifactAPI.createAndOpenArtifact(
      '

    Test

    ', 'html', 'My Title' ); // Check state window.MemoriArtifactAPI.getState(); // Close window.MemoriArtifactAPI.closeArtifact();`}
    ); }; return ; }, }; export const APIBridge_ProcessOutputElements: Story = { args: {}, render: () => { const TestComponent = () => { const { state } = useArtifact(); const createFromOutput = () => { const outputs = document.querySelectorAll( '.memori-artifact[data-sample="true"]' ); outputs.forEach(output => { const artifactId = window.MemoriArtifactAPI?.createFromOutputElement( output as HTMLOutputElement ); console.log('Created artifact:', artifactId); }); alert(`Processed ${outputs.length} artifacts. Check console for IDs.`); }; const addDynamicOutput = () => { const container = document.getElementById('dynamic-container'); if (container) { const output = document.createElement('output'); output.className = 'memori-artifact'; output.setAttribute('data-mimetype', 'html'); output.setAttribute('data-title', 'Dynamic Artifact'); output.setAttribute('data-sample', 'true'); output.innerHTML = `

    πŸš€ Dynamically Added!

    This artifact was added to the DOM dynamically and then processed.

    Timestamp: ${new Date().toLocaleTimeString()}

    `; container.appendChild(output); } }; return ( <> {/* Conditionally render ArtifactDrawer to avoid hooks error */} {state.isDrawerOpen && }

    πŸ”„ Process Output Elements

    This story demonstrates processing{' '} <output class="memori-artifact">{' '} elements using createFromOutputElement.

    Existing Output Elements:

    Sample Artifact 1

    This is a static output element in the DOM.

    {`# Sample Markdown This is **another** static output element. - Item 1 - Item 2 - Item 3`}

    πŸ’‘ How it works

    1. Click "Create From Output Elements" to process{' '} <output> elements
    2. Each element gets converted to an artifact and added to history
    3. Click "Add Dynamic Output" to inject a new element
    4. Process again to handle the new element
    5. The artifacts will appear in the chat history
    ); }; return ; }, }; export const APIBridge_WebSocketSimulation: Story = { args: {}, render: () => { const TestComponent = () => { const { state } = useArtifact(); const simulateWebSocket = () => { // Simulate receiving a message with artifacts from WebSocket const artifactContent = `

    πŸ“Š Sales Report Q4 2024

    Revenue

    $124,500

    Customers

    1,234

    Growth

    +23%

    `; // Use the API to create and open the artifact window.MemoriArtifactAPI?.createAndOpenArtifact( artifactContent, 'html', 'Data Visualization' ); // Update chat display const chatContainer = document.getElementById('chat-simulation'); if (chatContainer) { const messageHTML = `

    Bot: I've created a visualization for you. Click the artifact card to view it.

    πŸ“Š Data Visualization
    `; chatContainer.innerHTML += messageHTML; } console.log('WebSocket artifact created via API'); }; const clearChat = () => { const chatContainer = document.getElementById('chat-simulation'); if (chatContainer) { chatContainer.innerHTML = '

    Chat cleared. Click "Simulate WebSocket Message" to add new content.

    '; } }; return ( <> {/* Conditionally render ArtifactDrawer to avoid hooks error */} {state.isDrawerOpen && }

    🌐 WebSocket Integration Simulation

    This demonstrates how createAndOpenArtifact can be used with WebSocket or Action Cable to create artifacts from messages received dynamically.

    Click "Simulate WebSocket Message" to receive a message with an artifact...

    πŸ’‘ Implementation Example

                    {`// Rails Action Cable
    consumer.subscriptions.create("ChatChannel", {
      received(data) {
        if (data.artifact) {
          // Create artifact directly from received data
          window.MemoriArtifactAPI?.createAndOpenArtifact(
            data.artifact.content,
            data.artifact.mimeType,
            data.artifact.title
          );
        }
      }
    });`}
                  
    ); }; return ; }, };