Skip to content

Commit 792841a

Browse files
authored
refactor(dojo): add suggestions, improve styling, simplify code (#484)
* refactor(dojo): add suggestions, improve styling, simplify code - more suggestions across examples - improve code editor styling and add horizontal tab layout - improve look and stability of tool-based gen-ui --------- Signed-off-by: Tyler Slaton <[email protected]>
1 parent 14d051e commit 792841a

File tree

17 files changed

+1283
-991
lines changed

17 files changed

+1283
-991
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema.json",
3+
"style": "new-york",
4+
"rsc": true,
5+
"tsx": true,
6+
"tailwind": {
7+
"config": "tailwind.config.ts",
8+
"css": "src/app/globals.css",
9+
"baseColor": "neutral",
10+
"cssVariables": true,
11+
"prefix": ""
12+
},
13+
"iconLibrary": "lucide",
14+
"aliases": {
15+
"components": "@/components",
16+
"utils": "@/lib/utils",
17+
"ui": "@/components/ui",
18+
"lib": "@/lib",
19+
"hooks": "@/hooks"
20+
},
21+
"registries": {}
22+
}
Lines changed: 80 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,65 @@
1-
import { Page, Locator, expect } from '@playwright/test';
1+
import { Page, Locator, expect } from "@playwright/test";
22

33
export class ToolBaseGenUIPage {
44
readonly page: Page;
55
readonly haikuAgentIntro: Locator;
66
readonly messageBox: Locator;
77
readonly sendButton: Locator;
88
readonly applyButton: Locator;
9-
readonly appliedButton: Locator;
109
readonly haikuBlock: Locator;
1110
readonly japaneseLines: Locator;
11+
readonly mainHaikuDisplay: Locator;
1212

1313
constructor(page: Page) {
1414
this.page = page;
15-
this.haikuAgentIntro = page.getByText("I'm a haiku generator 👋. How can I help you?");
16-
this.messageBox = page.getByRole('textbox', { name: 'Type a message...' });
17-
this.sendButton = page.locator('[data-test-id="copilot-chat-ready"]');
15+
this.haikuAgentIntro = page.getByText("I'm a haiku generator 👋. How can I help you?").first();
16+
this.messageBox = page.getByPlaceholder("Type a message...").first();
17+
this.sendButton = page.locator('[data-test-id="copilot-chat-ready"]').first();
1818
this.haikuBlock = page.locator('[data-testid="haiku-card"]');
19-
this.applyButton = page.getByRole('button', { name: 'Apply' });
20-
this.japaneseLines = page.locator('[data-testid="haiku-line"]');
19+
this.applyButton = page.getByRole("button", { name: "Apply" });
20+
this.japaneseLines = page.locator('[data-testid="haiku-japanese-line"]');
21+
this.mainHaikuDisplay = page.locator('[data-testid="haiku-carousel"]');
2122
}
2223

2324
async generateHaiku(message: string) {
25+
// Wait for either sidebar or popup to be ready
26+
await this.page.waitForTimeout(2000);
27+
await this.messageBox.waitFor({ state: "visible", timeout: 15000 });
2428
await this.messageBox.click();
2529
await this.messageBox.fill(message);
30+
await this.page.waitForTimeout(1000);
31+
await this.sendButton.waitFor({ state: "visible", timeout: 15000 });
2632
await this.sendButton.click();
33+
await this.page.waitForTimeout(2000);
2734
}
2835

2936
async checkGeneratedHaiku() {
30-
await this.page.locator('[data-testid="haiku-card"]').last().isVisible();
31-
const mostRecentCard = this.page.locator('[data-testid="haiku-card"]').last();
32-
await mostRecentCard.locator('[data-testid="haiku-line"]').first().waitFor({ state: 'visible', timeout: 10000 });
37+
await this.page.waitForTimeout(3000);
38+
const cards = this.page.locator('[data-testid="haiku-card"]');
39+
await cards.last().waitFor({ state: "visible", timeout: 20000 });
40+
const mostRecentCard = cards.last();
41+
await mostRecentCard
42+
.locator('[data-testid="haiku-japanese-line"]')
43+
.first()
44+
.waitFor({ state: "visible", timeout: 20000 });
3345
}
3446

3547
async extractChatHaikuContent(page: Page): Promise<string> {
36-
await page.waitForTimeout(3000);
37-
await page.locator('[data-testid="haiku-card"]').first().waitFor({ state: 'visible' });
48+
await page.waitForTimeout(4000);
3849
const allHaikuCards = page.locator('[data-testid="haiku-card"]');
50+
await allHaikuCards.first().waitFor({ state: "visible", timeout: 15000 });
3951
const cardCount = await allHaikuCards.count();
4052
let chatHaikuContainer;
4153
let chatHaikuLines;
4254

4355
for (let cardIndex = cardCount - 1; cardIndex >= 0; cardIndex--) {
4456
chatHaikuContainer = allHaikuCards.nth(cardIndex);
45-
chatHaikuLines = chatHaikuContainer.locator('[data-testid="haiku-line"]');
57+
chatHaikuLines = chatHaikuContainer.locator('[data-testid="haiku-japanese-line"]');
4658
const linesCount = await chatHaikuLines.count();
4759

4860
if (linesCount > 0) {
4961
try {
50-
await chatHaikuLines.first().waitFor({ state: 'visible', timeout: 5000 });
62+
await chatHaikuLines.first().waitFor({ state: "visible", timeout: 8000 });
5163
break;
5264
} catch (error) {
5365
continue;
@@ -56,59 +68,94 @@ export class ToolBaseGenUIPage {
5668
}
5769

5870
if (!chatHaikuLines) {
59-
throw new Error('No haiku cards with visible lines found');
71+
throw new Error("No haiku cards with visible lines found");
6072
}
6173

6274
const count = await chatHaikuLines.count();
6375
const lines: string[] = [];
6476

6577
for (let i = 0; i < count; i++) {
6678
const haikuLine = chatHaikuLines.nth(i);
67-
const japaneseText = await haikuLine.locator('p').first().innerText();
79+
const japaneseText = await haikuLine.innerText();
6880
lines.push(japaneseText);
6981
}
7082

71-
const chatHaikuContent = lines.join('').replace(/\s/g, '');
83+
const chatHaikuContent = lines.join("").replace(/\s/g, "");
7284
return chatHaikuContent;
7385
}
7486

7587
async extractMainDisplayHaikuContent(page: Page): Promise<string> {
76-
const mainDisplayLines = page.locator('[data-testid="main-haiku-line"]');
88+
await page.waitForTimeout(2000);
89+
const carousel = page.locator('[data-testid="haiku-carousel"]');
90+
await carousel.waitFor({ state: "visible", timeout: 10000 });
91+
92+
// Find the visible carousel item (the active slide)
93+
const carouselItems = carousel.locator('[data-testid^="carousel-item-"]');
94+
const itemCount = await carouselItems.count();
95+
let activeCard = null;
96+
97+
// Find the visible/active carousel item
98+
for (let i = 0; i < itemCount; i++) {
99+
const item = carouselItems.nth(i);
100+
const isVisible = await item.isVisible();
101+
if (isVisible) {
102+
activeCard = item.locator('[data-testid="haiku-card"]');
103+
break;
104+
}
105+
}
106+
107+
if (!activeCard) {
108+
// Fallback to first card if none found visible
109+
activeCard = carousel.locator('[data-testid="haiku-card"]').first();
110+
}
111+
112+
const mainDisplayLines = activeCard.locator('[data-testid="haiku-japanese-line"]');
77113
const mainCount = await mainDisplayLines.count();
78114
const lines: string[] = [];
79115

80116
if (mainCount > 0) {
81117
for (let i = 0; i < mainCount; i++) {
82118
const haikuLine = mainDisplayLines.nth(i);
83-
const japaneseText = await haikuLine.locator('p').first().innerText();
119+
const japaneseText = await haikuLine.innerText();
84120
lines.push(japaneseText);
85121
}
86122
}
87123

88-
const mainHaikuContent = lines.join('').replace(/\s/g, '');
124+
const mainHaikuContent = lines.join("").replace(/\s/g, "");
89125
return mainHaikuContent;
90126
}
91127

92128
async checkHaikuDisplay(page: Page): Promise<void> {
93129
const chatHaikuContent = await this.extractChatHaikuContent(page);
94130

95-
await page.waitForTimeout(5000);
131+
await page.waitForTimeout(3000);
96132

97-
const mainHaikuContent = await this.extractMainDisplayHaikuContent(page);
133+
// Check that the haiku exists somewhere in the carousel
134+
const carousel = page.locator('[data-testid="haiku-carousel"]');
135+
await carousel.waitFor({ state: "visible", timeout: 10000 });
98136

99-
if (mainHaikuContent === '') {
100-
expect(chatHaikuContent.length).toBeGreaterThan(0);
101-
return;
102-
}
137+
const allCarouselCards = carousel.locator('[data-testid="haiku-card"]');
138+
const cardCount = await allCarouselCards.count();
103139

104-
if (chatHaikuContent === mainHaikuContent) {
105-
expect(mainHaikuContent).toBe(chatHaikuContent);
106-
} else {
107-
await page.waitForTimeout(3000);
140+
let foundMatch = false;
141+
for (let i = 0; i < cardCount; i++) {
142+
const card = allCarouselCards.nth(i);
143+
const lines = card.locator('[data-testid="haiku-japanese-line"]');
144+
const lineCount = await lines.count();
145+
const cardLines: string[] = [];
108146

109-
const updatedMainContent = await this.extractMainDisplayHaikuContent(page);
147+
for (let j = 0; j < lineCount; j++) {
148+
const text = await lines.nth(j).innerText();
149+
cardLines.push(text);
150+
}
110151

111-
expect(updatedMainContent).toBe(chatHaikuContent);
152+
const cardContent = cardLines.join("").replace(/\s/g, "");
153+
if (cardContent === chatHaikuContent) {
154+
foundMatch = true;
155+
break;
156+
}
112157
}
158+
159+
expect(foundMatch).toBe(true);
113160
}
114-
}
161+
}

typescript-sdk/apps/dojo/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"@ag-ui/server-starter": "workspace:*",
2424
"@ag-ui/server-starter-all-features": "workspace:*",
2525
"@ag-ui/vercel-ai-sdk": "workspace:*",
26+
"@ai-sdk/openai": "^2.0.42",
2627
"@copilotkit/react-core": "1.10.5",
2728
"@copilotkit/react-ui": "1.10.5",
2829
"@copilotkit/runtime": "1.10.5",
@@ -34,15 +35,14 @@
3435
"@mastra/libsql": "^0.15.0",
3536
"@mastra/loggers": "^0.10.14",
3637
"@mastra/memory": "^0.15.4",
37-
"@ai-sdk/openai": "^2.0.42",
3838
"@mdx-js/loader": "^3.1.0",
3939
"@mdx-js/mdx": "^3.1.0",
4040
"@mdx-js/react": "^3.1.0",
4141
"@monaco-editor/react": "^4.7.0",
4242
"@next/mdx": "^15.2.3",
4343
"@phosphor-icons/react": "^2.1.10",
4444
"@radix-ui/react-dropdown-menu": "^2.1.6",
45-
"@radix-ui/react-slot": "^1.1.2",
45+
"@radix-ui/react-slot": "^1.2.3",
4646
"@radix-ui/react-tabs": "^1.1.3",
4747
"@tiptap/extension-color": "^2.11.5",
4848
"@tiptap/extension-placeholder": "^2.11.5",
@@ -54,6 +54,7 @@
5454
"clsx": "^2.1.1",
5555
"dedent": "^1.7.0",
5656
"diff": "^7.0.0",
57+
"embla-carousel-react": "^8.6.0",
5758
"fast-json-patch": "^3.1.1",
5859
"lucide-react": "^0.477.0",
5960
"markdown-it": "^14.1.0",
@@ -66,7 +67,7 @@
6667
"react-markdown": "^10.1.0",
6768
"react-syntax-highlighter": "^15.6.1",
6869
"rxjs": "7.8.1",
69-
"tailwind-merge": "^3.0.2",
70+
"tailwind-merge": "^3.3.0",
7071
"tailwindcss-animate": "^1.0.7",
7172
"untruncate-json": "^0.0.1",
7273
"uuid": "^11.1.0",

typescript-sdk/apps/dojo/src/app/[integrationId]/feature/agentic_chat/page.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,25 @@ const Chat = () => {
5050
});
5151

5252
return (
53-
<div className="flex justify-center items-center h-full w-full" data-testid="background-container" style={{ background }}>
53+
<div
54+
className="flex justify-center items-center h-full w-full"
55+
data-testid="background-container"
56+
style={{ background }}
57+
>
5458
<div className="h-full w-full md:w-8/10 md:h-8/10 rounded-lg">
5559
<CopilotChat
5660
className="h-full rounded-2xl"
5761
labels={{ initial: "Hi, I'm an agent. Want to chat?" }}
62+
suggestions={[
63+
{
64+
title: "Change background",
65+
message: "Change the background to something new.",
66+
},
67+
{
68+
title: "Generate sonnet",
69+
message: "Write a short sonnet about AI.",
70+
},
71+
]}
5872
/>
5973
</div>
6074
</div>

0 commit comments

Comments
 (0)