diff --git a/typescript-sdk/apps/dojo/e2e/package.json b/typescript-sdk/apps/dojo/e2e/package.json index bf2af8e93..8ea54b975 100644 --- a/typescript-sdk/apps/dojo/e2e/package.json +++ b/typescript-sdk/apps/dojo/e2e/package.json @@ -6,7 +6,8 @@ "scripts": { "postinstall": "playwright install --with-deps", "test": "playwright test", - "test:ui": "playwright test --ui" + "test:ui": "playwright test --ui", + "report": "playwright show-report" }, "devDependencies": { "@playwright/test": "^1.43.1", diff --git a/typescript-sdk/apps/dojo/e2e/pages/crewAIPages/PredictiveStateUpdatesPage.ts b/typescript-sdk/apps/dojo/e2e/pages/crewAIPages/PredictiveStateUpdatesPage.ts index b7dcd9c3f..005bee3a7 100644 --- a/typescript-sdk/apps/dojo/e2e/pages/crewAIPages/PredictiveStateUpdatesPage.ts +++ b/typescript-sdk/apps/dojo/e2e/pages/crewAIPages/PredictiveStateUpdatesPage.ts @@ -30,7 +30,7 @@ export class PredictiveStateUpdatesPage { this.userMessage = page.locator('.copilotKitUserMessage'); } - async openChat() { + async openChat() { await this.agentGreeting.isVisible(); } @@ -54,13 +54,13 @@ export class PredictiveStateUpdatesPage { } async getUserApproval() { - await this.userApprovalModal.isVisible(); + await this.userApprovalModal.last().isVisible(); await this.getButton(this.page, "Confirm"); - const acceptedLabel = this.userApprovalModal.locator('text=✓ Accepted'); + const acceptedLabel = this.userApprovalModal.last().locator('text=✓ Accepted'); } async getUserRejection() { - await this.userApprovalModal.isVisible(); + await this.userApprovalModal.last().isVisible(); await this.getButton(this.page, "Reject"); const rejectedLabel = await this.getStatusLabelOfButton(this.page, "✕ Rejected"); await rejectedLabel.isVisible(); @@ -85,7 +85,7 @@ export class PredictiveStateUpdatesPage { 'div.tiptap em', 'div.tiptap s' ]; - + let count = 0; for (const selector of highlightSelectors) { count = await this.page.locator(selector).count(); @@ -93,7 +93,7 @@ export class PredictiveStateUpdatesPage { break; } } - + if (count > 0) { expect(count).toBeGreaterThan(0); } else { diff --git a/typescript-sdk/apps/dojo/e2e/pages/pydanticAIPages/PredictiveStateUpdatesPage.ts b/typescript-sdk/apps/dojo/e2e/pages/pydanticAIPages/PredictiveStateUpdatesPage.ts index b7dcd9c3f..005bee3a7 100644 --- a/typescript-sdk/apps/dojo/e2e/pages/pydanticAIPages/PredictiveStateUpdatesPage.ts +++ b/typescript-sdk/apps/dojo/e2e/pages/pydanticAIPages/PredictiveStateUpdatesPage.ts @@ -30,7 +30,7 @@ export class PredictiveStateUpdatesPage { this.userMessage = page.locator('.copilotKitUserMessage'); } - async openChat() { + async openChat() { await this.agentGreeting.isVisible(); } @@ -54,13 +54,13 @@ export class PredictiveStateUpdatesPage { } async getUserApproval() { - await this.userApprovalModal.isVisible(); + await this.userApprovalModal.last().isVisible(); await this.getButton(this.page, "Confirm"); - const acceptedLabel = this.userApprovalModal.locator('text=✓ Accepted'); + const acceptedLabel = this.userApprovalModal.last().locator('text=✓ Accepted'); } async getUserRejection() { - await this.userApprovalModal.isVisible(); + await this.userApprovalModal.last().isVisible(); await this.getButton(this.page, "Reject"); const rejectedLabel = await this.getStatusLabelOfButton(this.page, "✕ Rejected"); await rejectedLabel.isVisible(); @@ -85,7 +85,7 @@ export class PredictiveStateUpdatesPage { 'div.tiptap em', 'div.tiptap s' ]; - + let count = 0; for (const selector of highlightSelectors) { count = await this.page.locator(selector).count(); @@ -93,7 +93,7 @@ export class PredictiveStateUpdatesPage { break; } } - + if (count > 0) { expect(count).toBeGreaterThan(0); } else { diff --git a/typescript-sdk/apps/dojo/e2e/tests/agnoTests/toolBasedGenUIPage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/agnoTests/toolBasedGenUIPage.spec.ts index 27078ba32..5b5d88ba5 100644 --- a/typescript-sdk/apps/dojo/e2e/tests/agnoTests/toolBasedGenUIPage.spec.ts +++ b/typescript-sdk/apps/dojo/e2e/tests/agnoTests/toolBasedGenUIPage.spec.ts @@ -4,7 +4,7 @@ import { ToolBaseGenUIPage } from "../../pages/agnoPages/ToolBaseGenUIPage"; const pageURL = "/agno/feature/tool_based_generative_ui"; -test.fixme('[Agno] Haiku generation and display verification', async ({ +test('[Agno] Haiku generation and display verification', async ({ page, }) => { await page.goto(pageURL); @@ -17,7 +17,7 @@ test.fixme('[Agno] Haiku generation and display verification', async ({ await genAIAgent.checkHaikuDisplay(page); }); -test.fixme('[Agno] Haiku generation and UI consistency for two different prompts', async ({ +test('[Agno] Haiku generation and UI consistency for two different prompts', async ({ page, }) => { await page.goto(pageURL); diff --git a/typescript-sdk/apps/dojo/e2e/tests/crewAITests/agenticChatPage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/crewAITests/agenticChatPage.spec.ts index fedc8075a..efabfc306 100644 --- a/typescript-sdk/apps/dojo/e2e/tests/crewAITests/agenticChatPage.spec.ts +++ b/typescript-sdk/apps/dojo/e2e/tests/crewAITests/agenticChatPage.spec.ts @@ -26,7 +26,7 @@ test("[CrewAI] Agentic Chat sends and receives a message", async ({ }); }); -test.fixme("[CrewAI] Agentic Chat changes background on message and reset", async ({ +test("[CrewAI] Agentic Chat changes background on message and reset", async ({ page, }) => { await retryOnAIFailure(async () => { diff --git a/typescript-sdk/apps/dojo/e2e/tests/crewAITests/agenticGenUI.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/crewAITests/agenticGenUI.spec.ts index 7b29ff884..6fc685cac 100644 --- a/typescript-sdk/apps/dojo/e2e/tests/crewAITests/agenticGenUI.spec.ts +++ b/typescript-sdk/apps/dojo/e2e/tests/crewAITests/agenticGenUI.spec.ts @@ -2,6 +2,7 @@ import { test, expect } from "@playwright/test"; import { AgenticGenUIPage } from "../../pages/crewAIPages/AgenticUIGenPage"; test.describe("Agent Generative UI Feature", () => { + // Flaky test.fixme("[CrewAI] should interact with the chat to get a planner on prompt", async ({ page, }) => { @@ -33,6 +34,7 @@ test.describe("Agent Generative UI Feature", () => { ); }); + // Flaky test.fixme("[CrewAI] should interact with the chat using predefined prompts and perform steps", async ({ page, }) => { diff --git a/typescript-sdk/apps/dojo/e2e/tests/crewAITests/predictvieStateUpdatePage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/crewAITests/predictiveStateUpdatePage.spec.ts similarity index 100% rename from typescript-sdk/apps/dojo/e2e/tests/crewAITests/predictvieStateUpdatePage.spec.ts rename to typescript-sdk/apps/dojo/e2e/tests/crewAITests/predictiveStateUpdatePage.spec.ts diff --git a/typescript-sdk/apps/dojo/e2e/tests/langgraphFastAPITests/agenticGenUI.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/langgraphFastAPITests/agenticGenUI.spec.ts index 2ffda9f37..66b8ab3dd 100644 --- a/typescript-sdk/apps/dojo/e2e/tests/langgraphFastAPITests/agenticGenUI.spec.ts +++ b/typescript-sdk/apps/dojo/e2e/tests/langgraphFastAPITests/agenticGenUI.spec.ts @@ -35,7 +35,7 @@ test.describe("Agent Generative UI Feature", () => { ); }); - test.fixme("[LangGraph FastAPI] should interact with the chat using predefined prompts and perform steps", async ({ + test("[LangGraph FastAPI] should interact with the chat using predefined prompts and perform steps", async ({ page, }) => { const genUIAgent = new AgenticGenUIPage(page); diff --git a/typescript-sdk/apps/dojo/e2e/tests/langgraphFastAPITests/predictvieStateUpdatePage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/langgraphFastAPITests/predictiveStateUpdatePage.spec.ts similarity index 100% rename from typescript-sdk/apps/dojo/e2e/tests/langgraphFastAPITests/predictvieStateUpdatePage.spec.ts rename to typescript-sdk/apps/dojo/e2e/tests/langgraphFastAPITests/predictiveStateUpdatePage.spec.ts diff --git a/typescript-sdk/apps/dojo/e2e/tests/langgraphFastAPITests/sharedStatePage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/langgraphFastAPITests/sharedStatePage.spec.ts index c5213667d..a80c763d3 100644 --- a/typescript-sdk/apps/dojo/e2e/tests/langgraphFastAPITests/sharedStatePage.spec.ts +++ b/typescript-sdk/apps/dojo/e2e/tests/langgraphFastAPITests/sharedStatePage.spec.ts @@ -21,7 +21,7 @@ test.describe("Shared State Feature", () => { ); }); - test.fixme("[LangGraph FastAPI] should share state between UI and chat", async ({ + test("[LangGraph FastAPI] should share state between UI and chat", async ({ page, }) => { const sharedStateAgent = new SharedStatePage(page); diff --git a/typescript-sdk/apps/dojo/e2e/tests/langgraphFastAPITests/toolBasedGenUIPage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/langgraphFastAPITests/toolBasedGenUIPage.spec.ts index 38560a9df..4624ee995 100644 --- a/typescript-sdk/apps/dojo/e2e/tests/langgraphFastAPITests/toolBasedGenUIPage.spec.ts +++ b/typescript-sdk/apps/dojo/e2e/tests/langgraphFastAPITests/toolBasedGenUIPage.spec.ts @@ -4,7 +4,7 @@ import { ToolBaseGenUIPage } from "../../pages/langGraphFastAPIPages/ToolBaseGen const pageURL = "/langgraph-fastapi/feature/tool_based_generative_ui"; -test.fixme('[LangGraph FastAPI] Haiku generation and display verification', async ({ +test('[LangGraph FastAPI] Haiku generation and display verification', async ({ page, }) => { await page.goto(pageURL); @@ -17,7 +17,7 @@ test.fixme('[LangGraph FastAPI] Haiku generation and display verification', asyn await genAIAgent.checkHaikuDisplay(page); }); -test.fixme('[LangGraph FastAPI] Haiku generation and UI consistency for two different prompts', async ({ +test('[LangGraph FastAPI] Haiku generation and UI consistency for two different prompts', async ({ page, }) => { await page.goto(pageURL); diff --git a/typescript-sdk/apps/dojo/e2e/tests/langgraphTests/predictvieStateUpdatePage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/langgraphTests/predictiveStateUpdatePage.spec.ts similarity index 100% rename from typescript-sdk/apps/dojo/e2e/tests/langgraphTests/predictvieStateUpdatePage.spec.ts rename to typescript-sdk/apps/dojo/e2e/tests/langgraphTests/predictiveStateUpdatePage.spec.ts diff --git a/typescript-sdk/apps/dojo/e2e/tests/langgraphTests/toolBasedGenUIPage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/langgraphTests/toolBasedGenUIPage.spec.ts index 676bcb364..5c03599de 100644 --- a/typescript-sdk/apps/dojo/e2e/tests/langgraphTests/toolBasedGenUIPage.spec.ts +++ b/typescript-sdk/apps/dojo/e2e/tests/langgraphTests/toolBasedGenUIPage.spec.ts @@ -4,7 +4,7 @@ import { ToolBaseGenUIPage } from "../../pages/langGraphPages/ToolBaseGenUIPage" const pageURL = "/langgraph/feature/tool_based_generative_ui"; -test.fixme('[LangGraph] Haiku generation and display verification', async ({ +test('[LangGraph] Haiku generation and display verification', async ({ page, }) => { await page.goto(pageURL); @@ -17,7 +17,7 @@ test.fixme('[LangGraph] Haiku generation and display verification', async ({ await genAIAgent.checkHaikuDisplay(page); }); -test.fixme('[LangGraph] Haiku generation and UI consistency for two different prompts', async ({ +test('[LangGraph] Haiku generation and UI consistency for two different prompts', async ({ page, }) => { await page.goto(pageURL); diff --git a/typescript-sdk/apps/dojo/e2e/tests/llamaIndexTests/agenticGenUI.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/llamaIndexTests/agenticGenUI.spec.ts index ca16782c4..26eee678d 100644 --- a/typescript-sdk/apps/dojo/e2e/tests/llamaIndexTests/agenticGenUI.spec.ts +++ b/typescript-sdk/apps/dojo/e2e/tests/llamaIndexTests/agenticGenUI.spec.ts @@ -2,6 +2,7 @@ import { test, expect } from "@playwright/test"; import { AgenticGenUIPage } from "../../pages/llamaIndexPages/AgenticUIGenPage"; test.describe("Agent Generative UI Feature", () => { + // Fails. Issue with integration or something. test.fixme("[LlamaIndex] should interact with the chat to get a planner on prompt", async ({ page, }) => { @@ -35,6 +36,7 @@ test.describe("Agent Generative UI Feature", () => { ); }); + // Fails. Issue with integration or something. test.fixme("[LlamaIndex] should interact with the chat using predefined prompts and perform steps", async ({ page, }) => { diff --git a/typescript-sdk/apps/dojo/e2e/tests/mastraAgentLocalTests/toolBasedGenUIPage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/mastraAgentLocalTests/toolBasedGenUIPage.spec.ts index c5f32c1f9..133cc1c53 100644 --- a/typescript-sdk/apps/dojo/e2e/tests/mastraAgentLocalTests/toolBasedGenUIPage.spec.ts +++ b/typescript-sdk/apps/dojo/e2e/tests/mastraAgentLocalTests/toolBasedGenUIPage.spec.ts @@ -4,7 +4,7 @@ import { ToolBaseGenUIPage } from "../../pages/mastraAgentLocalPages/ToolBaseGen const pageURL = "/mastra-agent-local/feature/tool_based_generative_ui"; -test.fixme('[Mastra Agent Local] Haiku generation and display verification', async ({ +test('[Mastra Agent Local] Haiku generation and display verification', async ({ page, }) => { await page.goto(pageURL); @@ -17,7 +17,7 @@ test.fixme('[Mastra Agent Local] Haiku generation and display verification', asy await genAIAgent.checkHaikuDisplay(page); }); -test.fixme('[Mastra Agent Local] Haiku generation and UI consistency for two different prompts', async ({ +test('[Mastra Agent Local] Haiku generation and UI consistency for two different prompts', async ({ page, }) => { await page.goto(pageURL); diff --git a/typescript-sdk/apps/dojo/e2e/tests/mastraTests/toolBasedGenUIPage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/mastraTests/toolBasedGenUIPage.spec.ts index 2c2845211..2296b4f7f 100644 --- a/typescript-sdk/apps/dojo/e2e/tests/mastraTests/toolBasedGenUIPage.spec.ts +++ b/typescript-sdk/apps/dojo/e2e/tests/mastraTests/toolBasedGenUIPage.spec.ts @@ -4,6 +4,7 @@ import { ToolBaseGenUIPage } from "../../pages/mastraPages/ToolBaseGenUIPage"; const pageURL = "/mastra/feature/tool_based_generative_ui"; +// Fails. Not a test issue, issue with the integration or cpk. test.fixme('[Mastra] Haiku generation and display verification', async ({ page, }) => { @@ -17,6 +18,7 @@ test.fixme('[Mastra] Haiku generation and display verification', async ({ await genAIAgent.checkHaikuDisplay(page); }); +// Fails. Not a test issue, issue with the integration or cpk. test.fixme('[Mastra] Haiku generation and UI consistency for two different prompts', async ({ page, }) => { diff --git a/typescript-sdk/apps/dojo/e2e/tests/pydanticAITests/predictvieStateUpdatePage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/pydanticAITests/predictiveStateUpdatePage.spec.ts similarity index 95% rename from typescript-sdk/apps/dojo/e2e/tests/pydanticAITests/predictvieStateUpdatePage.spec.ts rename to typescript-sdk/apps/dojo/e2e/tests/pydanticAITests/predictiveStateUpdatePage.spec.ts index 3f386a236..373144b00 100644 --- a/typescript-sdk/apps/dojo/e2e/tests/pydanticAITests/predictvieStateUpdatePage.spec.ts +++ b/typescript-sdk/apps/dojo/e2e/tests/pydanticAITests/predictiveStateUpdatePage.spec.ts @@ -6,6 +6,7 @@ import { import { PredictiveStateUpdatesPage } from "../../pages/pydanticAIPages/PredictiveStateUpdatesPage"; test.describe("Predictive Status Updates Feature", () => { + // Fails. Issue with integration or something. test.fixme("[PydanticAI] should interact with agent and approve asked changes", async ({ page, }) => { @@ -43,7 +44,7 @@ test.describe("Predictive Status Updates Feature", () => { }); }); - test.fixme("[PydanticAI] should interact with agent and reject asked changes", async ({ + test("[PydanticAI] should interact with agent and reject asked changes", async ({ page, }) => { await retryOnAIFailure(async () => { diff --git a/typescript-sdk/apps/dojo/e2e/tests/pydanticAITests/toolBasedGenUIPage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/pydanticAITests/toolBasedGenUIPage.spec.ts index aeee3755a..556aa390d 100644 --- a/typescript-sdk/apps/dojo/e2e/tests/pydanticAITests/toolBasedGenUIPage.spec.ts +++ b/typescript-sdk/apps/dojo/e2e/tests/pydanticAITests/toolBasedGenUIPage.spec.ts @@ -4,7 +4,7 @@ import { ToolBaseGenUIPage } from "../../pages/pydanticAIPages/ToolBaseGenUIPage const pageURL = "/pydantic-ai/feature/tool_based_generative_ui"; -test.fixme('[PydanticAI] Haiku generation and display verification', async ({ +test('[PydanticAI] Haiku generation and display verification', async ({ page, }) => { await page.goto(pageURL); @@ -17,7 +17,7 @@ test.fixme('[PydanticAI] Haiku generation and display verification', async ({ await genAIAgent.checkHaikuDisplay(page); }); -test.fixme('[PydanticAI] Haiku generation and UI consistency for two different prompts', async ({ +test('[PydanticAI] Haiku generation and UI consistency for two different prompts', async ({ page, }) => { await page.goto(pageURL); diff --git a/typescript-sdk/apps/dojo/e2e/tests/serverStarterAllFeaturesTests/predictvieStateUpdatePage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/serverStarterAllFeaturesTests/predictiveStateUpdatePage.spec.ts similarity index 100% rename from typescript-sdk/apps/dojo/e2e/tests/serverStarterAllFeaturesTests/predictvieStateUpdatePage.spec.ts rename to typescript-sdk/apps/dojo/e2e/tests/serverStarterAllFeaturesTests/predictiveStateUpdatePage.spec.ts diff --git a/typescript-sdk/apps/dojo/e2e/tests/serverStarterAllFeaturesTests/sharedStatePage.spec.ts b/typescript-sdk/apps/dojo/e2e/tests/serverStarterAllFeaturesTests/sharedStatePage.spec.ts index 3729c1e14..87cef99d1 100644 --- a/typescript-sdk/apps/dojo/e2e/tests/serverStarterAllFeaturesTests/sharedStatePage.spec.ts +++ b/typescript-sdk/apps/dojo/e2e/tests/serverStarterAllFeaturesTests/sharedStatePage.spec.ts @@ -21,6 +21,7 @@ test.describe("Shared State Feature", () => { ); }); + // Fails. Issue with the test, most likely test.fixme("[Server Starter all features] should share state between UI and chat", async ({ page, }) => { diff --git a/typescript-sdk/apps/dojo/e2e2/.gitignore b/typescript-sdk/apps/dojo/e2e2/.gitignore deleted file mode 100644 index 58786aac7..000000000 --- a/typescript-sdk/apps/dojo/e2e2/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ - -# Playwright -node_modules/ -/test-results/ -/playwright-report/ -/blob-report/ -/playwright/.cache/ diff --git a/typescript-sdk/apps/dojo/e2e2/package.json b/typescript-sdk/apps/dojo/e2e2/package.json deleted file mode 100644 index e15283561..000000000 --- a/typescript-sdk/apps/dojo/e2e2/package.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "e2e2", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": {}, - "keywords": [], - "author": "", - "license": "ISC", - "devDependencies": { - "@playwright/test": "^1.54.2", - "@types/node": "^24.1.0" - } -} diff --git a/typescript-sdk/apps/dojo/e2e2/playwright.config.ts b/typescript-sdk/apps/dojo/e2e2/playwright.config.ts deleted file mode 100644 index 390646d4d..000000000 --- a/typescript-sdk/apps/dojo/e2e2/playwright.config.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { defineConfig, devices } from '@playwright/test'; - -/** - * Read environment variables from file. - * https://github.com/motdotla/dotenv - */ -// import dotenv from 'dotenv'; -// import path from 'path'; -// dotenv.config({ path: path.resolve(__dirname, '.env') }); - -/** - * See https://playwright.dev/docs/test-configuration. - */ -export default defineConfig({ - testDir: './tests', - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://localhost:3000', - - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', - }, - - /* Configure projects for major browsers */ - projects: [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, - }, - - { - name: 'firefox', - use: { ...devices['Desktop Firefox'] }, - }, - - { - name: 'webkit', - use: { ...devices['Desktop Safari'] }, - }, - - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, - - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, - // }, - ], - - /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // url: 'http://localhost:3000', - // reuseExistingServer: !process.env.CI, - // }, -}); diff --git a/typescript-sdk/apps/dojo/e2e2/pnpm-lock.yaml b/typescript-sdk/apps/dojo/e2e2/pnpm-lock.yaml deleted file mode 100644 index 26f24d9ed..000000000 --- a/typescript-sdk/apps/dojo/e2e2/pnpm-lock.yaml +++ /dev/null @@ -1,67 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - devDependencies: - '@playwright/test': - specifier: ^1.54.2 - version: 1.54.2 - '@types/node': - specifier: ^24.1.0 - version: 24.1.0 - -packages: - - '@playwright/test@1.54.2': - resolution: {integrity: sha512-A+znathYxPf+72riFd1r1ovOLqsIIB0jKIoPjyK2kqEIe30/6jF6BC7QNluHuwUmsD2tv1XZVugN8GqfTMOxsA==} - engines: {node: '>=18'} - hasBin: true - - '@types/node@24.1.0': - resolution: {integrity: sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==} - - fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - playwright-core@1.54.2: - resolution: {integrity: sha512-n5r4HFbMmWsB4twG7tJLDN9gmBUeSPcsBZiWSE4DnYz9mJMAFqr2ID7+eGC9kpEnxExJ1epttwR59LEWCk8mtA==} - engines: {node: '>=18'} - hasBin: true - - playwright@1.54.2: - resolution: {integrity: sha512-Hu/BMoA1NAdRUuulyvQC0pEqZ4vQbGfn8f7wPXcnqQmM+zct9UliKxsIkLNmz/ku7LElUNqmaiv1TG/aL5ACsw==} - engines: {node: '>=18'} - hasBin: true - - undici-types@7.8.0: - resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} - -snapshots: - - '@playwright/test@1.54.2': - dependencies: - playwright: 1.54.2 - - '@types/node@24.1.0': - dependencies: - undici-types: 7.8.0 - - fsevents@2.3.2: - optional: true - - playwright-core@1.54.2: {} - - playwright@1.54.2: - dependencies: - playwright-core: 1.54.2 - optionalDependencies: - fsevents: 2.3.2 - - undici-types@7.8.0: {} diff --git a/typescript-sdk/apps/dojo/e2e2/pnpm-workspace.yaml b/typescript-sdk/apps/dojo/e2e2/pnpm-workspace.yaml deleted file mode 100644 index e4aab11a2..000000000 --- a/typescript-sdk/apps/dojo/e2e2/pnpm-workspace.yaml +++ /dev/null @@ -1,2 +0,0 @@ -packages: - - '.' \ No newline at end of file diff --git a/typescript-sdk/apps/dojo/e2e2/tests/agno-agentic-chat.spec.ts b/typescript-sdk/apps/dojo/e2e2/tests/agno-agentic-chat.spec.ts deleted file mode 100644 index e556bc3ff..000000000 --- a/typescript-sdk/apps/dojo/e2e2/tests/agno-agentic-chat.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('renders initial message', async ({ page }) => { - await page.goto('http://localhost:9999/agno/feature/agentic_chat'); - - await expect(page.getByText('Hi, I\'m an agent. Want to chat?')).toBeVisible(); -}); - -test('responds to user message', async ({ page }) => { - await page.goto('http://localhost:9999/agno/feature/agentic_chat'); - - const textarea = page.getByPlaceholder('Type a message...'); - textarea.fill('How many sides are in a square? Please answer in one word. Do not use any punctuation, just the number in word form.'); - await page.keyboard.press('Enter'); - - page.locator('.copilotKitInputControls button.copilotKitInputControlButton').click(); - - await expect(page.locator('.copilotKitMessage')).toHaveCount(3); - await expect(page.locator('.copilotKitMessage.copilotKitAssistantMessage')).toHaveCount(2); - await expect(page.locator('.copilotKitMessage.copilotKitUserMessage')).toHaveCount(1); - await expect(page.locator('.copilotKitMessage.copilotKitAssistantMessage').last()).toHaveText('four', { ignoreCase: true }); -}); \ No newline at end of file diff --git a/typescript-sdk/apps/dojo/e2e2/tests/crewai-agentic-chat.spec.ts b/typescript-sdk/apps/dojo/e2e2/tests/crewai-agentic-chat.spec.ts deleted file mode 100644 index 24ca33b95..000000000 --- a/typescript-sdk/apps/dojo/e2e2/tests/crewai-agentic-chat.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('renders initial message', async ({ page }) => { - await page.goto('http://localhost:9999/crewai/feature/agentic_chat'); - - await expect(page.getByText('Hi, I\'m an agent. Want to chat?')).toBeVisible(); -}); - -test('responds to user message', async ({ page }) => { - await page.goto('http://localhost:9999/crewai/feature/agentic_chat'); - - const textarea = page.getByPlaceholder('Type a message...'); - textarea.fill('How many sides are in a square? Please answer in one word. Do not use any punctuation, just the number in word form.'); - await page.keyboard.press('Enter'); - - page.locator('.copilotKitInputControls button.copilotKitInputControlButton').click(); - - await expect(page.locator('.copilotKitMessage')).toHaveCount(3); - await expect(page.locator('.copilotKitMessage.copilotKitAssistantMessage')).toHaveCount(2); - await expect(page.locator('.copilotKitMessage.copilotKitUserMessage')).toHaveCount(1); - await expect(page.locator('.copilotKitMessage.copilotKitAssistantMessage').last()).toHaveText('four', { ignoreCase: true }); -}); \ No newline at end of file diff --git a/typescript-sdk/apps/dojo/e2e2/tests/langgraph-agentic-chat.spec.ts b/typescript-sdk/apps/dojo/e2e2/tests/langgraph-agentic-chat.spec.ts deleted file mode 100644 index 0d421f0dd..000000000 --- a/typescript-sdk/apps/dojo/e2e2/tests/langgraph-agentic-chat.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('renders initial message', async ({ page }) => { - await page.goto('http://localhost:9999/langgraph/feature/agentic_chat'); - - await expect(page.getByText('Hi, I\'m an agent. Want to chat?')).toBeVisible(); -}); - -test('responds to user message', async ({ page }) => { - await page.goto('http://localhost:9999/langgraph/feature/agentic_chat'); - - const textarea = page.getByPlaceholder('Type a message...'); - textarea.fill('How many sides are in a square? Please answer in one word. Do not use any punctuation, just the number in word form.'); - await page.keyboard.press('Enter'); - - page.locator('.copilotKitInputControls button.copilotKitInputControlButton').click(); - - await expect(page.locator('.copilotKitMessage')).toHaveCount(3); - await expect(page.locator('.copilotKitMessage.copilotKitAssistantMessage')).toHaveCount(2); - await expect(page.locator('.copilotKitMessage.copilotKitUserMessage')).toHaveCount(1); - await expect(page.locator('.copilotKitMessage.copilotKitAssistantMessage').last()).toHaveText('four', { ignoreCase: true }); -}); \ No newline at end of file diff --git a/typescript-sdk/apps/dojo/e2e2/tests/langgraph-fastapi-agentic-chat.spec.ts b/typescript-sdk/apps/dojo/e2e2/tests/langgraph-fastapi-agentic-chat.spec.ts deleted file mode 100644 index 45aae48d7..000000000 --- a/typescript-sdk/apps/dojo/e2e2/tests/langgraph-fastapi-agentic-chat.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('renders initial message', async ({ page }) => { - await page.goto('http://localhost:9999/langgraph-fastapi/feature/agentic_chat'); - - await expect(page.getByText('Hi, I\'m an agent. Want to chat?')).toBeVisible(); -}); - -test('responds to user message', async ({ page }) => { - await page.goto('http://localhost:9999/langgraph-fastapi/feature/agentic_chat'); - - const textarea = page.getByPlaceholder('Type a message...'); - textarea.fill('How many sides are in a square? Please answer in one word. Do not use any punctuation, just the number in word form.'); - await page.keyboard.press('Enter'); - - page.locator('.copilotKitInputControls button.copilotKitInputControlButton').click(); - - await expect(page.locator('.copilotKitMessage')).toHaveCount(3); - await expect(page.locator('.copilotKitMessage.copilotKitAssistantMessage')).toHaveCount(2); - await expect(page.locator('.copilotKitMessage.copilotKitUserMessage')).toHaveCount(1); - await expect(page.locator('.copilotKitMessage.copilotKitAssistantMessage').last()).toHaveText('four', { ignoreCase: true }); -}); \ No newline at end of file diff --git a/typescript-sdk/apps/dojo/e2e2/tests/langgraph-typescript-agentic-chat.spec.ts b/typescript-sdk/apps/dojo/e2e2/tests/langgraph-typescript-agentic-chat.spec.ts deleted file mode 100644 index 011efd3ff..000000000 --- a/typescript-sdk/apps/dojo/e2e2/tests/langgraph-typescript-agentic-chat.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('renders initial message', async ({ page }) => { - await page.goto('http://localhost:9999/langgraph-typescript/feature/agentic_chat'); - - await expect(page.getByText('Hi, I\'m an agent. Want to chat?')).toBeVisible(); -}); - -test('responds to user message', async ({ page }) => { - await page.goto('http://localhost:9999/langgraph-typescript/feature/agentic_chat'); - - const textarea = page.getByPlaceholder('Type a message...'); - textarea.fill('How many sides are in a square? Please answer in one word. Do not use any punctuation, just the number in word form.'); - await page.keyboard.press('Enter'); - - page.locator('.copilotKitInputControls button.copilotKitInputControlButton').click(); - - await expect(page.locator('.copilotKitMessage')).toHaveCount(3); - await expect(page.locator('.copilotKitMessage.copilotKitAssistantMessage')).toHaveCount(2); - await expect(page.locator('.copilotKitMessage.copilotKitUserMessage')).toHaveCount(1); - await expect(page.locator('.copilotKitMessage.copilotKitAssistantMessage').last()).toHaveText('four', { ignoreCase: true }); -}); \ No newline at end of file diff --git a/typescript-sdk/apps/dojo/e2e2/tests/llama-index-agentic-chat.spec.ts b/typescript-sdk/apps/dojo/e2e2/tests/llama-index-agentic-chat.spec.ts deleted file mode 100644 index cf2871800..000000000 --- a/typescript-sdk/apps/dojo/e2e2/tests/llama-index-agentic-chat.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('renders initial message', async ({ page }) => { - await page.goto('http://localhost:9999/llama-index/feature/agentic_chat'); - - await expect(page.getByText('Hi, I\'m an agent. Want to chat?')).toBeVisible(); -}); - -test('responds to user message', async ({ page }) => { - await page.goto('http://localhost:9999/llama-index/feature/agentic_chat'); - - const textarea = page.getByPlaceholder('Type a message...'); - textarea.fill('How many sides are in a square? Please answer in one word. Do not use any punctuation, just the number in word form.'); - await page.keyboard.press('Enter'); - - page.locator('.copilotKitInputControls button.copilotKitInputControlButton').click(); - - await expect(page.locator('.copilotKitMessage')).toHaveCount(3); - await expect(page.locator('.copilotKitMessage.copilotKitAssistantMessage')).toHaveCount(2); - await expect(page.locator('.copilotKitMessage.copilotKitUserMessage')).toHaveCount(1); - await expect(page.locator('.copilotKitMessage.copilotKitAssistantMessage').last()).toHaveText('four', { ignoreCase: true }); -}); \ No newline at end of file diff --git a/typescript-sdk/apps/dojo/e2e2/tests/mastra-agentic-chat.spec.ts b/typescript-sdk/apps/dojo/e2e2/tests/mastra-agentic-chat.spec.ts deleted file mode 100644 index 604b0652e..000000000 --- a/typescript-sdk/apps/dojo/e2e2/tests/mastra-agentic-chat.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('renders initial message', async ({ page }) => { - await page.goto('http://localhost:9999/mastra/feature/agentic_chat'); - - await expect(page.getByText('Hi, I\'m an agent. Want to chat?')).toBeVisible(); -}); - -test('responds to user message', async ({ page }) => { - await page.goto('http://localhost:9999/mastra/feature/agentic_chat'); - - const textarea = page.getByPlaceholder('Type a message...'); - textarea.fill('How many sides are in a square? Please answer in one word. Do not use any punctuation, just the number in word form.'); - await page.keyboard.press('Enter'); - - page.locator('.copilotKitInputControls button.copilotKitInputControlButton').click(); - - await expect(page.locator('.copilotKitMessage')).toHaveCount(3); - await expect(page.locator('.copilotKitMessage.copilotKitAssistantMessage')).toHaveCount(2); - await expect(page.locator('.copilotKitMessage.copilotKitUserMessage')).toHaveCount(1); - await expect(page.locator('.copilotKitMessage.copilotKitAssistantMessage').last()).toHaveText('four', { ignoreCase: true }); -}); \ No newline at end of file diff --git a/typescript-sdk/apps/dojo/e2e2/tests/pydantic-agentic-chat.spec.ts b/typescript-sdk/apps/dojo/e2e2/tests/pydantic-agentic-chat.spec.ts deleted file mode 100644 index 1f26ad2cf..000000000 --- a/typescript-sdk/apps/dojo/e2e2/tests/pydantic-agentic-chat.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('renders initial message', async ({ page }) => { - await page.goto('http://localhost:9999/pydantic-ai/feature/agentic_chat'); - - await expect(page.getByText('Hi, I\'m an agent. Want to chat?')).toBeVisible(); -}); - -test('responds to user message', async ({ page }) => { - await page.goto('http://localhost:9999/pydantic-ai/feature/agentic_chat'); - - const textarea = page.getByPlaceholder('Type a message...'); - textarea.fill('How many sides are in a square? Please answer in one word. Do not use any punctuation, just the number in word form.'); - await page.keyboard.press('Enter'); - - page.locator('.copilotKitInputControls button.copilotKitInputControlButton').click(); - - await expect(page.locator('.copilotKitMessage')).toHaveCount(3); - await expect(page.locator('.copilotKitMessage.copilotKitAssistantMessage')).toHaveCount(2); - await expect(page.locator('.copilotKitMessage.copilotKitUserMessage')).toHaveCount(1); - await expect(page.locator('.copilotKitMessage.copilotKitAssistantMessage').last()).toHaveText('four', { ignoreCase: true }); -}); \ No newline at end of file diff --git a/typescript-sdk/apps/dojo/e2e2/tests/vercel-ai-sdk-agentic-chat.spec.ts b/typescript-sdk/apps/dojo/e2e2/tests/vercel-ai-sdk-agentic-chat.spec.ts deleted file mode 100644 index 49640fde8..000000000 --- a/typescript-sdk/apps/dojo/e2e2/tests/vercel-ai-sdk-agentic-chat.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('renders initial message', async ({ page }) => { - await page.goto('http://localhost:9999/vercel-ai-sdk/feature/agentic_chat'); - - await expect(page.getByText('Hi, I\'m an agent. Want to chat?')).toBeVisible(); -}); - -test('responds to user message', async ({ page }) => { - await page.goto('http://localhost:9999/vercel-ai-sdk/feature/agentic_chat'); - - const textarea = page.getByPlaceholder('Type a message...'); - textarea.fill('How many sides are in a square? Please answer in one word. Do not use any punctuation, just the number in word form.'); - await page.keyboard.press('Enter'); - - page.locator('.copilotKitInputControls button.copilotKitInputControlButton').click(); - - await expect(page.locator('.copilotKitMessage')).toHaveCount(3); - await expect(page.locator('.copilotKitMessage.copilotKitAssistantMessage')).toHaveCount(2); - await expect(page.locator('.copilotKitMessage.copilotKitUserMessage')).toHaveCount(1); - await expect(page.locator('.copilotKitMessage.copilotKitAssistantMessage').last()).toHaveText('four', { ignoreCase: true }); -}); \ No newline at end of file diff --git a/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/tool_based_generative_ui/page.tsx b/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/tool_based_generative_ui/page.tsx index cb461aee4..e8387c8f7 100644 --- a/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/tool_based_generative_ui/page.tsx +++ b/typescript-sdk/apps/dojo/src/app/[integrationId]/feature/tool_based_generative_ui/page.tsx @@ -306,7 +306,7 @@ function Haiku() { image_names: finalCorrectedImages || [], selectedImage: finalCorrectedImages?.[0] || null, }; - setHaikus(prev => [...prev, newHaiku].filter(h => h.english[0] !== "A placeholder verse—")); + setHaikus(prev => [newHaiku, ...prev].filter(h => h.english[0] !== "A placeholder verse—")); setActiveIndex(haikus.length - 1); setIsJustApplied(true); setTimeout(() => setIsJustApplied(false), 600); diff --git a/typescript-sdk/apps/dojo/src/files.json b/typescript-sdk/apps/dojo/src/files.json index eb24814bc..43310706f 100644 --- a/typescript-sdk/apps/dojo/src/files.json +++ b/typescript-sdk/apps/dojo/src/files.json @@ -158,7 +158,7 @@ "pydantic-ai::tool_based_generative_ui": [ { "name": "page.tsx", - "content": "\"use client\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotKitCSSProperties, CopilotSidebar, CopilotChat } from \"@copilotkit/react-ui\";\nimport { Dispatch, SetStateAction, useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport React, { useMemo } from \"react\";\nimport { useMobileView } from \"@/utils/use-mobile-view\";\nimport { useMobileChat } from \"@/utils/use-mobile-chat\";\n\ninterface ToolBasedGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\ninterface GenerateHaiku {\n japanese: string[] | [],\n english: string[] | [],\n image_names: string[] | [],\n selectedImage: string | null,\n}\n\ninterface HaikuCardProps {\n generatedHaiku: GenerateHaiku | Partial\n setHaikus: Dispatch>\n haikus: GenerateHaiku[]\n}\n\nexport default function ToolBasedGenerativeUI({ params }: ToolBasedGenerativeUIProps) {\n const { integrationId } = React.use(params);\n const { isMobile } = useMobileView();\n\n\n const chatTitle = 'Haiku Generator'\n const chatDescription = 'Ask me to create haikus'\n const initialLabel = 'I\\'m a haiku generator 👋. How can I help you?'\n\n return (\n \n \n \n\n {/* Desktop Sidebar */}\n {!isMobile && (\n \n )}\n\n {/* Mobile Pull-Up Chat */}\n {isMobile && }\n \n \n );\n}\n\nfunction MobileChat({ chatTitle, chatDescription, initialLabel }: { chatTitle: string, chatDescription: string, initialLabel: string }) {\n const defaultChatHeight = 50\n\n const {\n isChatOpen,\n setChatHeight,\n setIsChatOpen,\n isDragging,\n chatHeight,\n handleDragStart\n } = useMobileChat(defaultChatHeight)\n return (\n <>\n {/* Chat Toggle Button */}\n
\n
\n {\n if (!isChatOpen) {\n setChatHeight(defaultChatHeight); // Reset to good default when opening\n }\n setIsChatOpen(!isChatOpen);\n }}\n >\n
\n
\n
{chatTitle}
\n
{chatDescription}
\n
\n
\n
\n \n \n \n
\n
\n \n\n {/* Pull-Up Chat Container */}\n \n {/* Drag Handle Bar */}\n \n
\n \n\n {/* Chat Header */}\n
\n
\n
\n

{chatTitle}

\n
\n setIsChatOpen(false)}\n className=\"p-2 hover:bg-gray-100 rounded-full transition-colors\"\n >\n \n \n \n \n
\n
\n\n {/* Chat Content - Flexible container for messages and input */}\n
\n \n
\n \n\n {/* Backdrop */}\n {isChatOpen && (\n setIsChatOpen(false)}\n />\n )}\n \n )\n}\n\nconst VALID_IMAGE_NAMES = [\n \"Osaka_Castle_Turret_Stone_Wall_Pine_Trees_Daytime.jpg\",\n \"Tokyo_Skyline_Night_Tokyo_Tower_Mount_Fuji_View.jpg\",\n \"Itsukushima_Shrine_Miyajima_Floating_Torii_Gate_Sunset_Long_Exposure.jpg\",\n \"Takachiho_Gorge_Waterfall_River_Lush_Greenery_Japan.jpg\",\n \"Bonsai_Tree_Potted_Japanese_Art_Green_Foliage.jpeg\",\n \"Shirakawa-go_Gassho-zukuri_Thatched_Roof_Village_Aerial_View.jpg\",\n \"Ginkaku-ji_Silver_Pavilion_Kyoto_Japanese_Garden_Pond_Reflection.jpg\",\n \"Senso-ji_Temple_Asakusa_Cherry_Blossoms_Kimono_Umbrella.jpg\",\n \"Cherry_Blossoms_Sakura_Night_View_City_Lights_Japan.jpg\",\n \"Mount_Fuji_Lake_Reflection_Cherry_Blossoms_Sakura_Spring.jpg\"\n];\n\nfunction getRandomImage(): string {\n return VALID_IMAGE_NAMES[Math.floor(Math.random() * VALID_IMAGE_NAMES.length)];\n}\n\nconst validateAndCorrectImageNames = (rawNames: string[] | undefined): string[] | null => {\n if (!rawNames || rawNames.length !== 3) {\n return null;\n }\n\n const correctedNames: string[] = [];\n const usedValidNames = new Set();\n\n for (const name of rawNames) {\n if (VALID_IMAGE_NAMES.includes(name) && !usedValidNames.has(name)) {\n correctedNames.push(name);\n usedValidNames.add(name);\n if (correctedNames.length === 3) break;\n }\n }\n\n while (correctedNames.length < 3) {\n const nextImage = getRandomImage();\n if (!usedValidNames.has(nextImage)) {\n correctedNames.push(nextImage);\n usedValidNames.add(nextImage);\n }\n }\n\n return correctedNames.slice(0, 3);\n};\n\nfunction HaikuCard({ generatedHaiku, setHaikus, haikus }: HaikuCardProps) {\n return (\n \n
\n {generatedHaiku?.japanese?.map((line, index) => (\n
\n

{line}

\n

\n {generatedHaiku.english?.[index]}\n

\n
\n ))}\n {generatedHaiku?.japanese && generatedHaiku.japanese.length >= 2 && (\n
\n {(() => {\n const firstLine = generatedHaiku?.japanese?.[0];\n if (!firstLine) return null;\n const haikuIndex = haikus.findIndex((h: any) => h.japanese[0] === firstLine);\n const haiku = haikus[haikuIndex];\n if (!haiku?.image_names) return null;\n\n return haiku.image_names.map((imageName, imgIndex) => (\n {\n setHaikus(prevHaikus => {\n const newHaikus = prevHaikus.map((h, idx) => {\n if (idx === haikuIndex) {\n return {\n ...h,\n selectedImage: imageName\n };\n }\n return h;\n });\n return newHaikus;\n });\n }}\n />\n ));\n })()}\n
\n )}\n
\n \n );\n}\n\ninterface Haiku {\n japanese: string[];\n english: string[];\n image_names: string[];\n selectedImage: string | null;\n}\n\nfunction Haiku() {\n const [haikus, setHaikus] = useState([{\n japanese: [\"仮の句よ\", \"まっさらながら\", \"花を呼ぶ\"],\n english: [\n \"A placeholder verse—\",\n \"even in a blank canvas,\",\n \"it beckons flowers.\",\n ],\n image_names: [],\n selectedImage: null,\n }])\n const [activeIndex, setActiveIndex] = useState(0);\n const [isJustApplied, setIsJustApplied] = useState(false);\n\n useCopilotAction({\n name: \"generate_haiku\",\n parameters: [\n {\n name: \"japanese\",\n type: \"string[]\",\n },\n {\n name: \"english\",\n type: \"string[]\",\n },\n {\n name: \"image_names\",\n type: \"string[]\",\n description: `Names of 3 relevant images selected from the following: \\n -${VALID_IMAGE_NAMES.join('\\n -')}`,\n },\n ],\n followUp: false,\n handler: async ({ japanese, english, image_names }: { japanese: string[], english: string[], image_names: string[] }) => {\n const finalCorrectedImages = validateAndCorrectImageNames(image_names);\n const newHaiku = {\n japanese: japanese || [],\n english: english || [],\n image_names: finalCorrectedImages || [],\n selectedImage: finalCorrectedImages?.[0] || null,\n };\n setHaikus(prev => [...prev, newHaiku].filter(h => h.english[0] !== \"A placeholder verse—\"));\n setActiveIndex(haikus.length - 1);\n setIsJustApplied(true);\n setTimeout(() => setIsJustApplied(false), 600);\n return \"Haiku generated.\";\n },\n render: ({ args: generatedHaiku }: { args: Partial }) => {\n return (\n \n );\n },\n }, [haikus]);\n\n const { isMobile } = useMobileView();\n\n return (\n
\n \n\n {/* Main Display */}\n
\n
\n {haikus.map((haiku, index) => (\n (haikus.length == 1 || index == activeIndex) && (\n\n \n {haiku.japanese.map((line, lineIndex) => (\n \n

\n {line}\n

\n

\n {haiku.english?.[lineIndex]}\n

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n setHaikus((prevHaikus) => {\n return prevHaikus.map((h, idx) => {\n if (idx === index) {\n return { ...h, selectedImage: imageName }\n } else {\n return { ...h }\n }\n })\n })}\n />\n ))}\n
\n )}\n
\n )\n ))}\n
\n \n \n );\n}\n\nfunction Thumbnails({ haikus, activeIndex, setActiveIndex, isMobile }: { haikus: Haiku[], activeIndex: number, setActiveIndex: (index: number) => void, isMobile: boolean }) {\n if (haikus.length == 0 || isMobile) { return null }\n return (\n
\n {haikus.map((haiku, index) => (\n setActiveIndex(index)}\n >\n {haiku.japanese.map((line, lineIndex) => (\n \n

{line}

\n

{haiku.english?.[lineIndex]}

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n \n ))}\n
\n )}\n \n ))}\n \n )\n\n}", + "content": "\"use client\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotKitCSSProperties, CopilotSidebar, CopilotChat } from \"@copilotkit/react-ui\";\nimport { Dispatch, SetStateAction, useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport React, { useMemo } from \"react\";\nimport { useMobileView } from \"@/utils/use-mobile-view\";\nimport { useMobileChat } from \"@/utils/use-mobile-chat\";\n\ninterface ToolBasedGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\ninterface GenerateHaiku {\n japanese: string[] | [],\n english: string[] | [],\n image_names: string[] | [],\n selectedImage: string | null,\n}\n\ninterface HaikuCardProps {\n generatedHaiku: GenerateHaiku | Partial\n setHaikus: Dispatch>\n haikus: GenerateHaiku[]\n}\n\nexport default function ToolBasedGenerativeUI({ params }: ToolBasedGenerativeUIProps) {\n const { integrationId } = React.use(params);\n const { isMobile } = useMobileView();\n\n\n const chatTitle = 'Haiku Generator'\n const chatDescription = 'Ask me to create haikus'\n const initialLabel = 'I\\'m a haiku generator 👋. How can I help you?'\n\n return (\n \n \n \n\n {/* Desktop Sidebar */}\n {!isMobile && (\n \n )}\n\n {/* Mobile Pull-Up Chat */}\n {isMobile && }\n \n \n );\n}\n\nfunction MobileChat({ chatTitle, chatDescription, initialLabel }: { chatTitle: string, chatDescription: string, initialLabel: string }) {\n const defaultChatHeight = 50\n\n const {\n isChatOpen,\n setChatHeight,\n setIsChatOpen,\n isDragging,\n chatHeight,\n handleDragStart\n } = useMobileChat(defaultChatHeight)\n return (\n <>\n {/* Chat Toggle Button */}\n
\n
\n {\n if (!isChatOpen) {\n setChatHeight(defaultChatHeight); // Reset to good default when opening\n }\n setIsChatOpen(!isChatOpen);\n }}\n >\n
\n
\n
{chatTitle}
\n
{chatDescription}
\n
\n
\n
\n \n \n \n
\n
\n \n\n {/* Pull-Up Chat Container */}\n \n {/* Drag Handle Bar */}\n \n
\n \n\n {/* Chat Header */}\n
\n
\n
\n

{chatTitle}

\n
\n setIsChatOpen(false)}\n className=\"p-2 hover:bg-gray-100 rounded-full transition-colors\"\n >\n \n \n \n \n
\n
\n\n {/* Chat Content - Flexible container for messages and input */}\n
\n \n
\n \n\n {/* Backdrop */}\n {isChatOpen && (\n setIsChatOpen(false)}\n />\n )}\n \n )\n}\n\nconst VALID_IMAGE_NAMES = [\n \"Osaka_Castle_Turret_Stone_Wall_Pine_Trees_Daytime.jpg\",\n \"Tokyo_Skyline_Night_Tokyo_Tower_Mount_Fuji_View.jpg\",\n \"Itsukushima_Shrine_Miyajima_Floating_Torii_Gate_Sunset_Long_Exposure.jpg\",\n \"Takachiho_Gorge_Waterfall_River_Lush_Greenery_Japan.jpg\",\n \"Bonsai_Tree_Potted_Japanese_Art_Green_Foliage.jpeg\",\n \"Shirakawa-go_Gassho-zukuri_Thatched_Roof_Village_Aerial_View.jpg\",\n \"Ginkaku-ji_Silver_Pavilion_Kyoto_Japanese_Garden_Pond_Reflection.jpg\",\n \"Senso-ji_Temple_Asakusa_Cherry_Blossoms_Kimono_Umbrella.jpg\",\n \"Cherry_Blossoms_Sakura_Night_View_City_Lights_Japan.jpg\",\n \"Mount_Fuji_Lake_Reflection_Cherry_Blossoms_Sakura_Spring.jpg\"\n];\n\nfunction getRandomImage(): string {\n return VALID_IMAGE_NAMES[Math.floor(Math.random() * VALID_IMAGE_NAMES.length)];\n}\n\nconst validateAndCorrectImageNames = (rawNames: string[] | undefined): string[] | null => {\n if (!rawNames || rawNames.length !== 3) {\n return null;\n }\n\n const correctedNames: string[] = [];\n const usedValidNames = new Set();\n\n for (const name of rawNames) {\n if (VALID_IMAGE_NAMES.includes(name) && !usedValidNames.has(name)) {\n correctedNames.push(name);\n usedValidNames.add(name);\n if (correctedNames.length === 3) break;\n }\n }\n\n while (correctedNames.length < 3) {\n const nextImage = getRandomImage();\n if (!usedValidNames.has(nextImage)) {\n correctedNames.push(nextImage);\n usedValidNames.add(nextImage);\n }\n }\n\n return correctedNames.slice(0, 3);\n};\n\nfunction HaikuCard({ generatedHaiku, setHaikus, haikus }: HaikuCardProps) {\n return (\n \n
\n {generatedHaiku?.japanese?.map((line, index) => (\n
\n

{line}

\n

\n {generatedHaiku.english?.[index]}\n

\n
\n ))}\n {generatedHaiku?.japanese && generatedHaiku.japanese.length >= 2 && (\n
\n {(() => {\n const firstLine = generatedHaiku?.japanese?.[0];\n if (!firstLine) return null;\n const haikuIndex = haikus.findIndex((h: any) => h.japanese[0] === firstLine);\n const haiku = haikus[haikuIndex];\n if (!haiku?.image_names) return null;\n\n return haiku.image_names.map((imageName, imgIndex) => (\n {\n setHaikus(prevHaikus => {\n const newHaikus = prevHaikus.map((h, idx) => {\n if (idx === haikuIndex) {\n return {\n ...h,\n selectedImage: imageName\n };\n }\n return h;\n });\n return newHaikus;\n });\n }}\n />\n ));\n })()}\n
\n )}\n
\n \n );\n}\n\ninterface Haiku {\n japanese: string[];\n english: string[];\n image_names: string[];\n selectedImage: string | null;\n}\n\nfunction Haiku() {\n const [haikus, setHaikus] = useState([{\n japanese: [\"仮の句よ\", \"まっさらながら\", \"花を呼ぶ\"],\n english: [\n \"A placeholder verse—\",\n \"even in a blank canvas,\",\n \"it beckons flowers.\",\n ],\n image_names: [],\n selectedImage: null,\n }])\n const [activeIndex, setActiveIndex] = useState(0);\n const [isJustApplied, setIsJustApplied] = useState(false);\n\n useCopilotAction({\n name: \"generate_haiku\",\n parameters: [\n {\n name: \"japanese\",\n type: \"string[]\",\n },\n {\n name: \"english\",\n type: \"string[]\",\n },\n {\n name: \"image_names\",\n type: \"string[]\",\n description: `Names of 3 relevant images selected from the following: \\n -${VALID_IMAGE_NAMES.join('\\n -')}`,\n },\n ],\n followUp: false,\n handler: async ({ japanese, english, image_names }: { japanese: string[], english: string[], image_names: string[] }) => {\n const finalCorrectedImages = validateAndCorrectImageNames(image_names);\n const newHaiku = {\n japanese: japanese || [],\n english: english || [],\n image_names: finalCorrectedImages || [],\n selectedImage: finalCorrectedImages?.[0] || null,\n };\n setHaikus(prev => [newHaiku, ...prev].filter(h => h.english[0] !== \"A placeholder verse—\"));\n setActiveIndex(haikus.length - 1);\n setIsJustApplied(true);\n setTimeout(() => setIsJustApplied(false), 600);\n return \"Haiku generated.\";\n },\n render: ({ args: generatedHaiku }: { args: Partial }) => {\n return (\n \n );\n },\n }, [haikus]);\n\n const { isMobile } = useMobileView();\n\n return (\n
\n \n\n {/* Main Display */}\n
\n
\n {haikus.map((haiku, index) => (\n (haikus.length == 1 || index == activeIndex) && (\n\n \n {haiku.japanese.map((line, lineIndex) => (\n \n

\n {line}\n

\n

\n {haiku.english?.[lineIndex]}\n

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n setHaikus((prevHaikus) => {\n return prevHaikus.map((h, idx) => {\n if (idx === index) {\n return { ...h, selectedImage: imageName }\n } else {\n return { ...h }\n }\n })\n })}\n />\n ))}\n
\n )}\n
\n )\n ))}\n
\n \n \n );\n}\n\nfunction Thumbnails({ haikus, activeIndex, setActiveIndex, isMobile }: { haikus: Haiku[], activeIndex: number, setActiveIndex: (index: number) => void, isMobile: boolean }) {\n if (haikus.length == 0 || isMobile) { return null }\n return (\n
\n {haikus.map((haiku, index) => (\n setActiveIndex(index)}\n >\n {haiku.japanese.map((line, lineIndex) => (\n \n

{line}

\n

{haiku.english?.[lineIndex]}

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n \n ))}\n
\n )}\n \n ))}\n \n )\n\n}", "language": "typescript", "type": "file" }, @@ -288,7 +288,7 @@ "server-starter-all-features::tool_based_generative_ui": [ { "name": "page.tsx", - "content": "\"use client\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotKitCSSProperties, CopilotSidebar, CopilotChat } from \"@copilotkit/react-ui\";\nimport { Dispatch, SetStateAction, useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport React, { useMemo } from \"react\";\nimport { useMobileView } from \"@/utils/use-mobile-view\";\nimport { useMobileChat } from \"@/utils/use-mobile-chat\";\n\ninterface ToolBasedGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\ninterface GenerateHaiku {\n japanese: string[] | [],\n english: string[] | [],\n image_names: string[] | [],\n selectedImage: string | null,\n}\n\ninterface HaikuCardProps {\n generatedHaiku: GenerateHaiku | Partial\n setHaikus: Dispatch>\n haikus: GenerateHaiku[]\n}\n\nexport default function ToolBasedGenerativeUI({ params }: ToolBasedGenerativeUIProps) {\n const { integrationId } = React.use(params);\n const { isMobile } = useMobileView();\n\n\n const chatTitle = 'Haiku Generator'\n const chatDescription = 'Ask me to create haikus'\n const initialLabel = 'I\\'m a haiku generator 👋. How can I help you?'\n\n return (\n \n \n \n\n {/* Desktop Sidebar */}\n {!isMobile && (\n \n )}\n\n {/* Mobile Pull-Up Chat */}\n {isMobile && }\n \n \n );\n}\n\nfunction MobileChat({ chatTitle, chatDescription, initialLabel }: { chatTitle: string, chatDescription: string, initialLabel: string }) {\n const defaultChatHeight = 50\n\n const {\n isChatOpen,\n setChatHeight,\n setIsChatOpen,\n isDragging,\n chatHeight,\n handleDragStart\n } = useMobileChat(defaultChatHeight)\n return (\n <>\n {/* Chat Toggle Button */}\n
\n
\n {\n if (!isChatOpen) {\n setChatHeight(defaultChatHeight); // Reset to good default when opening\n }\n setIsChatOpen(!isChatOpen);\n }}\n >\n
\n
\n
{chatTitle}
\n
{chatDescription}
\n
\n
\n
\n \n \n \n
\n
\n \n\n {/* Pull-Up Chat Container */}\n \n {/* Drag Handle Bar */}\n \n
\n \n\n {/* Chat Header */}\n
\n
\n
\n

{chatTitle}

\n
\n setIsChatOpen(false)}\n className=\"p-2 hover:bg-gray-100 rounded-full transition-colors\"\n >\n \n \n \n \n
\n
\n\n {/* Chat Content - Flexible container for messages and input */}\n
\n \n
\n \n\n {/* Backdrop */}\n {isChatOpen && (\n setIsChatOpen(false)}\n />\n )}\n \n )\n}\n\nconst VALID_IMAGE_NAMES = [\n \"Osaka_Castle_Turret_Stone_Wall_Pine_Trees_Daytime.jpg\",\n \"Tokyo_Skyline_Night_Tokyo_Tower_Mount_Fuji_View.jpg\",\n \"Itsukushima_Shrine_Miyajima_Floating_Torii_Gate_Sunset_Long_Exposure.jpg\",\n \"Takachiho_Gorge_Waterfall_River_Lush_Greenery_Japan.jpg\",\n \"Bonsai_Tree_Potted_Japanese_Art_Green_Foliage.jpeg\",\n \"Shirakawa-go_Gassho-zukuri_Thatched_Roof_Village_Aerial_View.jpg\",\n \"Ginkaku-ji_Silver_Pavilion_Kyoto_Japanese_Garden_Pond_Reflection.jpg\",\n \"Senso-ji_Temple_Asakusa_Cherry_Blossoms_Kimono_Umbrella.jpg\",\n \"Cherry_Blossoms_Sakura_Night_View_City_Lights_Japan.jpg\",\n \"Mount_Fuji_Lake_Reflection_Cherry_Blossoms_Sakura_Spring.jpg\"\n];\n\nfunction getRandomImage(): string {\n return VALID_IMAGE_NAMES[Math.floor(Math.random() * VALID_IMAGE_NAMES.length)];\n}\n\nconst validateAndCorrectImageNames = (rawNames: string[] | undefined): string[] | null => {\n if (!rawNames || rawNames.length !== 3) {\n return null;\n }\n\n const correctedNames: string[] = [];\n const usedValidNames = new Set();\n\n for (const name of rawNames) {\n if (VALID_IMAGE_NAMES.includes(name) && !usedValidNames.has(name)) {\n correctedNames.push(name);\n usedValidNames.add(name);\n if (correctedNames.length === 3) break;\n }\n }\n\n while (correctedNames.length < 3) {\n const nextImage = getRandomImage();\n if (!usedValidNames.has(nextImage)) {\n correctedNames.push(nextImage);\n usedValidNames.add(nextImage);\n }\n }\n\n return correctedNames.slice(0, 3);\n};\n\nfunction HaikuCard({ generatedHaiku, setHaikus, haikus }: HaikuCardProps) {\n return (\n \n
\n {generatedHaiku?.japanese?.map((line, index) => (\n
\n

{line}

\n

\n {generatedHaiku.english?.[index]}\n

\n
\n ))}\n {generatedHaiku?.japanese && generatedHaiku.japanese.length >= 2 && (\n
\n {(() => {\n const firstLine = generatedHaiku?.japanese?.[0];\n if (!firstLine) return null;\n const haikuIndex = haikus.findIndex((h: any) => h.japanese[0] === firstLine);\n const haiku = haikus[haikuIndex];\n if (!haiku?.image_names) return null;\n\n return haiku.image_names.map((imageName, imgIndex) => (\n {\n setHaikus(prevHaikus => {\n const newHaikus = prevHaikus.map((h, idx) => {\n if (idx === haikuIndex) {\n return {\n ...h,\n selectedImage: imageName\n };\n }\n return h;\n });\n return newHaikus;\n });\n }}\n />\n ));\n })()}\n
\n )}\n
\n \n );\n}\n\ninterface Haiku {\n japanese: string[];\n english: string[];\n image_names: string[];\n selectedImage: string | null;\n}\n\nfunction Haiku() {\n const [haikus, setHaikus] = useState([{\n japanese: [\"仮の句よ\", \"まっさらながら\", \"花を呼ぶ\"],\n english: [\n \"A placeholder verse—\",\n \"even in a blank canvas,\",\n \"it beckons flowers.\",\n ],\n image_names: [],\n selectedImage: null,\n }])\n const [activeIndex, setActiveIndex] = useState(0);\n const [isJustApplied, setIsJustApplied] = useState(false);\n\n useCopilotAction({\n name: \"generate_haiku\",\n parameters: [\n {\n name: \"japanese\",\n type: \"string[]\",\n },\n {\n name: \"english\",\n type: \"string[]\",\n },\n {\n name: \"image_names\",\n type: \"string[]\",\n description: `Names of 3 relevant images selected from the following: \\n -${VALID_IMAGE_NAMES.join('\\n -')}`,\n },\n ],\n followUp: false,\n handler: async ({ japanese, english, image_names }: { japanese: string[], english: string[], image_names: string[] }) => {\n const finalCorrectedImages = validateAndCorrectImageNames(image_names);\n const newHaiku = {\n japanese: japanese || [],\n english: english || [],\n image_names: finalCorrectedImages || [],\n selectedImage: finalCorrectedImages?.[0] || null,\n };\n setHaikus(prev => [...prev, newHaiku].filter(h => h.english[0] !== \"A placeholder verse—\"));\n setActiveIndex(haikus.length - 1);\n setIsJustApplied(true);\n setTimeout(() => setIsJustApplied(false), 600);\n return \"Haiku generated.\";\n },\n render: ({ args: generatedHaiku }: { args: Partial }) => {\n return (\n \n );\n },\n }, [haikus]);\n\n const { isMobile } = useMobileView();\n\n return (\n
\n \n\n {/* Main Display */}\n
\n
\n {haikus.map((haiku, index) => (\n (haikus.length == 1 || index == activeIndex) && (\n\n \n {haiku.japanese.map((line, lineIndex) => (\n \n

\n {line}\n

\n

\n {haiku.english?.[lineIndex]}\n

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n setHaikus((prevHaikus) => {\n return prevHaikus.map((h, idx) => {\n if (idx === index) {\n return { ...h, selectedImage: imageName }\n } else {\n return { ...h }\n }\n })\n })}\n />\n ))}\n
\n )}\n
\n )\n ))}\n
\n \n \n );\n}\n\nfunction Thumbnails({ haikus, activeIndex, setActiveIndex, isMobile }: { haikus: Haiku[], activeIndex: number, setActiveIndex: (index: number) => void, isMobile: boolean }) {\n if (haikus.length == 0 || isMobile) { return null }\n return (\n
\n {haikus.map((haiku, index) => (\n setActiveIndex(index)}\n >\n {haiku.japanese.map((line, lineIndex) => (\n \n

{line}

\n

{haiku.english?.[lineIndex]}

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n \n ))}\n
\n )}\n \n ))}\n \n )\n\n}", + "content": "\"use client\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotKitCSSProperties, CopilotSidebar, CopilotChat } from \"@copilotkit/react-ui\";\nimport { Dispatch, SetStateAction, useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport React, { useMemo } from \"react\";\nimport { useMobileView } from \"@/utils/use-mobile-view\";\nimport { useMobileChat } from \"@/utils/use-mobile-chat\";\n\ninterface ToolBasedGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\ninterface GenerateHaiku {\n japanese: string[] | [],\n english: string[] | [],\n image_names: string[] | [],\n selectedImage: string | null,\n}\n\ninterface HaikuCardProps {\n generatedHaiku: GenerateHaiku | Partial\n setHaikus: Dispatch>\n haikus: GenerateHaiku[]\n}\n\nexport default function ToolBasedGenerativeUI({ params }: ToolBasedGenerativeUIProps) {\n const { integrationId } = React.use(params);\n const { isMobile } = useMobileView();\n\n\n const chatTitle = 'Haiku Generator'\n const chatDescription = 'Ask me to create haikus'\n const initialLabel = 'I\\'m a haiku generator 👋. How can I help you?'\n\n return (\n \n \n \n\n {/* Desktop Sidebar */}\n {!isMobile && (\n \n )}\n\n {/* Mobile Pull-Up Chat */}\n {isMobile && }\n \n \n );\n}\n\nfunction MobileChat({ chatTitle, chatDescription, initialLabel }: { chatTitle: string, chatDescription: string, initialLabel: string }) {\n const defaultChatHeight = 50\n\n const {\n isChatOpen,\n setChatHeight,\n setIsChatOpen,\n isDragging,\n chatHeight,\n handleDragStart\n } = useMobileChat(defaultChatHeight)\n return (\n <>\n {/* Chat Toggle Button */}\n
\n
\n {\n if (!isChatOpen) {\n setChatHeight(defaultChatHeight); // Reset to good default when opening\n }\n setIsChatOpen(!isChatOpen);\n }}\n >\n
\n
\n
{chatTitle}
\n
{chatDescription}
\n
\n
\n
\n \n \n \n
\n
\n \n\n {/* Pull-Up Chat Container */}\n \n {/* Drag Handle Bar */}\n \n
\n \n\n {/* Chat Header */}\n
\n
\n
\n

{chatTitle}

\n
\n setIsChatOpen(false)}\n className=\"p-2 hover:bg-gray-100 rounded-full transition-colors\"\n >\n \n \n \n \n
\n
\n\n {/* Chat Content - Flexible container for messages and input */}\n
\n \n
\n \n\n {/* Backdrop */}\n {isChatOpen && (\n setIsChatOpen(false)}\n />\n )}\n \n )\n}\n\nconst VALID_IMAGE_NAMES = [\n \"Osaka_Castle_Turret_Stone_Wall_Pine_Trees_Daytime.jpg\",\n \"Tokyo_Skyline_Night_Tokyo_Tower_Mount_Fuji_View.jpg\",\n \"Itsukushima_Shrine_Miyajima_Floating_Torii_Gate_Sunset_Long_Exposure.jpg\",\n \"Takachiho_Gorge_Waterfall_River_Lush_Greenery_Japan.jpg\",\n \"Bonsai_Tree_Potted_Japanese_Art_Green_Foliage.jpeg\",\n \"Shirakawa-go_Gassho-zukuri_Thatched_Roof_Village_Aerial_View.jpg\",\n \"Ginkaku-ji_Silver_Pavilion_Kyoto_Japanese_Garden_Pond_Reflection.jpg\",\n \"Senso-ji_Temple_Asakusa_Cherry_Blossoms_Kimono_Umbrella.jpg\",\n \"Cherry_Blossoms_Sakura_Night_View_City_Lights_Japan.jpg\",\n \"Mount_Fuji_Lake_Reflection_Cherry_Blossoms_Sakura_Spring.jpg\"\n];\n\nfunction getRandomImage(): string {\n return VALID_IMAGE_NAMES[Math.floor(Math.random() * VALID_IMAGE_NAMES.length)];\n}\n\nconst validateAndCorrectImageNames = (rawNames: string[] | undefined): string[] | null => {\n if (!rawNames || rawNames.length !== 3) {\n return null;\n }\n\n const correctedNames: string[] = [];\n const usedValidNames = new Set();\n\n for (const name of rawNames) {\n if (VALID_IMAGE_NAMES.includes(name) && !usedValidNames.has(name)) {\n correctedNames.push(name);\n usedValidNames.add(name);\n if (correctedNames.length === 3) break;\n }\n }\n\n while (correctedNames.length < 3) {\n const nextImage = getRandomImage();\n if (!usedValidNames.has(nextImage)) {\n correctedNames.push(nextImage);\n usedValidNames.add(nextImage);\n }\n }\n\n return correctedNames.slice(0, 3);\n};\n\nfunction HaikuCard({ generatedHaiku, setHaikus, haikus }: HaikuCardProps) {\n return (\n \n
\n {generatedHaiku?.japanese?.map((line, index) => (\n
\n

{line}

\n

\n {generatedHaiku.english?.[index]}\n

\n
\n ))}\n {generatedHaiku?.japanese && generatedHaiku.japanese.length >= 2 && (\n
\n {(() => {\n const firstLine = generatedHaiku?.japanese?.[0];\n if (!firstLine) return null;\n const haikuIndex = haikus.findIndex((h: any) => h.japanese[0] === firstLine);\n const haiku = haikus[haikuIndex];\n if (!haiku?.image_names) return null;\n\n return haiku.image_names.map((imageName, imgIndex) => (\n {\n setHaikus(prevHaikus => {\n const newHaikus = prevHaikus.map((h, idx) => {\n if (idx === haikuIndex) {\n return {\n ...h,\n selectedImage: imageName\n };\n }\n return h;\n });\n return newHaikus;\n });\n }}\n />\n ));\n })()}\n
\n )}\n
\n \n );\n}\n\ninterface Haiku {\n japanese: string[];\n english: string[];\n image_names: string[];\n selectedImage: string | null;\n}\n\nfunction Haiku() {\n const [haikus, setHaikus] = useState([{\n japanese: [\"仮の句よ\", \"まっさらながら\", \"花を呼ぶ\"],\n english: [\n \"A placeholder verse—\",\n \"even in a blank canvas,\",\n \"it beckons flowers.\",\n ],\n image_names: [],\n selectedImage: null,\n }])\n const [activeIndex, setActiveIndex] = useState(0);\n const [isJustApplied, setIsJustApplied] = useState(false);\n\n useCopilotAction({\n name: \"generate_haiku\",\n parameters: [\n {\n name: \"japanese\",\n type: \"string[]\",\n },\n {\n name: \"english\",\n type: \"string[]\",\n },\n {\n name: \"image_names\",\n type: \"string[]\",\n description: `Names of 3 relevant images selected from the following: \\n -${VALID_IMAGE_NAMES.join('\\n -')}`,\n },\n ],\n followUp: false,\n handler: async ({ japanese, english, image_names }: { japanese: string[], english: string[], image_names: string[] }) => {\n const finalCorrectedImages = validateAndCorrectImageNames(image_names);\n const newHaiku = {\n japanese: japanese || [],\n english: english || [],\n image_names: finalCorrectedImages || [],\n selectedImage: finalCorrectedImages?.[0] || null,\n };\n setHaikus(prev => [newHaiku, ...prev].filter(h => h.english[0] !== \"A placeholder verse—\"));\n setActiveIndex(haikus.length - 1);\n setIsJustApplied(true);\n setTimeout(() => setIsJustApplied(false), 600);\n return \"Haiku generated.\";\n },\n render: ({ args: generatedHaiku }: { args: Partial }) => {\n return (\n \n );\n },\n }, [haikus]);\n\n const { isMobile } = useMobileView();\n\n return (\n
\n \n\n {/* Main Display */}\n
\n
\n {haikus.map((haiku, index) => (\n (haikus.length == 1 || index == activeIndex) && (\n\n \n {haiku.japanese.map((line, lineIndex) => (\n \n

\n {line}\n

\n

\n {haiku.english?.[lineIndex]}\n

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n setHaikus((prevHaikus) => {\n return prevHaikus.map((h, idx) => {\n if (idx === index) {\n return { ...h, selectedImage: imageName }\n } else {\n return { ...h }\n }\n })\n })}\n />\n ))}\n
\n )}\n
\n )\n ))}\n
\n \n \n );\n}\n\nfunction Thumbnails({ haikus, activeIndex, setActiveIndex, isMobile }: { haikus: Haiku[], activeIndex: number, setActiveIndex: (index: number) => void, isMobile: boolean }) {\n if (haikus.length == 0 || isMobile) { return null }\n return (\n
\n {haikus.map((haiku, index) => (\n setActiveIndex(index)}\n >\n {haiku.japanese.map((line, lineIndex) => (\n \n

{line}

\n

{haiku.english?.[lineIndex]}

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n \n ))}\n
\n )}\n \n ))}\n \n )\n\n}", "language": "typescript", "type": "file" }, @@ -552,7 +552,7 @@ "langgraph::tool_based_generative_ui": [ { "name": "page.tsx", - "content": "\"use client\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotKitCSSProperties, CopilotSidebar, CopilotChat } from \"@copilotkit/react-ui\";\nimport { Dispatch, SetStateAction, useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport React, { useMemo } from \"react\";\nimport { useMobileView } from \"@/utils/use-mobile-view\";\nimport { useMobileChat } from \"@/utils/use-mobile-chat\";\n\ninterface ToolBasedGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\ninterface GenerateHaiku {\n japanese: string[] | [],\n english: string[] | [],\n image_names: string[] | [],\n selectedImage: string | null,\n}\n\ninterface HaikuCardProps {\n generatedHaiku: GenerateHaiku | Partial\n setHaikus: Dispatch>\n haikus: GenerateHaiku[]\n}\n\nexport default function ToolBasedGenerativeUI({ params }: ToolBasedGenerativeUIProps) {\n const { integrationId } = React.use(params);\n const { isMobile } = useMobileView();\n\n\n const chatTitle = 'Haiku Generator'\n const chatDescription = 'Ask me to create haikus'\n const initialLabel = 'I\\'m a haiku generator 👋. How can I help you?'\n\n return (\n \n \n \n\n {/* Desktop Sidebar */}\n {!isMobile && (\n \n )}\n\n {/* Mobile Pull-Up Chat */}\n {isMobile && }\n \n \n );\n}\n\nfunction MobileChat({ chatTitle, chatDescription, initialLabel }: { chatTitle: string, chatDescription: string, initialLabel: string }) {\n const defaultChatHeight = 50\n\n const {\n isChatOpen,\n setChatHeight,\n setIsChatOpen,\n isDragging,\n chatHeight,\n handleDragStart\n } = useMobileChat(defaultChatHeight)\n return (\n <>\n {/* Chat Toggle Button */}\n
\n
\n {\n if (!isChatOpen) {\n setChatHeight(defaultChatHeight); // Reset to good default when opening\n }\n setIsChatOpen(!isChatOpen);\n }}\n >\n
\n
\n
{chatTitle}
\n
{chatDescription}
\n
\n
\n
\n \n \n \n
\n
\n \n\n {/* Pull-Up Chat Container */}\n \n {/* Drag Handle Bar */}\n \n
\n \n\n {/* Chat Header */}\n
\n
\n
\n

{chatTitle}

\n
\n setIsChatOpen(false)}\n className=\"p-2 hover:bg-gray-100 rounded-full transition-colors\"\n >\n \n \n \n \n
\n
\n\n {/* Chat Content - Flexible container for messages and input */}\n
\n \n
\n \n\n {/* Backdrop */}\n {isChatOpen && (\n setIsChatOpen(false)}\n />\n )}\n \n )\n}\n\nconst VALID_IMAGE_NAMES = [\n \"Osaka_Castle_Turret_Stone_Wall_Pine_Trees_Daytime.jpg\",\n \"Tokyo_Skyline_Night_Tokyo_Tower_Mount_Fuji_View.jpg\",\n \"Itsukushima_Shrine_Miyajima_Floating_Torii_Gate_Sunset_Long_Exposure.jpg\",\n \"Takachiho_Gorge_Waterfall_River_Lush_Greenery_Japan.jpg\",\n \"Bonsai_Tree_Potted_Japanese_Art_Green_Foliage.jpeg\",\n \"Shirakawa-go_Gassho-zukuri_Thatched_Roof_Village_Aerial_View.jpg\",\n \"Ginkaku-ji_Silver_Pavilion_Kyoto_Japanese_Garden_Pond_Reflection.jpg\",\n \"Senso-ji_Temple_Asakusa_Cherry_Blossoms_Kimono_Umbrella.jpg\",\n \"Cherry_Blossoms_Sakura_Night_View_City_Lights_Japan.jpg\",\n \"Mount_Fuji_Lake_Reflection_Cherry_Blossoms_Sakura_Spring.jpg\"\n];\n\nfunction getRandomImage(): string {\n return VALID_IMAGE_NAMES[Math.floor(Math.random() * VALID_IMAGE_NAMES.length)];\n}\n\nconst validateAndCorrectImageNames = (rawNames: string[] | undefined): string[] | null => {\n if (!rawNames || rawNames.length !== 3) {\n return null;\n }\n\n const correctedNames: string[] = [];\n const usedValidNames = new Set();\n\n for (const name of rawNames) {\n if (VALID_IMAGE_NAMES.includes(name) && !usedValidNames.has(name)) {\n correctedNames.push(name);\n usedValidNames.add(name);\n if (correctedNames.length === 3) break;\n }\n }\n\n while (correctedNames.length < 3) {\n const nextImage = getRandomImage();\n if (!usedValidNames.has(nextImage)) {\n correctedNames.push(nextImage);\n usedValidNames.add(nextImage);\n }\n }\n\n return correctedNames.slice(0, 3);\n};\n\nfunction HaikuCard({ generatedHaiku, setHaikus, haikus }: HaikuCardProps) {\n return (\n \n
\n {generatedHaiku?.japanese?.map((line, index) => (\n
\n

{line}

\n

\n {generatedHaiku.english?.[index]}\n

\n
\n ))}\n {generatedHaiku?.japanese && generatedHaiku.japanese.length >= 2 && (\n
\n {(() => {\n const firstLine = generatedHaiku?.japanese?.[0];\n if (!firstLine) return null;\n const haikuIndex = haikus.findIndex((h: any) => h.japanese[0] === firstLine);\n const haiku = haikus[haikuIndex];\n if (!haiku?.image_names) return null;\n\n return haiku.image_names.map((imageName, imgIndex) => (\n {\n setHaikus(prevHaikus => {\n const newHaikus = prevHaikus.map((h, idx) => {\n if (idx === haikuIndex) {\n return {\n ...h,\n selectedImage: imageName\n };\n }\n return h;\n });\n return newHaikus;\n });\n }}\n />\n ));\n })()}\n
\n )}\n
\n \n );\n}\n\ninterface Haiku {\n japanese: string[];\n english: string[];\n image_names: string[];\n selectedImage: string | null;\n}\n\nfunction Haiku() {\n const [haikus, setHaikus] = useState([{\n japanese: [\"仮の句よ\", \"まっさらながら\", \"花を呼ぶ\"],\n english: [\n \"A placeholder verse—\",\n \"even in a blank canvas,\",\n \"it beckons flowers.\",\n ],\n image_names: [],\n selectedImage: null,\n }])\n const [activeIndex, setActiveIndex] = useState(0);\n const [isJustApplied, setIsJustApplied] = useState(false);\n\n useCopilotAction({\n name: \"generate_haiku\",\n parameters: [\n {\n name: \"japanese\",\n type: \"string[]\",\n },\n {\n name: \"english\",\n type: \"string[]\",\n },\n {\n name: \"image_names\",\n type: \"string[]\",\n description: `Names of 3 relevant images selected from the following: \\n -${VALID_IMAGE_NAMES.join('\\n -')}`,\n },\n ],\n followUp: false,\n handler: async ({ japanese, english, image_names }: { japanese: string[], english: string[], image_names: string[] }) => {\n const finalCorrectedImages = validateAndCorrectImageNames(image_names);\n const newHaiku = {\n japanese: japanese || [],\n english: english || [],\n image_names: finalCorrectedImages || [],\n selectedImage: finalCorrectedImages?.[0] || null,\n };\n setHaikus(prev => [...prev, newHaiku].filter(h => h.english[0] !== \"A placeholder verse—\"));\n setActiveIndex(haikus.length - 1);\n setIsJustApplied(true);\n setTimeout(() => setIsJustApplied(false), 600);\n return \"Haiku generated.\";\n },\n render: ({ args: generatedHaiku }: { args: Partial }) => {\n return (\n \n );\n },\n }, [haikus]);\n\n const { isMobile } = useMobileView();\n\n return (\n
\n \n\n {/* Main Display */}\n
\n
\n {haikus.map((haiku, index) => (\n (haikus.length == 1 || index == activeIndex) && (\n\n \n {haiku.japanese.map((line, lineIndex) => (\n \n

\n {line}\n

\n

\n {haiku.english?.[lineIndex]}\n

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n setHaikus((prevHaikus) => {\n return prevHaikus.map((h, idx) => {\n if (idx === index) {\n return { ...h, selectedImage: imageName }\n } else {\n return { ...h }\n }\n })\n })}\n />\n ))}\n
\n )}\n
\n )\n ))}\n
\n \n \n );\n}\n\nfunction Thumbnails({ haikus, activeIndex, setActiveIndex, isMobile }: { haikus: Haiku[], activeIndex: number, setActiveIndex: (index: number) => void, isMobile: boolean }) {\n if (haikus.length == 0 || isMobile) { return null }\n return (\n
\n {haikus.map((haiku, index) => (\n setActiveIndex(index)}\n >\n {haiku.japanese.map((line, lineIndex) => (\n \n

{line}

\n

{haiku.english?.[lineIndex]}

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n \n ))}\n
\n )}\n \n ))}\n \n )\n\n}", + "content": "\"use client\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotKitCSSProperties, CopilotSidebar, CopilotChat } from \"@copilotkit/react-ui\";\nimport { Dispatch, SetStateAction, useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport React, { useMemo } from \"react\";\nimport { useMobileView } from \"@/utils/use-mobile-view\";\nimport { useMobileChat } from \"@/utils/use-mobile-chat\";\n\ninterface ToolBasedGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\ninterface GenerateHaiku {\n japanese: string[] | [],\n english: string[] | [],\n image_names: string[] | [],\n selectedImage: string | null,\n}\n\ninterface HaikuCardProps {\n generatedHaiku: GenerateHaiku | Partial\n setHaikus: Dispatch>\n haikus: GenerateHaiku[]\n}\n\nexport default function ToolBasedGenerativeUI({ params }: ToolBasedGenerativeUIProps) {\n const { integrationId } = React.use(params);\n const { isMobile } = useMobileView();\n\n\n const chatTitle = 'Haiku Generator'\n const chatDescription = 'Ask me to create haikus'\n const initialLabel = 'I\\'m a haiku generator 👋. How can I help you?'\n\n return (\n \n \n \n\n {/* Desktop Sidebar */}\n {!isMobile && (\n \n )}\n\n {/* Mobile Pull-Up Chat */}\n {isMobile && }\n \n \n );\n}\n\nfunction MobileChat({ chatTitle, chatDescription, initialLabel }: { chatTitle: string, chatDescription: string, initialLabel: string }) {\n const defaultChatHeight = 50\n\n const {\n isChatOpen,\n setChatHeight,\n setIsChatOpen,\n isDragging,\n chatHeight,\n handleDragStart\n } = useMobileChat(defaultChatHeight)\n return (\n <>\n {/* Chat Toggle Button */}\n
\n
\n {\n if (!isChatOpen) {\n setChatHeight(defaultChatHeight); // Reset to good default when opening\n }\n setIsChatOpen(!isChatOpen);\n }}\n >\n
\n
\n
{chatTitle}
\n
{chatDescription}
\n
\n
\n
\n \n \n \n
\n
\n \n\n {/* Pull-Up Chat Container */}\n \n {/* Drag Handle Bar */}\n \n
\n \n\n {/* Chat Header */}\n
\n
\n
\n

{chatTitle}

\n
\n setIsChatOpen(false)}\n className=\"p-2 hover:bg-gray-100 rounded-full transition-colors\"\n >\n \n \n \n \n
\n
\n\n {/* Chat Content - Flexible container for messages and input */}\n
\n \n
\n \n\n {/* Backdrop */}\n {isChatOpen && (\n setIsChatOpen(false)}\n />\n )}\n \n )\n}\n\nconst VALID_IMAGE_NAMES = [\n \"Osaka_Castle_Turret_Stone_Wall_Pine_Trees_Daytime.jpg\",\n \"Tokyo_Skyline_Night_Tokyo_Tower_Mount_Fuji_View.jpg\",\n \"Itsukushima_Shrine_Miyajima_Floating_Torii_Gate_Sunset_Long_Exposure.jpg\",\n \"Takachiho_Gorge_Waterfall_River_Lush_Greenery_Japan.jpg\",\n \"Bonsai_Tree_Potted_Japanese_Art_Green_Foliage.jpeg\",\n \"Shirakawa-go_Gassho-zukuri_Thatched_Roof_Village_Aerial_View.jpg\",\n \"Ginkaku-ji_Silver_Pavilion_Kyoto_Japanese_Garden_Pond_Reflection.jpg\",\n \"Senso-ji_Temple_Asakusa_Cherry_Blossoms_Kimono_Umbrella.jpg\",\n \"Cherry_Blossoms_Sakura_Night_View_City_Lights_Japan.jpg\",\n \"Mount_Fuji_Lake_Reflection_Cherry_Blossoms_Sakura_Spring.jpg\"\n];\n\nfunction getRandomImage(): string {\n return VALID_IMAGE_NAMES[Math.floor(Math.random() * VALID_IMAGE_NAMES.length)];\n}\n\nconst validateAndCorrectImageNames = (rawNames: string[] | undefined): string[] | null => {\n if (!rawNames || rawNames.length !== 3) {\n return null;\n }\n\n const correctedNames: string[] = [];\n const usedValidNames = new Set();\n\n for (const name of rawNames) {\n if (VALID_IMAGE_NAMES.includes(name) && !usedValidNames.has(name)) {\n correctedNames.push(name);\n usedValidNames.add(name);\n if (correctedNames.length === 3) break;\n }\n }\n\n while (correctedNames.length < 3) {\n const nextImage = getRandomImage();\n if (!usedValidNames.has(nextImage)) {\n correctedNames.push(nextImage);\n usedValidNames.add(nextImage);\n }\n }\n\n return correctedNames.slice(0, 3);\n};\n\nfunction HaikuCard({ generatedHaiku, setHaikus, haikus }: HaikuCardProps) {\n return (\n \n
\n {generatedHaiku?.japanese?.map((line, index) => (\n
\n

{line}

\n

\n {generatedHaiku.english?.[index]}\n

\n
\n ))}\n {generatedHaiku?.japanese && generatedHaiku.japanese.length >= 2 && (\n
\n {(() => {\n const firstLine = generatedHaiku?.japanese?.[0];\n if (!firstLine) return null;\n const haikuIndex = haikus.findIndex((h: any) => h.japanese[0] === firstLine);\n const haiku = haikus[haikuIndex];\n if (!haiku?.image_names) return null;\n\n return haiku.image_names.map((imageName, imgIndex) => (\n {\n setHaikus(prevHaikus => {\n const newHaikus = prevHaikus.map((h, idx) => {\n if (idx === haikuIndex) {\n return {\n ...h,\n selectedImage: imageName\n };\n }\n return h;\n });\n return newHaikus;\n });\n }}\n />\n ));\n })()}\n
\n )}\n
\n \n );\n}\n\ninterface Haiku {\n japanese: string[];\n english: string[];\n image_names: string[];\n selectedImage: string | null;\n}\n\nfunction Haiku() {\n const [haikus, setHaikus] = useState([{\n japanese: [\"仮の句よ\", \"まっさらながら\", \"花を呼ぶ\"],\n english: [\n \"A placeholder verse—\",\n \"even in a blank canvas,\",\n \"it beckons flowers.\",\n ],\n image_names: [],\n selectedImage: null,\n }])\n const [activeIndex, setActiveIndex] = useState(0);\n const [isJustApplied, setIsJustApplied] = useState(false);\n\n useCopilotAction({\n name: \"generate_haiku\",\n parameters: [\n {\n name: \"japanese\",\n type: \"string[]\",\n },\n {\n name: \"english\",\n type: \"string[]\",\n },\n {\n name: \"image_names\",\n type: \"string[]\",\n description: `Names of 3 relevant images selected from the following: \\n -${VALID_IMAGE_NAMES.join('\\n -')}`,\n },\n ],\n followUp: false,\n handler: async ({ japanese, english, image_names }: { japanese: string[], english: string[], image_names: string[] }) => {\n const finalCorrectedImages = validateAndCorrectImageNames(image_names);\n const newHaiku = {\n japanese: japanese || [],\n english: english || [],\n image_names: finalCorrectedImages || [],\n selectedImage: finalCorrectedImages?.[0] || null,\n };\n setHaikus(prev => [newHaiku, ...prev].filter(h => h.english[0] !== \"A placeholder verse—\"));\n setActiveIndex(haikus.length - 1);\n setIsJustApplied(true);\n setTimeout(() => setIsJustApplied(false), 600);\n return \"Haiku generated.\";\n },\n render: ({ args: generatedHaiku }: { args: Partial }) => {\n return (\n \n );\n },\n }, [haikus]);\n\n const { isMobile } = useMobileView();\n\n return (\n
\n \n\n {/* Main Display */}\n
\n
\n {haikus.map((haiku, index) => (\n (haikus.length == 1 || index == activeIndex) && (\n\n \n {haiku.japanese.map((line, lineIndex) => (\n \n

\n {line}\n

\n

\n {haiku.english?.[lineIndex]}\n

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n setHaikus((prevHaikus) => {\n return prevHaikus.map((h, idx) => {\n if (idx === index) {\n return { ...h, selectedImage: imageName }\n } else {\n return { ...h }\n }\n })\n })}\n />\n ))}\n
\n )}\n
\n )\n ))}\n
\n \n \n );\n}\n\nfunction Thumbnails({ haikus, activeIndex, setActiveIndex, isMobile }: { haikus: Haiku[], activeIndex: number, setActiveIndex: (index: number) => void, isMobile: boolean }) {\n if (haikus.length == 0 || isMobile) { return null }\n return (\n
\n {haikus.map((haiku, index) => (\n setActiveIndex(index)}\n >\n {haiku.japanese.map((line, lineIndex) => (\n \n

{line}

\n

{haiku.english?.[lineIndex]}

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n \n ))}\n
\n )}\n \n ))}\n \n )\n\n}", "language": "typescript", "type": "file" }, @@ -570,7 +570,7 @@ }, { "name": "agent.py", - "content": "\"\"\"\nAn example demonstrating tool-based generative UI using LangGraph.\n\"\"\"\n\nfrom typing import Any, List\nfrom typing_extensions import Literal\nfrom langchain_openai import ChatOpenAI\nfrom langchain_core.messages import SystemMessage\nfrom langchain_core.runnables import RunnableConfig\nfrom langgraph.graph import StateGraph, END\nfrom langgraph.types import Command\nfrom langgraph.graph import MessagesState\nfrom langgraph.prebuilt import ToolNode\n\n\nclass AgentState(MessagesState):\n \"\"\"\n State of the agent.\n \"\"\"\n tools: List[Any]\n\nasync def chat_node(state: AgentState, config: RunnableConfig) -> Command[Literal[\"tool_node\", \"__end__\"]]:\n \"\"\"\n Standard chat node based on the ReAct design pattern. It handles:\n - The model to use (and binds in CopilotKit actions and the tools defined above)\n - The system prompt\n - Getting a response from the model\n - Handling tool calls\n\n For more about the ReAct design pattern, see:\n https://www.perplexity.ai/search/react-agents-NcXLQhreS0WDzpVaS4m9Cg\n \"\"\"\n\n model = ChatOpenAI(model=\"gpt-4o\")\n\n model_with_tools = model.bind_tools(\n [\n *state.get(\"tools\", []), # bind tools defined by ag-ui\n ],\n parallel_tool_calls=False,\n )\n\n system_message = SystemMessage(\n content=f\"Help the user with writing Haikus. If the user asks for a haiku, use the generate_haiku tool to display the haiku to the user.\"\n )\n\n response = await model_with_tools.ainvoke([\n system_message,\n *state[\"messages\"],\n ], config)\n\n return Command(\n goto=END,\n update={\n \"messages\": [response],\n }\n )\n\nworkflow = StateGraph(AgentState)\nworkflow.add_node(\"chat_node\", chat_node)\n# This is required even though we don't have any backend tools to pass in.\nworkflow.add_node(\"tool_node\", ToolNode(tools=[]))\nworkflow.set_entry_point(\"chat_node\")\n\ngraph = workflow.compile()", + "content": "\"\"\"\nAn example demonstrating tool-based generative UI using LangGraph.\n\"\"\"\n\nimport os\nfrom typing import Any, List\nfrom typing_extensions import Literal\nfrom langchain_openai import ChatOpenAI\nfrom langchain_core.messages import SystemMessage\nfrom langchain_core.runnables import RunnableConfig\nfrom langgraph.graph import StateGraph, END\nfrom langgraph.types import Command\nfrom langgraph.graph import MessagesState\nfrom langgraph.prebuilt import ToolNode\n\n\nclass AgentState(MessagesState):\n \"\"\"\n State of the agent.\n \"\"\"\n tools: List[Any]\n\nasync def chat_node(state: AgentState, config: RunnableConfig) -> Command[Literal[\"tool_node\", \"__end__\"]]:\n \"\"\"\n Standard chat node based on the ReAct design pattern. It handles:\n - The model to use (and binds in CopilotKit actions and the tools defined above)\n - The system prompt\n - Getting a response from the model\n - Handling tool calls\n\n For more about the ReAct design pattern, see:\n https://www.perplexity.ai/search/react-agents-NcXLQhreS0WDzpVaS4m9Cg\n \"\"\"\n\n model = ChatOpenAI(model=\"gpt-4o\")\n\n model_with_tools = model.bind_tools(\n [\n *state.get(\"tools\", []), # bind tools defined by ag-ui\n ],\n parallel_tool_calls=False,\n )\n\n system_message = SystemMessage(\n content=f\"Help the user with writing Haikus. If the user asks for a haiku, use the generate_haiku tool to display the haiku to the user.\"\n )\n\n response = await model_with_tools.ainvoke([\n system_message,\n *state[\"messages\"],\n ], config)\n\n return Command(\n goto=END,\n update={\n \"messages\": [response],\n }\n )\n\nworkflow = StateGraph(AgentState)\nworkflow.add_node(\"chat_node\", chat_node)\n# This is required even though we don't have any backend tools to pass in.\nworkflow.add_node(\"tool_node\", ToolNode(tools=[]))\nworkflow.set_entry_point(\"chat_node\")\nworkflow.add_edge(\"chat_node\", END)\n\n\n# Conditionally use a checkpointer based on the environment\n# Check for multiple indicators that we're running in LangGraph dev/API mode\nis_fast_api = os.environ.get(\"LANGGRAPH_FAST_API\", \"false\").lower() == \"true\"\n\n# Compile the graph\nif is_fast_api:\n # For CopilotKit and other contexts, use MemorySaver\n from langgraph.checkpoint.memory import MemorySaver\n memory = MemorySaver()\n graph = workflow.compile(checkpointer=memory)\nelse:\n # When running in LangGraph API/dev, don't use a custom checkpointer\n graph = workflow.compile()\n", "language": "python", "type": "file" }, @@ -741,7 +741,7 @@ "langgraph-fastapi::tool_based_generative_ui": [ { "name": "page.tsx", - "content": "\"use client\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotKitCSSProperties, CopilotSidebar, CopilotChat } from \"@copilotkit/react-ui\";\nimport { Dispatch, SetStateAction, useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport React, { useMemo } from \"react\";\nimport { useMobileView } from \"@/utils/use-mobile-view\";\nimport { useMobileChat } from \"@/utils/use-mobile-chat\";\n\ninterface ToolBasedGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\ninterface GenerateHaiku {\n japanese: string[] | [],\n english: string[] | [],\n image_names: string[] | [],\n selectedImage: string | null,\n}\n\ninterface HaikuCardProps {\n generatedHaiku: GenerateHaiku | Partial\n setHaikus: Dispatch>\n haikus: GenerateHaiku[]\n}\n\nexport default function ToolBasedGenerativeUI({ params }: ToolBasedGenerativeUIProps) {\n const { integrationId } = React.use(params);\n const { isMobile } = useMobileView();\n\n\n const chatTitle = 'Haiku Generator'\n const chatDescription = 'Ask me to create haikus'\n const initialLabel = 'I\\'m a haiku generator 👋. How can I help you?'\n\n return (\n \n \n \n\n {/* Desktop Sidebar */}\n {!isMobile && (\n \n )}\n\n {/* Mobile Pull-Up Chat */}\n {isMobile && }\n \n \n );\n}\n\nfunction MobileChat({ chatTitle, chatDescription, initialLabel }: { chatTitle: string, chatDescription: string, initialLabel: string }) {\n const defaultChatHeight = 50\n\n const {\n isChatOpen,\n setChatHeight,\n setIsChatOpen,\n isDragging,\n chatHeight,\n handleDragStart\n } = useMobileChat(defaultChatHeight)\n return (\n <>\n {/* Chat Toggle Button */}\n
\n
\n {\n if (!isChatOpen) {\n setChatHeight(defaultChatHeight); // Reset to good default when opening\n }\n setIsChatOpen(!isChatOpen);\n }}\n >\n
\n
\n
{chatTitle}
\n
{chatDescription}
\n
\n
\n
\n \n \n \n
\n
\n \n\n {/* Pull-Up Chat Container */}\n \n {/* Drag Handle Bar */}\n \n
\n \n\n {/* Chat Header */}\n
\n
\n
\n

{chatTitle}

\n
\n setIsChatOpen(false)}\n className=\"p-2 hover:bg-gray-100 rounded-full transition-colors\"\n >\n \n \n \n \n
\n
\n\n {/* Chat Content - Flexible container for messages and input */}\n
\n \n
\n \n\n {/* Backdrop */}\n {isChatOpen && (\n setIsChatOpen(false)}\n />\n )}\n \n )\n}\n\nconst VALID_IMAGE_NAMES = [\n \"Osaka_Castle_Turret_Stone_Wall_Pine_Trees_Daytime.jpg\",\n \"Tokyo_Skyline_Night_Tokyo_Tower_Mount_Fuji_View.jpg\",\n \"Itsukushima_Shrine_Miyajima_Floating_Torii_Gate_Sunset_Long_Exposure.jpg\",\n \"Takachiho_Gorge_Waterfall_River_Lush_Greenery_Japan.jpg\",\n \"Bonsai_Tree_Potted_Japanese_Art_Green_Foliage.jpeg\",\n \"Shirakawa-go_Gassho-zukuri_Thatched_Roof_Village_Aerial_View.jpg\",\n \"Ginkaku-ji_Silver_Pavilion_Kyoto_Japanese_Garden_Pond_Reflection.jpg\",\n \"Senso-ji_Temple_Asakusa_Cherry_Blossoms_Kimono_Umbrella.jpg\",\n \"Cherry_Blossoms_Sakura_Night_View_City_Lights_Japan.jpg\",\n \"Mount_Fuji_Lake_Reflection_Cherry_Blossoms_Sakura_Spring.jpg\"\n];\n\nfunction getRandomImage(): string {\n return VALID_IMAGE_NAMES[Math.floor(Math.random() * VALID_IMAGE_NAMES.length)];\n}\n\nconst validateAndCorrectImageNames = (rawNames: string[] | undefined): string[] | null => {\n if (!rawNames || rawNames.length !== 3) {\n return null;\n }\n\n const correctedNames: string[] = [];\n const usedValidNames = new Set();\n\n for (const name of rawNames) {\n if (VALID_IMAGE_NAMES.includes(name) && !usedValidNames.has(name)) {\n correctedNames.push(name);\n usedValidNames.add(name);\n if (correctedNames.length === 3) break;\n }\n }\n\n while (correctedNames.length < 3) {\n const nextImage = getRandomImage();\n if (!usedValidNames.has(nextImage)) {\n correctedNames.push(nextImage);\n usedValidNames.add(nextImage);\n }\n }\n\n return correctedNames.slice(0, 3);\n};\n\nfunction HaikuCard({ generatedHaiku, setHaikus, haikus }: HaikuCardProps) {\n return (\n \n
\n {generatedHaiku?.japanese?.map((line, index) => (\n
\n

{line}

\n

\n {generatedHaiku.english?.[index]}\n

\n
\n ))}\n {generatedHaiku?.japanese && generatedHaiku.japanese.length >= 2 && (\n
\n {(() => {\n const firstLine = generatedHaiku?.japanese?.[0];\n if (!firstLine) return null;\n const haikuIndex = haikus.findIndex((h: any) => h.japanese[0] === firstLine);\n const haiku = haikus[haikuIndex];\n if (!haiku?.image_names) return null;\n\n return haiku.image_names.map((imageName, imgIndex) => (\n {\n setHaikus(prevHaikus => {\n const newHaikus = prevHaikus.map((h, idx) => {\n if (idx === haikuIndex) {\n return {\n ...h,\n selectedImage: imageName\n };\n }\n return h;\n });\n return newHaikus;\n });\n }}\n />\n ));\n })()}\n
\n )}\n
\n \n );\n}\n\ninterface Haiku {\n japanese: string[];\n english: string[];\n image_names: string[];\n selectedImage: string | null;\n}\n\nfunction Haiku() {\n const [haikus, setHaikus] = useState([{\n japanese: [\"仮の句よ\", \"まっさらながら\", \"花を呼ぶ\"],\n english: [\n \"A placeholder verse—\",\n \"even in a blank canvas,\",\n \"it beckons flowers.\",\n ],\n image_names: [],\n selectedImage: null,\n }])\n const [activeIndex, setActiveIndex] = useState(0);\n const [isJustApplied, setIsJustApplied] = useState(false);\n\n useCopilotAction({\n name: \"generate_haiku\",\n parameters: [\n {\n name: \"japanese\",\n type: \"string[]\",\n },\n {\n name: \"english\",\n type: \"string[]\",\n },\n {\n name: \"image_names\",\n type: \"string[]\",\n description: `Names of 3 relevant images selected from the following: \\n -${VALID_IMAGE_NAMES.join('\\n -')}`,\n },\n ],\n followUp: false,\n handler: async ({ japanese, english, image_names }: { japanese: string[], english: string[], image_names: string[] }) => {\n const finalCorrectedImages = validateAndCorrectImageNames(image_names);\n const newHaiku = {\n japanese: japanese || [],\n english: english || [],\n image_names: finalCorrectedImages || [],\n selectedImage: finalCorrectedImages?.[0] || null,\n };\n setHaikus(prev => [...prev, newHaiku].filter(h => h.english[0] !== \"A placeholder verse—\"));\n setActiveIndex(haikus.length - 1);\n setIsJustApplied(true);\n setTimeout(() => setIsJustApplied(false), 600);\n return \"Haiku generated.\";\n },\n render: ({ args: generatedHaiku }: { args: Partial }) => {\n return (\n \n );\n },\n }, [haikus]);\n\n const { isMobile } = useMobileView();\n\n return (\n
\n \n\n {/* Main Display */}\n
\n
\n {haikus.map((haiku, index) => (\n (haikus.length == 1 || index == activeIndex) && (\n\n \n {haiku.japanese.map((line, lineIndex) => (\n \n

\n {line}\n

\n

\n {haiku.english?.[lineIndex]}\n

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n setHaikus((prevHaikus) => {\n return prevHaikus.map((h, idx) => {\n if (idx === index) {\n return { ...h, selectedImage: imageName }\n } else {\n return { ...h }\n }\n })\n })}\n />\n ))}\n
\n )}\n
\n )\n ))}\n
\n \n \n );\n}\n\nfunction Thumbnails({ haikus, activeIndex, setActiveIndex, isMobile }: { haikus: Haiku[], activeIndex: number, setActiveIndex: (index: number) => void, isMobile: boolean }) {\n if (haikus.length == 0 || isMobile) { return null }\n return (\n
\n {haikus.map((haiku, index) => (\n setActiveIndex(index)}\n >\n {haiku.japanese.map((line, lineIndex) => (\n \n

{line}

\n

{haiku.english?.[lineIndex]}

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n \n ))}\n
\n )}\n \n ))}\n \n )\n\n}", + "content": "\"use client\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotKitCSSProperties, CopilotSidebar, CopilotChat } from \"@copilotkit/react-ui\";\nimport { Dispatch, SetStateAction, useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport React, { useMemo } from \"react\";\nimport { useMobileView } from \"@/utils/use-mobile-view\";\nimport { useMobileChat } from \"@/utils/use-mobile-chat\";\n\ninterface ToolBasedGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\ninterface GenerateHaiku {\n japanese: string[] | [],\n english: string[] | [],\n image_names: string[] | [],\n selectedImage: string | null,\n}\n\ninterface HaikuCardProps {\n generatedHaiku: GenerateHaiku | Partial\n setHaikus: Dispatch>\n haikus: GenerateHaiku[]\n}\n\nexport default function ToolBasedGenerativeUI({ params }: ToolBasedGenerativeUIProps) {\n const { integrationId } = React.use(params);\n const { isMobile } = useMobileView();\n\n\n const chatTitle = 'Haiku Generator'\n const chatDescription = 'Ask me to create haikus'\n const initialLabel = 'I\\'m a haiku generator 👋. How can I help you?'\n\n return (\n \n \n \n\n {/* Desktop Sidebar */}\n {!isMobile && (\n \n )}\n\n {/* Mobile Pull-Up Chat */}\n {isMobile && }\n \n \n );\n}\n\nfunction MobileChat({ chatTitle, chatDescription, initialLabel }: { chatTitle: string, chatDescription: string, initialLabel: string }) {\n const defaultChatHeight = 50\n\n const {\n isChatOpen,\n setChatHeight,\n setIsChatOpen,\n isDragging,\n chatHeight,\n handleDragStart\n } = useMobileChat(defaultChatHeight)\n return (\n <>\n {/* Chat Toggle Button */}\n
\n
\n {\n if (!isChatOpen) {\n setChatHeight(defaultChatHeight); // Reset to good default when opening\n }\n setIsChatOpen(!isChatOpen);\n }}\n >\n
\n
\n
{chatTitle}
\n
{chatDescription}
\n
\n
\n
\n \n \n \n
\n
\n \n\n {/* Pull-Up Chat Container */}\n \n {/* Drag Handle Bar */}\n \n
\n \n\n {/* Chat Header */}\n
\n
\n
\n

{chatTitle}

\n
\n setIsChatOpen(false)}\n className=\"p-2 hover:bg-gray-100 rounded-full transition-colors\"\n >\n \n \n \n \n
\n
\n\n {/* Chat Content - Flexible container for messages and input */}\n
\n \n
\n \n\n {/* Backdrop */}\n {isChatOpen && (\n setIsChatOpen(false)}\n />\n )}\n \n )\n}\n\nconst VALID_IMAGE_NAMES = [\n \"Osaka_Castle_Turret_Stone_Wall_Pine_Trees_Daytime.jpg\",\n \"Tokyo_Skyline_Night_Tokyo_Tower_Mount_Fuji_View.jpg\",\n \"Itsukushima_Shrine_Miyajima_Floating_Torii_Gate_Sunset_Long_Exposure.jpg\",\n \"Takachiho_Gorge_Waterfall_River_Lush_Greenery_Japan.jpg\",\n \"Bonsai_Tree_Potted_Japanese_Art_Green_Foliage.jpeg\",\n \"Shirakawa-go_Gassho-zukuri_Thatched_Roof_Village_Aerial_View.jpg\",\n \"Ginkaku-ji_Silver_Pavilion_Kyoto_Japanese_Garden_Pond_Reflection.jpg\",\n \"Senso-ji_Temple_Asakusa_Cherry_Blossoms_Kimono_Umbrella.jpg\",\n \"Cherry_Blossoms_Sakura_Night_View_City_Lights_Japan.jpg\",\n \"Mount_Fuji_Lake_Reflection_Cherry_Blossoms_Sakura_Spring.jpg\"\n];\n\nfunction getRandomImage(): string {\n return VALID_IMAGE_NAMES[Math.floor(Math.random() * VALID_IMAGE_NAMES.length)];\n}\n\nconst validateAndCorrectImageNames = (rawNames: string[] | undefined): string[] | null => {\n if (!rawNames || rawNames.length !== 3) {\n return null;\n }\n\n const correctedNames: string[] = [];\n const usedValidNames = new Set();\n\n for (const name of rawNames) {\n if (VALID_IMAGE_NAMES.includes(name) && !usedValidNames.has(name)) {\n correctedNames.push(name);\n usedValidNames.add(name);\n if (correctedNames.length === 3) break;\n }\n }\n\n while (correctedNames.length < 3) {\n const nextImage = getRandomImage();\n if (!usedValidNames.has(nextImage)) {\n correctedNames.push(nextImage);\n usedValidNames.add(nextImage);\n }\n }\n\n return correctedNames.slice(0, 3);\n};\n\nfunction HaikuCard({ generatedHaiku, setHaikus, haikus }: HaikuCardProps) {\n return (\n \n
\n {generatedHaiku?.japanese?.map((line, index) => (\n
\n

{line}

\n

\n {generatedHaiku.english?.[index]}\n

\n
\n ))}\n {generatedHaiku?.japanese && generatedHaiku.japanese.length >= 2 && (\n
\n {(() => {\n const firstLine = generatedHaiku?.japanese?.[0];\n if (!firstLine) return null;\n const haikuIndex = haikus.findIndex((h: any) => h.japanese[0] === firstLine);\n const haiku = haikus[haikuIndex];\n if (!haiku?.image_names) return null;\n\n return haiku.image_names.map((imageName, imgIndex) => (\n {\n setHaikus(prevHaikus => {\n const newHaikus = prevHaikus.map((h, idx) => {\n if (idx === haikuIndex) {\n return {\n ...h,\n selectedImage: imageName\n };\n }\n return h;\n });\n return newHaikus;\n });\n }}\n />\n ));\n })()}\n
\n )}\n
\n \n );\n}\n\ninterface Haiku {\n japanese: string[];\n english: string[];\n image_names: string[];\n selectedImage: string | null;\n}\n\nfunction Haiku() {\n const [haikus, setHaikus] = useState([{\n japanese: [\"仮の句よ\", \"まっさらながら\", \"花を呼ぶ\"],\n english: [\n \"A placeholder verse—\",\n \"even in a blank canvas,\",\n \"it beckons flowers.\",\n ],\n image_names: [],\n selectedImage: null,\n }])\n const [activeIndex, setActiveIndex] = useState(0);\n const [isJustApplied, setIsJustApplied] = useState(false);\n\n useCopilotAction({\n name: \"generate_haiku\",\n parameters: [\n {\n name: \"japanese\",\n type: \"string[]\",\n },\n {\n name: \"english\",\n type: \"string[]\",\n },\n {\n name: \"image_names\",\n type: \"string[]\",\n description: `Names of 3 relevant images selected from the following: \\n -${VALID_IMAGE_NAMES.join('\\n -')}`,\n },\n ],\n followUp: false,\n handler: async ({ japanese, english, image_names }: { japanese: string[], english: string[], image_names: string[] }) => {\n const finalCorrectedImages = validateAndCorrectImageNames(image_names);\n const newHaiku = {\n japanese: japanese || [],\n english: english || [],\n image_names: finalCorrectedImages || [],\n selectedImage: finalCorrectedImages?.[0] || null,\n };\n setHaikus(prev => [newHaiku, ...prev].filter(h => h.english[0] !== \"A placeholder verse—\"));\n setActiveIndex(haikus.length - 1);\n setIsJustApplied(true);\n setTimeout(() => setIsJustApplied(false), 600);\n return \"Haiku generated.\";\n },\n render: ({ args: generatedHaiku }: { args: Partial }) => {\n return (\n \n );\n },\n }, [haikus]);\n\n const { isMobile } = useMobileView();\n\n return (\n
\n \n\n {/* Main Display */}\n
\n
\n {haikus.map((haiku, index) => (\n (haikus.length == 1 || index == activeIndex) && (\n\n \n {haiku.japanese.map((line, lineIndex) => (\n \n

\n {line}\n

\n

\n {haiku.english?.[lineIndex]}\n

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n setHaikus((prevHaikus) => {\n return prevHaikus.map((h, idx) => {\n if (idx === index) {\n return { ...h, selectedImage: imageName }\n } else {\n return { ...h }\n }\n })\n })}\n />\n ))}\n
\n )}\n
\n )\n ))}\n
\n \n \n );\n}\n\nfunction Thumbnails({ haikus, activeIndex, setActiveIndex, isMobile }: { haikus: Haiku[], activeIndex: number, setActiveIndex: (index: number) => void, isMobile: boolean }) {\n if (haikus.length == 0 || isMobile) { return null }\n return (\n
\n {haikus.map((haiku, index) => (\n setActiveIndex(index)}\n >\n {haiku.japanese.map((line, lineIndex) => (\n \n

{line}

\n

{haiku.english?.[lineIndex]}

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n \n ))}\n
\n )}\n \n ))}\n \n )\n\n}", "language": "typescript", "type": "file" }, @@ -759,7 +759,7 @@ }, { "name": "agent.py", - "content": "\"\"\"\nAn example demonstrating tool-based generative UI using LangGraph.\n\"\"\"\n\nfrom typing import Any, List\nfrom typing_extensions import Literal\nfrom langchain_openai import ChatOpenAI\nfrom langchain_core.messages import SystemMessage\nfrom langchain_core.runnables import RunnableConfig\nfrom langgraph.graph import StateGraph, END\nfrom langgraph.types import Command\nfrom langgraph.graph import MessagesState\nfrom langgraph.prebuilt import ToolNode\n\n\nclass AgentState(MessagesState):\n \"\"\"\n State of the agent.\n \"\"\"\n tools: List[Any]\n\nasync def chat_node(state: AgentState, config: RunnableConfig) -> Command[Literal[\"tool_node\", \"__end__\"]]:\n \"\"\"\n Standard chat node based on the ReAct design pattern. It handles:\n - The model to use (and binds in CopilotKit actions and the tools defined above)\n - The system prompt\n - Getting a response from the model\n - Handling tool calls\n\n For more about the ReAct design pattern, see:\n https://www.perplexity.ai/search/react-agents-NcXLQhreS0WDzpVaS4m9Cg\n \"\"\"\n\n model = ChatOpenAI(model=\"gpt-4o\")\n\n model_with_tools = model.bind_tools(\n [\n *state.get(\"tools\", []), # bind tools defined by ag-ui\n ],\n parallel_tool_calls=False,\n )\n\n system_message = SystemMessage(\n content=f\"Help the user with writing Haikus. If the user asks for a haiku, use the generate_haiku tool to display the haiku to the user.\"\n )\n\n response = await model_with_tools.ainvoke([\n system_message,\n *state[\"messages\"],\n ], config)\n\n return Command(\n goto=END,\n update={\n \"messages\": [response],\n }\n )\n\nworkflow = StateGraph(AgentState)\nworkflow.add_node(\"chat_node\", chat_node)\n# This is required even though we don't have any backend tools to pass in.\nworkflow.add_node(\"tool_node\", ToolNode(tools=[]))\nworkflow.set_entry_point(\"chat_node\")\n\ngraph = workflow.compile()", + "content": "\"\"\"\nAn example demonstrating tool-based generative UI using LangGraph.\n\"\"\"\n\nimport os\nfrom typing import Any, List\nfrom typing_extensions import Literal\nfrom langchain_openai import ChatOpenAI\nfrom langchain_core.messages import SystemMessage\nfrom langchain_core.runnables import RunnableConfig\nfrom langgraph.graph import StateGraph, END\nfrom langgraph.types import Command\nfrom langgraph.graph import MessagesState\nfrom langgraph.prebuilt import ToolNode\n\n\nclass AgentState(MessagesState):\n \"\"\"\n State of the agent.\n \"\"\"\n tools: List[Any]\n\nasync def chat_node(state: AgentState, config: RunnableConfig) -> Command[Literal[\"tool_node\", \"__end__\"]]:\n \"\"\"\n Standard chat node based on the ReAct design pattern. It handles:\n - The model to use (and binds in CopilotKit actions and the tools defined above)\n - The system prompt\n - Getting a response from the model\n - Handling tool calls\n\n For more about the ReAct design pattern, see:\n https://www.perplexity.ai/search/react-agents-NcXLQhreS0WDzpVaS4m9Cg\n \"\"\"\n\n model = ChatOpenAI(model=\"gpt-4o\")\n\n model_with_tools = model.bind_tools(\n [\n *state.get(\"tools\", []), # bind tools defined by ag-ui\n ],\n parallel_tool_calls=False,\n )\n\n system_message = SystemMessage(\n content=f\"Help the user with writing Haikus. If the user asks for a haiku, use the generate_haiku tool to display the haiku to the user.\"\n )\n\n response = await model_with_tools.ainvoke([\n system_message,\n *state[\"messages\"],\n ], config)\n\n return Command(\n goto=END,\n update={\n \"messages\": [response],\n }\n )\n\nworkflow = StateGraph(AgentState)\nworkflow.add_node(\"chat_node\", chat_node)\n# This is required even though we don't have any backend tools to pass in.\nworkflow.add_node(\"tool_node\", ToolNode(tools=[]))\nworkflow.set_entry_point(\"chat_node\")\nworkflow.add_edge(\"chat_node\", END)\n\n\n# Conditionally use a checkpointer based on the environment\n# Check for multiple indicators that we're running in LangGraph dev/API mode\nis_fast_api = os.environ.get(\"LANGGRAPH_FAST_API\", \"false\").lower() == \"true\"\n\n# Compile the graph\nif is_fast_api:\n # For CopilotKit and other contexts, use MemorySaver\n from langgraph.checkpoint.memory import MemorySaver\n memory = MemorySaver()\n graph = workflow.compile(checkpointer=memory)\nelse:\n # When running in LangGraph API/dev, don't use a custom checkpointer\n graph = workflow.compile()\n", "language": "python", "type": "file" } @@ -953,7 +953,7 @@ "langgraph-typescript::tool_based_generative_ui": [ { "name": "page.tsx", - "content": "\"use client\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotKitCSSProperties, CopilotSidebar, CopilotChat } from \"@copilotkit/react-ui\";\nimport { Dispatch, SetStateAction, useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport React, { useMemo } from \"react\";\nimport { useMobileView } from \"@/utils/use-mobile-view\";\nimport { useMobileChat } from \"@/utils/use-mobile-chat\";\n\ninterface ToolBasedGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\ninterface GenerateHaiku {\n japanese: string[] | [],\n english: string[] | [],\n image_names: string[] | [],\n selectedImage: string | null,\n}\n\ninterface HaikuCardProps {\n generatedHaiku: GenerateHaiku | Partial\n setHaikus: Dispatch>\n haikus: GenerateHaiku[]\n}\n\nexport default function ToolBasedGenerativeUI({ params }: ToolBasedGenerativeUIProps) {\n const { integrationId } = React.use(params);\n const { isMobile } = useMobileView();\n\n\n const chatTitle = 'Haiku Generator'\n const chatDescription = 'Ask me to create haikus'\n const initialLabel = 'I\\'m a haiku generator 👋. How can I help you?'\n\n return (\n \n \n \n\n {/* Desktop Sidebar */}\n {!isMobile && (\n \n )}\n\n {/* Mobile Pull-Up Chat */}\n {isMobile && }\n \n \n );\n}\n\nfunction MobileChat({ chatTitle, chatDescription, initialLabel }: { chatTitle: string, chatDescription: string, initialLabel: string }) {\n const defaultChatHeight = 50\n\n const {\n isChatOpen,\n setChatHeight,\n setIsChatOpen,\n isDragging,\n chatHeight,\n handleDragStart\n } = useMobileChat(defaultChatHeight)\n return (\n <>\n {/* Chat Toggle Button */}\n
\n
\n {\n if (!isChatOpen) {\n setChatHeight(defaultChatHeight); // Reset to good default when opening\n }\n setIsChatOpen(!isChatOpen);\n }}\n >\n
\n
\n
{chatTitle}
\n
{chatDescription}
\n
\n
\n
\n \n \n \n
\n
\n \n\n {/* Pull-Up Chat Container */}\n \n {/* Drag Handle Bar */}\n \n
\n \n\n {/* Chat Header */}\n
\n
\n
\n

{chatTitle}

\n
\n setIsChatOpen(false)}\n className=\"p-2 hover:bg-gray-100 rounded-full transition-colors\"\n >\n \n \n \n \n
\n
\n\n {/* Chat Content - Flexible container for messages and input */}\n
\n \n
\n \n\n {/* Backdrop */}\n {isChatOpen && (\n setIsChatOpen(false)}\n />\n )}\n \n )\n}\n\nconst VALID_IMAGE_NAMES = [\n \"Osaka_Castle_Turret_Stone_Wall_Pine_Trees_Daytime.jpg\",\n \"Tokyo_Skyline_Night_Tokyo_Tower_Mount_Fuji_View.jpg\",\n \"Itsukushima_Shrine_Miyajima_Floating_Torii_Gate_Sunset_Long_Exposure.jpg\",\n \"Takachiho_Gorge_Waterfall_River_Lush_Greenery_Japan.jpg\",\n \"Bonsai_Tree_Potted_Japanese_Art_Green_Foliage.jpeg\",\n \"Shirakawa-go_Gassho-zukuri_Thatched_Roof_Village_Aerial_View.jpg\",\n \"Ginkaku-ji_Silver_Pavilion_Kyoto_Japanese_Garden_Pond_Reflection.jpg\",\n \"Senso-ji_Temple_Asakusa_Cherry_Blossoms_Kimono_Umbrella.jpg\",\n \"Cherry_Blossoms_Sakura_Night_View_City_Lights_Japan.jpg\",\n \"Mount_Fuji_Lake_Reflection_Cherry_Blossoms_Sakura_Spring.jpg\"\n];\n\nfunction getRandomImage(): string {\n return VALID_IMAGE_NAMES[Math.floor(Math.random() * VALID_IMAGE_NAMES.length)];\n}\n\nconst validateAndCorrectImageNames = (rawNames: string[] | undefined): string[] | null => {\n if (!rawNames || rawNames.length !== 3) {\n return null;\n }\n\n const correctedNames: string[] = [];\n const usedValidNames = new Set();\n\n for (const name of rawNames) {\n if (VALID_IMAGE_NAMES.includes(name) && !usedValidNames.has(name)) {\n correctedNames.push(name);\n usedValidNames.add(name);\n if (correctedNames.length === 3) break;\n }\n }\n\n while (correctedNames.length < 3) {\n const nextImage = getRandomImage();\n if (!usedValidNames.has(nextImage)) {\n correctedNames.push(nextImage);\n usedValidNames.add(nextImage);\n }\n }\n\n return correctedNames.slice(0, 3);\n};\n\nfunction HaikuCard({ generatedHaiku, setHaikus, haikus }: HaikuCardProps) {\n return (\n \n
\n {generatedHaiku?.japanese?.map((line, index) => (\n
\n

{line}

\n

\n {generatedHaiku.english?.[index]}\n

\n
\n ))}\n {generatedHaiku?.japanese && generatedHaiku.japanese.length >= 2 && (\n
\n {(() => {\n const firstLine = generatedHaiku?.japanese?.[0];\n if (!firstLine) return null;\n const haikuIndex = haikus.findIndex((h: any) => h.japanese[0] === firstLine);\n const haiku = haikus[haikuIndex];\n if (!haiku?.image_names) return null;\n\n return haiku.image_names.map((imageName, imgIndex) => (\n {\n setHaikus(prevHaikus => {\n const newHaikus = prevHaikus.map((h, idx) => {\n if (idx === haikuIndex) {\n return {\n ...h,\n selectedImage: imageName\n };\n }\n return h;\n });\n return newHaikus;\n });\n }}\n />\n ));\n })()}\n
\n )}\n
\n \n );\n}\n\ninterface Haiku {\n japanese: string[];\n english: string[];\n image_names: string[];\n selectedImage: string | null;\n}\n\nfunction Haiku() {\n const [haikus, setHaikus] = useState([{\n japanese: [\"仮の句よ\", \"まっさらながら\", \"花を呼ぶ\"],\n english: [\n \"A placeholder verse—\",\n \"even in a blank canvas,\",\n \"it beckons flowers.\",\n ],\n image_names: [],\n selectedImage: null,\n }])\n const [activeIndex, setActiveIndex] = useState(0);\n const [isJustApplied, setIsJustApplied] = useState(false);\n\n useCopilotAction({\n name: \"generate_haiku\",\n parameters: [\n {\n name: \"japanese\",\n type: \"string[]\",\n },\n {\n name: \"english\",\n type: \"string[]\",\n },\n {\n name: \"image_names\",\n type: \"string[]\",\n description: `Names of 3 relevant images selected from the following: \\n -${VALID_IMAGE_NAMES.join('\\n -')}`,\n },\n ],\n followUp: false,\n handler: async ({ japanese, english, image_names }: { japanese: string[], english: string[], image_names: string[] }) => {\n const finalCorrectedImages = validateAndCorrectImageNames(image_names);\n const newHaiku = {\n japanese: japanese || [],\n english: english || [],\n image_names: finalCorrectedImages || [],\n selectedImage: finalCorrectedImages?.[0] || null,\n };\n setHaikus(prev => [...prev, newHaiku].filter(h => h.english[0] !== \"A placeholder verse—\"));\n setActiveIndex(haikus.length - 1);\n setIsJustApplied(true);\n setTimeout(() => setIsJustApplied(false), 600);\n return \"Haiku generated.\";\n },\n render: ({ args: generatedHaiku }: { args: Partial }) => {\n return (\n \n );\n },\n }, [haikus]);\n\n const { isMobile } = useMobileView();\n\n return (\n
\n \n\n {/* Main Display */}\n
\n
\n {haikus.map((haiku, index) => (\n (haikus.length == 1 || index == activeIndex) && (\n\n \n {haiku.japanese.map((line, lineIndex) => (\n \n

\n {line}\n

\n

\n {haiku.english?.[lineIndex]}\n

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n setHaikus((prevHaikus) => {\n return prevHaikus.map((h, idx) => {\n if (idx === index) {\n return { ...h, selectedImage: imageName }\n } else {\n return { ...h }\n }\n })\n })}\n />\n ))}\n
\n )}\n
\n )\n ))}\n
\n \n \n );\n}\n\nfunction Thumbnails({ haikus, activeIndex, setActiveIndex, isMobile }: { haikus: Haiku[], activeIndex: number, setActiveIndex: (index: number) => void, isMobile: boolean }) {\n if (haikus.length == 0 || isMobile) { return null }\n return (\n
\n {haikus.map((haiku, index) => (\n setActiveIndex(index)}\n >\n {haiku.japanese.map((line, lineIndex) => (\n \n

{line}

\n

{haiku.english?.[lineIndex]}

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n \n ))}\n
\n )}\n \n ))}\n \n )\n\n}", + "content": "\"use client\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotKitCSSProperties, CopilotSidebar, CopilotChat } from \"@copilotkit/react-ui\";\nimport { Dispatch, SetStateAction, useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport React, { useMemo } from \"react\";\nimport { useMobileView } from \"@/utils/use-mobile-view\";\nimport { useMobileChat } from \"@/utils/use-mobile-chat\";\n\ninterface ToolBasedGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\ninterface GenerateHaiku {\n japanese: string[] | [],\n english: string[] | [],\n image_names: string[] | [],\n selectedImage: string | null,\n}\n\ninterface HaikuCardProps {\n generatedHaiku: GenerateHaiku | Partial\n setHaikus: Dispatch>\n haikus: GenerateHaiku[]\n}\n\nexport default function ToolBasedGenerativeUI({ params }: ToolBasedGenerativeUIProps) {\n const { integrationId } = React.use(params);\n const { isMobile } = useMobileView();\n\n\n const chatTitle = 'Haiku Generator'\n const chatDescription = 'Ask me to create haikus'\n const initialLabel = 'I\\'m a haiku generator 👋. How can I help you?'\n\n return (\n \n \n \n\n {/* Desktop Sidebar */}\n {!isMobile && (\n \n )}\n\n {/* Mobile Pull-Up Chat */}\n {isMobile && }\n \n \n );\n}\n\nfunction MobileChat({ chatTitle, chatDescription, initialLabel }: { chatTitle: string, chatDescription: string, initialLabel: string }) {\n const defaultChatHeight = 50\n\n const {\n isChatOpen,\n setChatHeight,\n setIsChatOpen,\n isDragging,\n chatHeight,\n handleDragStart\n } = useMobileChat(defaultChatHeight)\n return (\n <>\n {/* Chat Toggle Button */}\n
\n
\n {\n if (!isChatOpen) {\n setChatHeight(defaultChatHeight); // Reset to good default when opening\n }\n setIsChatOpen(!isChatOpen);\n }}\n >\n
\n
\n
{chatTitle}
\n
{chatDescription}
\n
\n
\n
\n \n \n \n
\n
\n \n\n {/* Pull-Up Chat Container */}\n \n {/* Drag Handle Bar */}\n \n
\n \n\n {/* Chat Header */}\n
\n
\n
\n

{chatTitle}

\n
\n setIsChatOpen(false)}\n className=\"p-2 hover:bg-gray-100 rounded-full transition-colors\"\n >\n \n \n \n \n
\n
\n\n {/* Chat Content - Flexible container for messages and input */}\n
\n \n
\n \n\n {/* Backdrop */}\n {isChatOpen && (\n setIsChatOpen(false)}\n />\n )}\n \n )\n}\n\nconst VALID_IMAGE_NAMES = [\n \"Osaka_Castle_Turret_Stone_Wall_Pine_Trees_Daytime.jpg\",\n \"Tokyo_Skyline_Night_Tokyo_Tower_Mount_Fuji_View.jpg\",\n \"Itsukushima_Shrine_Miyajima_Floating_Torii_Gate_Sunset_Long_Exposure.jpg\",\n \"Takachiho_Gorge_Waterfall_River_Lush_Greenery_Japan.jpg\",\n \"Bonsai_Tree_Potted_Japanese_Art_Green_Foliage.jpeg\",\n \"Shirakawa-go_Gassho-zukuri_Thatched_Roof_Village_Aerial_View.jpg\",\n \"Ginkaku-ji_Silver_Pavilion_Kyoto_Japanese_Garden_Pond_Reflection.jpg\",\n \"Senso-ji_Temple_Asakusa_Cherry_Blossoms_Kimono_Umbrella.jpg\",\n \"Cherry_Blossoms_Sakura_Night_View_City_Lights_Japan.jpg\",\n \"Mount_Fuji_Lake_Reflection_Cherry_Blossoms_Sakura_Spring.jpg\"\n];\n\nfunction getRandomImage(): string {\n return VALID_IMAGE_NAMES[Math.floor(Math.random() * VALID_IMAGE_NAMES.length)];\n}\n\nconst validateAndCorrectImageNames = (rawNames: string[] | undefined): string[] | null => {\n if (!rawNames || rawNames.length !== 3) {\n return null;\n }\n\n const correctedNames: string[] = [];\n const usedValidNames = new Set();\n\n for (const name of rawNames) {\n if (VALID_IMAGE_NAMES.includes(name) && !usedValidNames.has(name)) {\n correctedNames.push(name);\n usedValidNames.add(name);\n if (correctedNames.length === 3) break;\n }\n }\n\n while (correctedNames.length < 3) {\n const nextImage = getRandomImage();\n if (!usedValidNames.has(nextImage)) {\n correctedNames.push(nextImage);\n usedValidNames.add(nextImage);\n }\n }\n\n return correctedNames.slice(0, 3);\n};\n\nfunction HaikuCard({ generatedHaiku, setHaikus, haikus }: HaikuCardProps) {\n return (\n \n
\n {generatedHaiku?.japanese?.map((line, index) => (\n
\n

{line}

\n

\n {generatedHaiku.english?.[index]}\n

\n
\n ))}\n {generatedHaiku?.japanese && generatedHaiku.japanese.length >= 2 && (\n
\n {(() => {\n const firstLine = generatedHaiku?.japanese?.[0];\n if (!firstLine) return null;\n const haikuIndex = haikus.findIndex((h: any) => h.japanese[0] === firstLine);\n const haiku = haikus[haikuIndex];\n if (!haiku?.image_names) return null;\n\n return haiku.image_names.map((imageName, imgIndex) => (\n {\n setHaikus(prevHaikus => {\n const newHaikus = prevHaikus.map((h, idx) => {\n if (idx === haikuIndex) {\n return {\n ...h,\n selectedImage: imageName\n };\n }\n return h;\n });\n return newHaikus;\n });\n }}\n />\n ));\n })()}\n
\n )}\n
\n \n );\n}\n\ninterface Haiku {\n japanese: string[];\n english: string[];\n image_names: string[];\n selectedImage: string | null;\n}\n\nfunction Haiku() {\n const [haikus, setHaikus] = useState([{\n japanese: [\"仮の句よ\", \"まっさらながら\", \"花を呼ぶ\"],\n english: [\n \"A placeholder verse—\",\n \"even in a blank canvas,\",\n \"it beckons flowers.\",\n ],\n image_names: [],\n selectedImage: null,\n }])\n const [activeIndex, setActiveIndex] = useState(0);\n const [isJustApplied, setIsJustApplied] = useState(false);\n\n useCopilotAction({\n name: \"generate_haiku\",\n parameters: [\n {\n name: \"japanese\",\n type: \"string[]\",\n },\n {\n name: \"english\",\n type: \"string[]\",\n },\n {\n name: \"image_names\",\n type: \"string[]\",\n description: `Names of 3 relevant images selected from the following: \\n -${VALID_IMAGE_NAMES.join('\\n -')}`,\n },\n ],\n followUp: false,\n handler: async ({ japanese, english, image_names }: { japanese: string[], english: string[], image_names: string[] }) => {\n const finalCorrectedImages = validateAndCorrectImageNames(image_names);\n const newHaiku = {\n japanese: japanese || [],\n english: english || [],\n image_names: finalCorrectedImages || [],\n selectedImage: finalCorrectedImages?.[0] || null,\n };\n setHaikus(prev => [newHaiku, ...prev].filter(h => h.english[0] !== \"A placeholder verse—\"));\n setActiveIndex(haikus.length - 1);\n setIsJustApplied(true);\n setTimeout(() => setIsJustApplied(false), 600);\n return \"Haiku generated.\";\n },\n render: ({ args: generatedHaiku }: { args: Partial }) => {\n return (\n \n );\n },\n }, [haikus]);\n\n const { isMobile } = useMobileView();\n\n return (\n
\n \n\n {/* Main Display */}\n
\n
\n {haikus.map((haiku, index) => (\n (haikus.length == 1 || index == activeIndex) && (\n\n \n {haiku.japanese.map((line, lineIndex) => (\n \n

\n {line}\n

\n

\n {haiku.english?.[lineIndex]}\n

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n setHaikus((prevHaikus) => {\n return prevHaikus.map((h, idx) => {\n if (idx === index) {\n return { ...h, selectedImage: imageName }\n } else {\n return { ...h }\n }\n })\n })}\n />\n ))}\n
\n )}\n
\n )\n ))}\n
\n \n \n );\n}\n\nfunction Thumbnails({ haikus, activeIndex, setActiveIndex, isMobile }: { haikus: Haiku[], activeIndex: number, setActiveIndex: (index: number) => void, isMobile: boolean }) {\n if (haikus.length == 0 || isMobile) { return null }\n return (\n
\n {haikus.map((haiku, index) => (\n setActiveIndex(index)}\n >\n {haiku.japanese.map((line, lineIndex) => (\n \n

{line}

\n

{haiku.english?.[lineIndex]}

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n \n ))}\n
\n )}\n \n ))}\n \n )\n\n}", "language": "typescript", "type": "file" }, @@ -971,7 +971,7 @@ }, { "name": "agent.py", - "content": "\"\"\"\nAn example demonstrating tool-based generative UI using LangGraph.\n\"\"\"\n\nfrom typing import Any, List\nfrom typing_extensions import Literal\nfrom langchain_openai import ChatOpenAI\nfrom langchain_core.messages import SystemMessage\nfrom langchain_core.runnables import RunnableConfig\nfrom langgraph.graph import StateGraph, END\nfrom langgraph.types import Command\nfrom langgraph.graph import MessagesState\nfrom langgraph.prebuilt import ToolNode\n\n\nclass AgentState(MessagesState):\n \"\"\"\n State of the agent.\n \"\"\"\n tools: List[Any]\n\nasync def chat_node(state: AgentState, config: RunnableConfig) -> Command[Literal[\"tool_node\", \"__end__\"]]:\n \"\"\"\n Standard chat node based on the ReAct design pattern. It handles:\n - The model to use (and binds in CopilotKit actions and the tools defined above)\n - The system prompt\n - Getting a response from the model\n - Handling tool calls\n\n For more about the ReAct design pattern, see:\n https://www.perplexity.ai/search/react-agents-NcXLQhreS0WDzpVaS4m9Cg\n \"\"\"\n\n model = ChatOpenAI(model=\"gpt-4o\")\n\n model_with_tools = model.bind_tools(\n [\n *state.get(\"tools\", []), # bind tools defined by ag-ui\n ],\n parallel_tool_calls=False,\n )\n\n system_message = SystemMessage(\n content=f\"Help the user with writing Haikus. If the user asks for a haiku, use the generate_haiku tool to display the haiku to the user.\"\n )\n\n response = await model_with_tools.ainvoke([\n system_message,\n *state[\"messages\"],\n ], config)\n\n return Command(\n goto=END,\n update={\n \"messages\": [response],\n }\n )\n\nworkflow = StateGraph(AgentState)\nworkflow.add_node(\"chat_node\", chat_node)\n# This is required even though we don't have any backend tools to pass in.\nworkflow.add_node(\"tool_node\", ToolNode(tools=[]))\nworkflow.set_entry_point(\"chat_node\")\n\ngraph = workflow.compile()", + "content": "\"\"\"\nAn example demonstrating tool-based generative UI using LangGraph.\n\"\"\"\n\nimport os\nfrom typing import Any, List\nfrom typing_extensions import Literal\nfrom langchain_openai import ChatOpenAI\nfrom langchain_core.messages import SystemMessage\nfrom langchain_core.runnables import RunnableConfig\nfrom langgraph.graph import StateGraph, END\nfrom langgraph.types import Command\nfrom langgraph.graph import MessagesState\nfrom langgraph.prebuilt import ToolNode\n\n\nclass AgentState(MessagesState):\n \"\"\"\n State of the agent.\n \"\"\"\n tools: List[Any]\n\nasync def chat_node(state: AgentState, config: RunnableConfig) -> Command[Literal[\"tool_node\", \"__end__\"]]:\n \"\"\"\n Standard chat node based on the ReAct design pattern. It handles:\n - The model to use (and binds in CopilotKit actions and the tools defined above)\n - The system prompt\n - Getting a response from the model\n - Handling tool calls\n\n For more about the ReAct design pattern, see:\n https://www.perplexity.ai/search/react-agents-NcXLQhreS0WDzpVaS4m9Cg\n \"\"\"\n\n model = ChatOpenAI(model=\"gpt-4o\")\n\n model_with_tools = model.bind_tools(\n [\n *state.get(\"tools\", []), # bind tools defined by ag-ui\n ],\n parallel_tool_calls=False,\n )\n\n system_message = SystemMessage(\n content=f\"Help the user with writing Haikus. If the user asks for a haiku, use the generate_haiku tool to display the haiku to the user.\"\n )\n\n response = await model_with_tools.ainvoke([\n system_message,\n *state[\"messages\"],\n ], config)\n\n return Command(\n goto=END,\n update={\n \"messages\": [response],\n }\n )\n\nworkflow = StateGraph(AgentState)\nworkflow.add_node(\"chat_node\", chat_node)\n# This is required even though we don't have any backend tools to pass in.\nworkflow.add_node(\"tool_node\", ToolNode(tools=[]))\nworkflow.set_entry_point(\"chat_node\")\nworkflow.add_edge(\"chat_node\", END)\n\n\n# Conditionally use a checkpointer based on the environment\n# Check for multiple indicators that we're running in LangGraph dev/API mode\nis_fast_api = os.environ.get(\"LANGGRAPH_FAST_API\", \"false\").lower() == \"true\"\n\n# Compile the graph\nif is_fast_api:\n # For CopilotKit and other contexts, use MemorySaver\n from langgraph.checkpoint.memory import MemorySaver\n memory = MemorySaver()\n graph = workflow.compile(checkpointer=memory)\nelse:\n # When running in LangGraph API/dev, don't use a custom checkpointer\n graph = workflow.compile()\n", "language": "python", "type": "file" }, @@ -1005,7 +1005,7 @@ "agno::tool_based_generative_ui": [ { "name": "page.tsx", - "content": "\"use client\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotKitCSSProperties, CopilotSidebar, CopilotChat } from \"@copilotkit/react-ui\";\nimport { Dispatch, SetStateAction, useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport React, { useMemo } from \"react\";\nimport { useMobileView } from \"@/utils/use-mobile-view\";\nimport { useMobileChat } from \"@/utils/use-mobile-chat\";\n\ninterface ToolBasedGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\ninterface GenerateHaiku {\n japanese: string[] | [],\n english: string[] | [],\n image_names: string[] | [],\n selectedImage: string | null,\n}\n\ninterface HaikuCardProps {\n generatedHaiku: GenerateHaiku | Partial\n setHaikus: Dispatch>\n haikus: GenerateHaiku[]\n}\n\nexport default function ToolBasedGenerativeUI({ params }: ToolBasedGenerativeUIProps) {\n const { integrationId } = React.use(params);\n const { isMobile } = useMobileView();\n\n\n const chatTitle = 'Haiku Generator'\n const chatDescription = 'Ask me to create haikus'\n const initialLabel = 'I\\'m a haiku generator 👋. How can I help you?'\n\n return (\n \n \n \n\n {/* Desktop Sidebar */}\n {!isMobile && (\n \n )}\n\n {/* Mobile Pull-Up Chat */}\n {isMobile && }\n \n \n );\n}\n\nfunction MobileChat({ chatTitle, chatDescription, initialLabel }: { chatTitle: string, chatDescription: string, initialLabel: string }) {\n const defaultChatHeight = 50\n\n const {\n isChatOpen,\n setChatHeight,\n setIsChatOpen,\n isDragging,\n chatHeight,\n handleDragStart\n } = useMobileChat(defaultChatHeight)\n return (\n <>\n {/* Chat Toggle Button */}\n
\n
\n {\n if (!isChatOpen) {\n setChatHeight(defaultChatHeight); // Reset to good default when opening\n }\n setIsChatOpen(!isChatOpen);\n }}\n >\n
\n
\n
{chatTitle}
\n
{chatDescription}
\n
\n
\n
\n \n \n \n
\n
\n \n\n {/* Pull-Up Chat Container */}\n \n {/* Drag Handle Bar */}\n \n
\n \n\n {/* Chat Header */}\n
\n
\n
\n

{chatTitle}

\n
\n setIsChatOpen(false)}\n className=\"p-2 hover:bg-gray-100 rounded-full transition-colors\"\n >\n \n \n \n \n
\n
\n\n {/* Chat Content - Flexible container for messages and input */}\n
\n \n
\n \n\n {/* Backdrop */}\n {isChatOpen && (\n setIsChatOpen(false)}\n />\n )}\n \n )\n}\n\nconst VALID_IMAGE_NAMES = [\n \"Osaka_Castle_Turret_Stone_Wall_Pine_Trees_Daytime.jpg\",\n \"Tokyo_Skyline_Night_Tokyo_Tower_Mount_Fuji_View.jpg\",\n \"Itsukushima_Shrine_Miyajima_Floating_Torii_Gate_Sunset_Long_Exposure.jpg\",\n \"Takachiho_Gorge_Waterfall_River_Lush_Greenery_Japan.jpg\",\n \"Bonsai_Tree_Potted_Japanese_Art_Green_Foliage.jpeg\",\n \"Shirakawa-go_Gassho-zukuri_Thatched_Roof_Village_Aerial_View.jpg\",\n \"Ginkaku-ji_Silver_Pavilion_Kyoto_Japanese_Garden_Pond_Reflection.jpg\",\n \"Senso-ji_Temple_Asakusa_Cherry_Blossoms_Kimono_Umbrella.jpg\",\n \"Cherry_Blossoms_Sakura_Night_View_City_Lights_Japan.jpg\",\n \"Mount_Fuji_Lake_Reflection_Cherry_Blossoms_Sakura_Spring.jpg\"\n];\n\nfunction getRandomImage(): string {\n return VALID_IMAGE_NAMES[Math.floor(Math.random() * VALID_IMAGE_NAMES.length)];\n}\n\nconst validateAndCorrectImageNames = (rawNames: string[] | undefined): string[] | null => {\n if (!rawNames || rawNames.length !== 3) {\n return null;\n }\n\n const correctedNames: string[] = [];\n const usedValidNames = new Set();\n\n for (const name of rawNames) {\n if (VALID_IMAGE_NAMES.includes(name) && !usedValidNames.has(name)) {\n correctedNames.push(name);\n usedValidNames.add(name);\n if (correctedNames.length === 3) break;\n }\n }\n\n while (correctedNames.length < 3) {\n const nextImage = getRandomImage();\n if (!usedValidNames.has(nextImage)) {\n correctedNames.push(nextImage);\n usedValidNames.add(nextImage);\n }\n }\n\n return correctedNames.slice(0, 3);\n};\n\nfunction HaikuCard({ generatedHaiku, setHaikus, haikus }: HaikuCardProps) {\n return (\n \n
\n {generatedHaiku?.japanese?.map((line, index) => (\n
\n

{line}

\n

\n {generatedHaiku.english?.[index]}\n

\n
\n ))}\n {generatedHaiku?.japanese && generatedHaiku.japanese.length >= 2 && (\n
\n {(() => {\n const firstLine = generatedHaiku?.japanese?.[0];\n if (!firstLine) return null;\n const haikuIndex = haikus.findIndex((h: any) => h.japanese[0] === firstLine);\n const haiku = haikus[haikuIndex];\n if (!haiku?.image_names) return null;\n\n return haiku.image_names.map((imageName, imgIndex) => (\n {\n setHaikus(prevHaikus => {\n const newHaikus = prevHaikus.map((h, idx) => {\n if (idx === haikuIndex) {\n return {\n ...h,\n selectedImage: imageName\n };\n }\n return h;\n });\n return newHaikus;\n });\n }}\n />\n ));\n })()}\n
\n )}\n
\n \n );\n}\n\ninterface Haiku {\n japanese: string[];\n english: string[];\n image_names: string[];\n selectedImage: string | null;\n}\n\nfunction Haiku() {\n const [haikus, setHaikus] = useState([{\n japanese: [\"仮の句よ\", \"まっさらながら\", \"花を呼ぶ\"],\n english: [\n \"A placeholder verse—\",\n \"even in a blank canvas,\",\n \"it beckons flowers.\",\n ],\n image_names: [],\n selectedImage: null,\n }])\n const [activeIndex, setActiveIndex] = useState(0);\n const [isJustApplied, setIsJustApplied] = useState(false);\n\n useCopilotAction({\n name: \"generate_haiku\",\n parameters: [\n {\n name: \"japanese\",\n type: \"string[]\",\n },\n {\n name: \"english\",\n type: \"string[]\",\n },\n {\n name: \"image_names\",\n type: \"string[]\",\n description: `Names of 3 relevant images selected from the following: \\n -${VALID_IMAGE_NAMES.join('\\n -')}`,\n },\n ],\n followUp: false,\n handler: async ({ japanese, english, image_names }: { japanese: string[], english: string[], image_names: string[] }) => {\n const finalCorrectedImages = validateAndCorrectImageNames(image_names);\n const newHaiku = {\n japanese: japanese || [],\n english: english || [],\n image_names: finalCorrectedImages || [],\n selectedImage: finalCorrectedImages?.[0] || null,\n };\n setHaikus(prev => [...prev, newHaiku].filter(h => h.english[0] !== \"A placeholder verse—\"));\n setActiveIndex(haikus.length - 1);\n setIsJustApplied(true);\n setTimeout(() => setIsJustApplied(false), 600);\n return \"Haiku generated.\";\n },\n render: ({ args: generatedHaiku }: { args: Partial }) => {\n return (\n \n );\n },\n }, [haikus]);\n\n const { isMobile } = useMobileView();\n\n return (\n
\n \n\n {/* Main Display */}\n
\n
\n {haikus.map((haiku, index) => (\n (haikus.length == 1 || index == activeIndex) && (\n\n \n {haiku.japanese.map((line, lineIndex) => (\n \n

\n {line}\n

\n

\n {haiku.english?.[lineIndex]}\n

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n setHaikus((prevHaikus) => {\n return prevHaikus.map((h, idx) => {\n if (idx === index) {\n return { ...h, selectedImage: imageName }\n } else {\n return { ...h }\n }\n })\n })}\n />\n ))}\n
\n )}\n
\n )\n ))}\n
\n \n \n );\n}\n\nfunction Thumbnails({ haikus, activeIndex, setActiveIndex, isMobile }: { haikus: Haiku[], activeIndex: number, setActiveIndex: (index: number) => void, isMobile: boolean }) {\n if (haikus.length == 0 || isMobile) { return null }\n return (\n
\n {haikus.map((haiku, index) => (\n setActiveIndex(index)}\n >\n {haiku.japanese.map((line, lineIndex) => (\n \n

{line}

\n

{haiku.english?.[lineIndex]}

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n \n ))}\n
\n )}\n \n ))}\n \n )\n\n}", + "content": "\"use client\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotKitCSSProperties, CopilotSidebar, CopilotChat } from \"@copilotkit/react-ui\";\nimport { Dispatch, SetStateAction, useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport React, { useMemo } from \"react\";\nimport { useMobileView } from \"@/utils/use-mobile-view\";\nimport { useMobileChat } from \"@/utils/use-mobile-chat\";\n\ninterface ToolBasedGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\ninterface GenerateHaiku {\n japanese: string[] | [],\n english: string[] | [],\n image_names: string[] | [],\n selectedImage: string | null,\n}\n\ninterface HaikuCardProps {\n generatedHaiku: GenerateHaiku | Partial\n setHaikus: Dispatch>\n haikus: GenerateHaiku[]\n}\n\nexport default function ToolBasedGenerativeUI({ params }: ToolBasedGenerativeUIProps) {\n const { integrationId } = React.use(params);\n const { isMobile } = useMobileView();\n\n\n const chatTitle = 'Haiku Generator'\n const chatDescription = 'Ask me to create haikus'\n const initialLabel = 'I\\'m a haiku generator 👋. How can I help you?'\n\n return (\n \n \n \n\n {/* Desktop Sidebar */}\n {!isMobile && (\n \n )}\n\n {/* Mobile Pull-Up Chat */}\n {isMobile && }\n \n \n );\n}\n\nfunction MobileChat({ chatTitle, chatDescription, initialLabel }: { chatTitle: string, chatDescription: string, initialLabel: string }) {\n const defaultChatHeight = 50\n\n const {\n isChatOpen,\n setChatHeight,\n setIsChatOpen,\n isDragging,\n chatHeight,\n handleDragStart\n } = useMobileChat(defaultChatHeight)\n return (\n <>\n {/* Chat Toggle Button */}\n
\n
\n {\n if (!isChatOpen) {\n setChatHeight(defaultChatHeight); // Reset to good default when opening\n }\n setIsChatOpen(!isChatOpen);\n }}\n >\n
\n
\n
{chatTitle}
\n
{chatDescription}
\n
\n
\n
\n \n \n \n
\n
\n \n\n {/* Pull-Up Chat Container */}\n \n {/* Drag Handle Bar */}\n \n
\n \n\n {/* Chat Header */}\n
\n
\n
\n

{chatTitle}

\n
\n setIsChatOpen(false)}\n className=\"p-2 hover:bg-gray-100 rounded-full transition-colors\"\n >\n \n \n \n \n
\n
\n\n {/* Chat Content - Flexible container for messages and input */}\n
\n \n
\n \n\n {/* Backdrop */}\n {isChatOpen && (\n setIsChatOpen(false)}\n />\n )}\n \n )\n}\n\nconst VALID_IMAGE_NAMES = [\n \"Osaka_Castle_Turret_Stone_Wall_Pine_Trees_Daytime.jpg\",\n \"Tokyo_Skyline_Night_Tokyo_Tower_Mount_Fuji_View.jpg\",\n \"Itsukushima_Shrine_Miyajima_Floating_Torii_Gate_Sunset_Long_Exposure.jpg\",\n \"Takachiho_Gorge_Waterfall_River_Lush_Greenery_Japan.jpg\",\n \"Bonsai_Tree_Potted_Japanese_Art_Green_Foliage.jpeg\",\n \"Shirakawa-go_Gassho-zukuri_Thatched_Roof_Village_Aerial_View.jpg\",\n \"Ginkaku-ji_Silver_Pavilion_Kyoto_Japanese_Garden_Pond_Reflection.jpg\",\n \"Senso-ji_Temple_Asakusa_Cherry_Blossoms_Kimono_Umbrella.jpg\",\n \"Cherry_Blossoms_Sakura_Night_View_City_Lights_Japan.jpg\",\n \"Mount_Fuji_Lake_Reflection_Cherry_Blossoms_Sakura_Spring.jpg\"\n];\n\nfunction getRandomImage(): string {\n return VALID_IMAGE_NAMES[Math.floor(Math.random() * VALID_IMAGE_NAMES.length)];\n}\n\nconst validateAndCorrectImageNames = (rawNames: string[] | undefined): string[] | null => {\n if (!rawNames || rawNames.length !== 3) {\n return null;\n }\n\n const correctedNames: string[] = [];\n const usedValidNames = new Set();\n\n for (const name of rawNames) {\n if (VALID_IMAGE_NAMES.includes(name) && !usedValidNames.has(name)) {\n correctedNames.push(name);\n usedValidNames.add(name);\n if (correctedNames.length === 3) break;\n }\n }\n\n while (correctedNames.length < 3) {\n const nextImage = getRandomImage();\n if (!usedValidNames.has(nextImage)) {\n correctedNames.push(nextImage);\n usedValidNames.add(nextImage);\n }\n }\n\n return correctedNames.slice(0, 3);\n};\n\nfunction HaikuCard({ generatedHaiku, setHaikus, haikus }: HaikuCardProps) {\n return (\n \n
\n {generatedHaiku?.japanese?.map((line, index) => (\n
\n

{line}

\n

\n {generatedHaiku.english?.[index]}\n

\n
\n ))}\n {generatedHaiku?.japanese && generatedHaiku.japanese.length >= 2 && (\n
\n {(() => {\n const firstLine = generatedHaiku?.japanese?.[0];\n if (!firstLine) return null;\n const haikuIndex = haikus.findIndex((h: any) => h.japanese[0] === firstLine);\n const haiku = haikus[haikuIndex];\n if (!haiku?.image_names) return null;\n\n return haiku.image_names.map((imageName, imgIndex) => (\n {\n setHaikus(prevHaikus => {\n const newHaikus = prevHaikus.map((h, idx) => {\n if (idx === haikuIndex) {\n return {\n ...h,\n selectedImage: imageName\n };\n }\n return h;\n });\n return newHaikus;\n });\n }}\n />\n ));\n })()}\n
\n )}\n
\n \n );\n}\n\ninterface Haiku {\n japanese: string[];\n english: string[];\n image_names: string[];\n selectedImage: string | null;\n}\n\nfunction Haiku() {\n const [haikus, setHaikus] = useState([{\n japanese: [\"仮の句よ\", \"まっさらながら\", \"花を呼ぶ\"],\n english: [\n \"A placeholder verse—\",\n \"even in a blank canvas,\",\n \"it beckons flowers.\",\n ],\n image_names: [],\n selectedImage: null,\n }])\n const [activeIndex, setActiveIndex] = useState(0);\n const [isJustApplied, setIsJustApplied] = useState(false);\n\n useCopilotAction({\n name: \"generate_haiku\",\n parameters: [\n {\n name: \"japanese\",\n type: \"string[]\",\n },\n {\n name: \"english\",\n type: \"string[]\",\n },\n {\n name: \"image_names\",\n type: \"string[]\",\n description: `Names of 3 relevant images selected from the following: \\n -${VALID_IMAGE_NAMES.join('\\n -')}`,\n },\n ],\n followUp: false,\n handler: async ({ japanese, english, image_names }: { japanese: string[], english: string[], image_names: string[] }) => {\n const finalCorrectedImages = validateAndCorrectImageNames(image_names);\n const newHaiku = {\n japanese: japanese || [],\n english: english || [],\n image_names: finalCorrectedImages || [],\n selectedImage: finalCorrectedImages?.[0] || null,\n };\n setHaikus(prev => [newHaiku, ...prev].filter(h => h.english[0] !== \"A placeholder verse—\"));\n setActiveIndex(haikus.length - 1);\n setIsJustApplied(true);\n setTimeout(() => setIsJustApplied(false), 600);\n return \"Haiku generated.\";\n },\n render: ({ args: generatedHaiku }: { args: Partial }) => {\n return (\n \n );\n },\n }, [haikus]);\n\n const { isMobile } = useMobileView();\n\n return (\n
\n \n\n {/* Main Display */}\n
\n
\n {haikus.map((haiku, index) => (\n (haikus.length == 1 || index == activeIndex) && (\n\n \n {haiku.japanese.map((line, lineIndex) => (\n \n

\n {line}\n

\n

\n {haiku.english?.[lineIndex]}\n

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n setHaikus((prevHaikus) => {\n return prevHaikus.map((h, idx) => {\n if (idx === index) {\n return { ...h, selectedImage: imageName }\n } else {\n return { ...h }\n }\n })\n })}\n />\n ))}\n
\n )}\n
\n )\n ))}\n
\n \n \n );\n}\n\nfunction Thumbnails({ haikus, activeIndex, setActiveIndex, isMobile }: { haikus: Haiku[], activeIndex: number, setActiveIndex: (index: number) => void, isMobile: boolean }) {\n if (haikus.length == 0 || isMobile) { return null }\n return (\n
\n {haikus.map((haiku, index) => (\n setActiveIndex(index)}\n >\n {haiku.japanese.map((line, lineIndex) => (\n \n

{line}

\n

{haiku.english?.[lineIndex]}

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n \n ))}\n
\n )}\n \n ))}\n \n )\n\n}", "language": "typescript", "type": "file" }, @@ -1181,7 +1181,7 @@ "crewai::tool_based_generative_ui": [ { "name": "page.tsx", - "content": "\"use client\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotKitCSSProperties, CopilotSidebar, CopilotChat } from \"@copilotkit/react-ui\";\nimport { Dispatch, SetStateAction, useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport React, { useMemo } from \"react\";\nimport { useMobileView } from \"@/utils/use-mobile-view\";\nimport { useMobileChat } from \"@/utils/use-mobile-chat\";\n\ninterface ToolBasedGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\ninterface GenerateHaiku {\n japanese: string[] | [],\n english: string[] | [],\n image_names: string[] | [],\n selectedImage: string | null,\n}\n\ninterface HaikuCardProps {\n generatedHaiku: GenerateHaiku | Partial\n setHaikus: Dispatch>\n haikus: GenerateHaiku[]\n}\n\nexport default function ToolBasedGenerativeUI({ params }: ToolBasedGenerativeUIProps) {\n const { integrationId } = React.use(params);\n const { isMobile } = useMobileView();\n\n\n const chatTitle = 'Haiku Generator'\n const chatDescription = 'Ask me to create haikus'\n const initialLabel = 'I\\'m a haiku generator 👋. How can I help you?'\n\n return (\n \n \n \n\n {/* Desktop Sidebar */}\n {!isMobile && (\n \n )}\n\n {/* Mobile Pull-Up Chat */}\n {isMobile && }\n \n \n );\n}\n\nfunction MobileChat({ chatTitle, chatDescription, initialLabel }: { chatTitle: string, chatDescription: string, initialLabel: string }) {\n const defaultChatHeight = 50\n\n const {\n isChatOpen,\n setChatHeight,\n setIsChatOpen,\n isDragging,\n chatHeight,\n handleDragStart\n } = useMobileChat(defaultChatHeight)\n return (\n <>\n {/* Chat Toggle Button */}\n
\n
\n {\n if (!isChatOpen) {\n setChatHeight(defaultChatHeight); // Reset to good default when opening\n }\n setIsChatOpen(!isChatOpen);\n }}\n >\n
\n
\n
{chatTitle}
\n
{chatDescription}
\n
\n
\n
\n \n \n \n
\n
\n \n\n {/* Pull-Up Chat Container */}\n \n {/* Drag Handle Bar */}\n \n
\n \n\n {/* Chat Header */}\n
\n
\n
\n

{chatTitle}

\n
\n setIsChatOpen(false)}\n className=\"p-2 hover:bg-gray-100 rounded-full transition-colors\"\n >\n \n \n \n \n
\n
\n\n {/* Chat Content - Flexible container for messages and input */}\n
\n \n
\n \n\n {/* Backdrop */}\n {isChatOpen && (\n setIsChatOpen(false)}\n />\n )}\n \n )\n}\n\nconst VALID_IMAGE_NAMES = [\n \"Osaka_Castle_Turret_Stone_Wall_Pine_Trees_Daytime.jpg\",\n \"Tokyo_Skyline_Night_Tokyo_Tower_Mount_Fuji_View.jpg\",\n \"Itsukushima_Shrine_Miyajima_Floating_Torii_Gate_Sunset_Long_Exposure.jpg\",\n \"Takachiho_Gorge_Waterfall_River_Lush_Greenery_Japan.jpg\",\n \"Bonsai_Tree_Potted_Japanese_Art_Green_Foliage.jpeg\",\n \"Shirakawa-go_Gassho-zukuri_Thatched_Roof_Village_Aerial_View.jpg\",\n \"Ginkaku-ji_Silver_Pavilion_Kyoto_Japanese_Garden_Pond_Reflection.jpg\",\n \"Senso-ji_Temple_Asakusa_Cherry_Blossoms_Kimono_Umbrella.jpg\",\n \"Cherry_Blossoms_Sakura_Night_View_City_Lights_Japan.jpg\",\n \"Mount_Fuji_Lake_Reflection_Cherry_Blossoms_Sakura_Spring.jpg\"\n];\n\nfunction getRandomImage(): string {\n return VALID_IMAGE_NAMES[Math.floor(Math.random() * VALID_IMAGE_NAMES.length)];\n}\n\nconst validateAndCorrectImageNames = (rawNames: string[] | undefined): string[] | null => {\n if (!rawNames || rawNames.length !== 3) {\n return null;\n }\n\n const correctedNames: string[] = [];\n const usedValidNames = new Set();\n\n for (const name of rawNames) {\n if (VALID_IMAGE_NAMES.includes(name) && !usedValidNames.has(name)) {\n correctedNames.push(name);\n usedValidNames.add(name);\n if (correctedNames.length === 3) break;\n }\n }\n\n while (correctedNames.length < 3) {\n const nextImage = getRandomImage();\n if (!usedValidNames.has(nextImage)) {\n correctedNames.push(nextImage);\n usedValidNames.add(nextImage);\n }\n }\n\n return correctedNames.slice(0, 3);\n};\n\nfunction HaikuCard({ generatedHaiku, setHaikus, haikus }: HaikuCardProps) {\n return (\n \n
\n {generatedHaiku?.japanese?.map((line, index) => (\n
\n

{line}

\n

\n {generatedHaiku.english?.[index]}\n

\n
\n ))}\n {generatedHaiku?.japanese && generatedHaiku.japanese.length >= 2 && (\n
\n {(() => {\n const firstLine = generatedHaiku?.japanese?.[0];\n if (!firstLine) return null;\n const haikuIndex = haikus.findIndex((h: any) => h.japanese[0] === firstLine);\n const haiku = haikus[haikuIndex];\n if (!haiku?.image_names) return null;\n\n return haiku.image_names.map((imageName, imgIndex) => (\n {\n setHaikus(prevHaikus => {\n const newHaikus = prevHaikus.map((h, idx) => {\n if (idx === haikuIndex) {\n return {\n ...h,\n selectedImage: imageName\n };\n }\n return h;\n });\n return newHaikus;\n });\n }}\n />\n ));\n })()}\n
\n )}\n
\n \n );\n}\n\ninterface Haiku {\n japanese: string[];\n english: string[];\n image_names: string[];\n selectedImage: string | null;\n}\n\nfunction Haiku() {\n const [haikus, setHaikus] = useState([{\n japanese: [\"仮の句よ\", \"まっさらながら\", \"花を呼ぶ\"],\n english: [\n \"A placeholder verse—\",\n \"even in a blank canvas,\",\n \"it beckons flowers.\",\n ],\n image_names: [],\n selectedImage: null,\n }])\n const [activeIndex, setActiveIndex] = useState(0);\n const [isJustApplied, setIsJustApplied] = useState(false);\n\n useCopilotAction({\n name: \"generate_haiku\",\n parameters: [\n {\n name: \"japanese\",\n type: \"string[]\",\n },\n {\n name: \"english\",\n type: \"string[]\",\n },\n {\n name: \"image_names\",\n type: \"string[]\",\n description: `Names of 3 relevant images selected from the following: \\n -${VALID_IMAGE_NAMES.join('\\n -')}`,\n },\n ],\n followUp: false,\n handler: async ({ japanese, english, image_names }: { japanese: string[], english: string[], image_names: string[] }) => {\n const finalCorrectedImages = validateAndCorrectImageNames(image_names);\n const newHaiku = {\n japanese: japanese || [],\n english: english || [],\n image_names: finalCorrectedImages || [],\n selectedImage: finalCorrectedImages?.[0] || null,\n };\n setHaikus(prev => [...prev, newHaiku].filter(h => h.english[0] !== \"A placeholder verse—\"));\n setActiveIndex(haikus.length - 1);\n setIsJustApplied(true);\n setTimeout(() => setIsJustApplied(false), 600);\n return \"Haiku generated.\";\n },\n render: ({ args: generatedHaiku }: { args: Partial }) => {\n return (\n \n );\n },\n }, [haikus]);\n\n const { isMobile } = useMobileView();\n\n return (\n
\n \n\n {/* Main Display */}\n
\n
\n {haikus.map((haiku, index) => (\n (haikus.length == 1 || index == activeIndex) && (\n\n \n {haiku.japanese.map((line, lineIndex) => (\n \n

\n {line}\n

\n

\n {haiku.english?.[lineIndex]}\n

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n setHaikus((prevHaikus) => {\n return prevHaikus.map((h, idx) => {\n if (idx === index) {\n return { ...h, selectedImage: imageName }\n } else {\n return { ...h }\n }\n })\n })}\n />\n ))}\n
\n )}\n
\n )\n ))}\n
\n \n \n );\n}\n\nfunction Thumbnails({ haikus, activeIndex, setActiveIndex, isMobile }: { haikus: Haiku[], activeIndex: number, setActiveIndex: (index: number) => void, isMobile: boolean }) {\n if (haikus.length == 0 || isMobile) { return null }\n return (\n
\n {haikus.map((haiku, index) => (\n setActiveIndex(index)}\n >\n {haiku.japanese.map((line, lineIndex) => (\n \n

{line}

\n

{haiku.english?.[lineIndex]}

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n \n ))}\n
\n )}\n \n ))}\n \n )\n\n}", + "content": "\"use client\";\nimport { CopilotKit, useCopilotAction } from \"@copilotkit/react-core\";\nimport { CopilotKitCSSProperties, CopilotSidebar, CopilotChat } from \"@copilotkit/react-ui\";\nimport { Dispatch, SetStateAction, useState, useEffect } from \"react\";\nimport \"@copilotkit/react-ui/styles.css\";\nimport \"./style.css\";\nimport React, { useMemo } from \"react\";\nimport { useMobileView } from \"@/utils/use-mobile-view\";\nimport { useMobileChat } from \"@/utils/use-mobile-chat\";\n\ninterface ToolBasedGenerativeUIProps {\n params: Promise<{\n integrationId: string;\n }>;\n}\n\ninterface GenerateHaiku {\n japanese: string[] | [],\n english: string[] | [],\n image_names: string[] | [],\n selectedImage: string | null,\n}\n\ninterface HaikuCardProps {\n generatedHaiku: GenerateHaiku | Partial\n setHaikus: Dispatch>\n haikus: GenerateHaiku[]\n}\n\nexport default function ToolBasedGenerativeUI({ params }: ToolBasedGenerativeUIProps) {\n const { integrationId } = React.use(params);\n const { isMobile } = useMobileView();\n\n\n const chatTitle = 'Haiku Generator'\n const chatDescription = 'Ask me to create haikus'\n const initialLabel = 'I\\'m a haiku generator 👋. How can I help you?'\n\n return (\n \n \n \n\n {/* Desktop Sidebar */}\n {!isMobile && (\n \n )}\n\n {/* Mobile Pull-Up Chat */}\n {isMobile && }\n \n \n );\n}\n\nfunction MobileChat({ chatTitle, chatDescription, initialLabel }: { chatTitle: string, chatDescription: string, initialLabel: string }) {\n const defaultChatHeight = 50\n\n const {\n isChatOpen,\n setChatHeight,\n setIsChatOpen,\n isDragging,\n chatHeight,\n handleDragStart\n } = useMobileChat(defaultChatHeight)\n return (\n <>\n {/* Chat Toggle Button */}\n
\n
\n {\n if (!isChatOpen) {\n setChatHeight(defaultChatHeight); // Reset to good default when opening\n }\n setIsChatOpen(!isChatOpen);\n }}\n >\n
\n
\n
{chatTitle}
\n
{chatDescription}
\n
\n
\n
\n \n \n \n
\n
\n \n\n {/* Pull-Up Chat Container */}\n \n {/* Drag Handle Bar */}\n \n
\n \n\n {/* Chat Header */}\n
\n
\n
\n

{chatTitle}

\n
\n setIsChatOpen(false)}\n className=\"p-2 hover:bg-gray-100 rounded-full transition-colors\"\n >\n \n \n \n \n
\n
\n\n {/* Chat Content - Flexible container for messages and input */}\n
\n \n
\n \n\n {/* Backdrop */}\n {isChatOpen && (\n setIsChatOpen(false)}\n />\n )}\n \n )\n}\n\nconst VALID_IMAGE_NAMES = [\n \"Osaka_Castle_Turret_Stone_Wall_Pine_Trees_Daytime.jpg\",\n \"Tokyo_Skyline_Night_Tokyo_Tower_Mount_Fuji_View.jpg\",\n \"Itsukushima_Shrine_Miyajima_Floating_Torii_Gate_Sunset_Long_Exposure.jpg\",\n \"Takachiho_Gorge_Waterfall_River_Lush_Greenery_Japan.jpg\",\n \"Bonsai_Tree_Potted_Japanese_Art_Green_Foliage.jpeg\",\n \"Shirakawa-go_Gassho-zukuri_Thatched_Roof_Village_Aerial_View.jpg\",\n \"Ginkaku-ji_Silver_Pavilion_Kyoto_Japanese_Garden_Pond_Reflection.jpg\",\n \"Senso-ji_Temple_Asakusa_Cherry_Blossoms_Kimono_Umbrella.jpg\",\n \"Cherry_Blossoms_Sakura_Night_View_City_Lights_Japan.jpg\",\n \"Mount_Fuji_Lake_Reflection_Cherry_Blossoms_Sakura_Spring.jpg\"\n];\n\nfunction getRandomImage(): string {\n return VALID_IMAGE_NAMES[Math.floor(Math.random() * VALID_IMAGE_NAMES.length)];\n}\n\nconst validateAndCorrectImageNames = (rawNames: string[] | undefined): string[] | null => {\n if (!rawNames || rawNames.length !== 3) {\n return null;\n }\n\n const correctedNames: string[] = [];\n const usedValidNames = new Set();\n\n for (const name of rawNames) {\n if (VALID_IMAGE_NAMES.includes(name) && !usedValidNames.has(name)) {\n correctedNames.push(name);\n usedValidNames.add(name);\n if (correctedNames.length === 3) break;\n }\n }\n\n while (correctedNames.length < 3) {\n const nextImage = getRandomImage();\n if (!usedValidNames.has(nextImage)) {\n correctedNames.push(nextImage);\n usedValidNames.add(nextImage);\n }\n }\n\n return correctedNames.slice(0, 3);\n};\n\nfunction HaikuCard({ generatedHaiku, setHaikus, haikus }: HaikuCardProps) {\n return (\n \n
\n {generatedHaiku?.japanese?.map((line, index) => (\n
\n

{line}

\n

\n {generatedHaiku.english?.[index]}\n

\n
\n ))}\n {generatedHaiku?.japanese && generatedHaiku.japanese.length >= 2 && (\n
\n {(() => {\n const firstLine = generatedHaiku?.japanese?.[0];\n if (!firstLine) return null;\n const haikuIndex = haikus.findIndex((h: any) => h.japanese[0] === firstLine);\n const haiku = haikus[haikuIndex];\n if (!haiku?.image_names) return null;\n\n return haiku.image_names.map((imageName, imgIndex) => (\n {\n setHaikus(prevHaikus => {\n const newHaikus = prevHaikus.map((h, idx) => {\n if (idx === haikuIndex) {\n return {\n ...h,\n selectedImage: imageName\n };\n }\n return h;\n });\n return newHaikus;\n });\n }}\n />\n ));\n })()}\n
\n )}\n
\n \n );\n}\n\ninterface Haiku {\n japanese: string[];\n english: string[];\n image_names: string[];\n selectedImage: string | null;\n}\n\nfunction Haiku() {\n const [haikus, setHaikus] = useState([{\n japanese: [\"仮の句よ\", \"まっさらながら\", \"花を呼ぶ\"],\n english: [\n \"A placeholder verse—\",\n \"even in a blank canvas,\",\n \"it beckons flowers.\",\n ],\n image_names: [],\n selectedImage: null,\n }])\n const [activeIndex, setActiveIndex] = useState(0);\n const [isJustApplied, setIsJustApplied] = useState(false);\n\n useCopilotAction({\n name: \"generate_haiku\",\n parameters: [\n {\n name: \"japanese\",\n type: \"string[]\",\n },\n {\n name: \"english\",\n type: \"string[]\",\n },\n {\n name: \"image_names\",\n type: \"string[]\",\n description: `Names of 3 relevant images selected from the following: \\n -${VALID_IMAGE_NAMES.join('\\n -')}`,\n },\n ],\n followUp: false,\n handler: async ({ japanese, english, image_names }: { japanese: string[], english: string[], image_names: string[] }) => {\n const finalCorrectedImages = validateAndCorrectImageNames(image_names);\n const newHaiku = {\n japanese: japanese || [],\n english: english || [],\n image_names: finalCorrectedImages || [],\n selectedImage: finalCorrectedImages?.[0] || null,\n };\n setHaikus(prev => [newHaiku, ...prev].filter(h => h.english[0] !== \"A placeholder verse—\"));\n setActiveIndex(haikus.length - 1);\n setIsJustApplied(true);\n setTimeout(() => setIsJustApplied(false), 600);\n return \"Haiku generated.\";\n },\n render: ({ args: generatedHaiku }: { args: Partial }) => {\n return (\n \n );\n },\n }, [haikus]);\n\n const { isMobile } = useMobileView();\n\n return (\n
\n \n\n {/* Main Display */}\n
\n
\n {haikus.map((haiku, index) => (\n (haikus.length == 1 || index == activeIndex) && (\n\n \n {haiku.japanese.map((line, lineIndex) => (\n \n

\n {line}\n

\n

\n {haiku.english?.[lineIndex]}\n

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n setHaikus((prevHaikus) => {\n return prevHaikus.map((h, idx) => {\n if (idx === index) {\n return { ...h, selectedImage: imageName }\n } else {\n return { ...h }\n }\n })\n })}\n />\n ))}\n
\n )}\n
\n )\n ))}\n
\n \n \n );\n}\n\nfunction Thumbnails({ haikus, activeIndex, setActiveIndex, isMobile }: { haikus: Haiku[], activeIndex: number, setActiveIndex: (index: number) => void, isMobile: boolean }) {\n if (haikus.length == 0 || isMobile) { return null }\n return (\n
\n {haikus.map((haiku, index) => (\n setActiveIndex(index)}\n >\n {haiku.japanese.map((line, lineIndex) => (\n \n

{line}

\n

{haiku.english?.[lineIndex]}

\n
\n ))}\n {haiku.image_names && haiku.image_names.length === 3 && (\n
\n {haiku.image_names.map((imageName, imgIndex) => (\n \n ))}\n
\n )}\n \n ))}\n \n )\n\n}", "language": "typescript", "type": "file" }, diff --git a/typescript-sdk/apps/dojo/src/menu.ts b/typescript-sdk/apps/dojo/src/menu.ts index a931f8541..d670b962f 100644 --- a/typescript-sdk/apps/dojo/src/menu.ts +++ b/typescript-sdk/apps/dojo/src/menu.ts @@ -6,18 +6,6 @@ export const menuIntegrations: MenuIntegrationConfig[] = [ name: "Middleware Starter", features: ["agentic_chat"], }, - { - id: "pydantic-ai", - name: "Pydantic AI", - features: [ - "agentic_chat", - "human_in_the_loop", - "agentic_generative_ui", - "tool_based_generative_ui", - "shared_state", - "predictive_state_updates", - ], - }, { id: "server-starter", name: "Server Starter", @@ -29,26 +17,29 @@ export const menuIntegrations: MenuIntegrationConfig[] = [ features: [ "agentic_chat", "human_in_the_loop", + "agentic_chat_reasoning", "agentic_generative_ui", - "tool_based_generative_ui", - "shared_state", "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", ], }, { - id: "mastra", - name: "Mastra", + id: "agno", + name: "Agno", features: ["agentic_chat", "tool_based_generative_ui"], }, { - id: "mastra-agent-local", - name: "Mastra Agent (Local)", - features: ["agentic_chat", "shared_state", "tool_based_generative_ui"], - }, - { - id: "vercel-ai-sdk", - name: "Vercel AI SDK", - features: ["agentic_chat"], + id: "crewai", + name: "CrewAI", + features: [ + "agentic_chat", + "human_in_the_loop", + "agentic_generative_ui", + "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", + ], }, { id: "langgraph", @@ -57,9 +48,9 @@ export const menuIntegrations: MenuIntegrationConfig[] = [ "agentic_chat", "human_in_the_loop", "agentic_generative_ui", - "tool_based_generative_ui", "predictive_state_updates", "shared_state", + "tool_based_generative_ui", ], }, { @@ -68,14 +59,13 @@ export const menuIntegrations: MenuIntegrationConfig[] = [ features: [ "agentic_chat", "human_in_the_loop", + "agentic_chat_reasoning", "agentic_generative_ui", - "tool_based_generative_ui", "predictive_state_updates", "shared_state", - "agentic_chat_reasoning", + "tool_based_generative_ui", ], }, - { id: "langgraph-typescript", name: "LangGraph (Typescript)", @@ -83,31 +73,43 @@ export const menuIntegrations: MenuIntegrationConfig[] = [ "agentic_chat", "human_in_the_loop", "agentic_generative_ui", - "tool_based_generative_ui", "predictive_state_updates", - "shared_state" + "shared_state", + "tool_based_generative_ui" ], }, - { - id: "agno", - name: "Agno", - features: ["agentic_chat", "tool_based_generative_ui"], - }, { id: "llama-index", name: "LlamaIndex", features: ["agentic_chat", "human_in_the_loop", "agentic_generative_ui", "shared_state"], + }, { - id: "crewai", - name: "CrewAI", + id: "mastra", + name: "Mastra", + features: ["agentic_chat", "tool_based_generative_ui"], + }, + { + id: "mastra-agent-local", + name: "Mastra Agent (Local)", + features: ["agentic_chat", "shared_state", "tool_based_generative_ui"], + }, + { + id: "pydantic-ai", + name: "Pydantic AI", features: [ "agentic_chat", "human_in_the_loop", - "tool_based_generative_ui", "agentic_generative_ui", - "shared_state", "predictive_state_updates", + "shared_state", + "tool_based_generative_ui", ], }, + { + id: "vercel-ai-sdk", + name: "Vercel AI SDK", + features: ["agentic_chat"], + }, ]; + diff --git a/typescript-sdk/apps/dojo/tsconfig.json b/typescript-sdk/apps/dojo/tsconfig.json index 15e5e0688..af157911a 100644 --- a/typescript-sdk/apps/dojo/tsconfig.json +++ b/typescript-sdk/apps/dojo/tsconfig.json @@ -25,5 +25,5 @@ } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules", "e2e", "e2e2"] + "exclude": ["node_modules", "e2e"] } diff --git a/typescript-sdk/integrations/langgraph/examples/python/agents/tool_based_generative_ui/agent.py b/typescript-sdk/integrations/langgraph/examples/python/agents/tool_based_generative_ui/agent.py index b42b1aef6..013a9c06f 100644 --- a/typescript-sdk/integrations/langgraph/examples/python/agents/tool_based_generative_ui/agent.py +++ b/typescript-sdk/integrations/langgraph/examples/python/agents/tool_based_generative_ui/agent.py @@ -2,6 +2,7 @@ An example demonstrating tool-based generative UI using LangGraph. """ +import os from typing import Any, List from typing_extensions import Literal from langchain_openai import ChatOpenAI @@ -61,5 +62,19 @@ async def chat_node(state: AgentState, config: RunnableConfig) -> Command[Litera # This is required even though we don't have any backend tools to pass in. workflow.add_node("tool_node", ToolNode(tools=[])) workflow.set_entry_point("chat_node") +workflow.add_edge("chat_node", END) -graph = workflow.compile() \ No newline at end of file + +# Conditionally use a checkpointer based on the environment +# Check for multiple indicators that we're running in LangGraph dev/API mode +is_fast_api = os.environ.get("LANGGRAPH_FAST_API", "false").lower() == "true" + +# Compile the graph +if is_fast_api: + # For CopilotKit and other contexts, use MemorySaver + from langgraph.checkpoint.memory import MemorySaver + memory = MemorySaver() + graph = workflow.compile(checkpointer=memory) +else: + # When running in LangGraph API/dev, don't use a custom checkpointer + graph = workflow.compile()