Skip to content

Commit 602bb3f

Browse files
committed
feat(docs-ai): switch ai panel props and guide intent to llms fetch
1 parent d7ad47b commit 602bb3f

File tree

12 files changed

+1755
-266
lines changed

12 files changed

+1755
-266
lines changed

docs/app/api/chat/route.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { createOpenAI } from "@ai-sdk/openai";
22
import { convertToModelMessages, safeValidateUIMessages, stepCountIs, streamText } from "ai";
3-
import { systemPrompt } from "@/lib/ai/system-prompt";
4-
import { clientTools } from "@/lib/ai/tools";
3+
import { buildSystemPrompt } from "@/lib/ai/system-prompt";
4+
import { createClientTools } from "@/lib/ai/tools";
55
import { getMCPTools } from "@/lib/ai/mcp-client";
6+
import { detectComponentGuideIntent, extractLatestUserText } from "@/lib/ai/component-guide-intent";
7+
import { resolveComponentGuideLinks } from "@/lib/ai/component-guide-links";
68
import { z } from "zod";
79

810
const llmRouter = createOpenAI({
@@ -22,6 +24,8 @@ const chatRequestSchema = z.object({
2224
});
2325

2426
export async function POST(req: Request) {
27+
const baseUrl = new URL(req.url).origin;
28+
2529
let body: unknown;
2630
try {
2731
body = await req.json();
@@ -35,6 +39,7 @@ export async function POST(req: Request) {
3539
}
3640

3741
const mcpTools = await getMCPTools();
42+
const clientTools = createClientTools({ baseUrl });
3843
const tools = {
3944
...clientTools,
4045
...mcpTools,
@@ -48,14 +53,33 @@ export async function POST(req: Request) {
4853
return Response.json({ error: "Invalid messages format" }, { status: 400 });
4954
}
5055

56+
const latestUserText = extractLatestUserText(validatedMessages.data);
57+
const componentGuideIntent = latestUserText
58+
? await detectComponentGuideIntent(latestUserText, { baseUrl })
59+
: null;
60+
const componentGuideLinks = componentGuideIntent
61+
? await resolveComponentGuideLinks({
62+
componentId: componentGuideIntent.component.id,
63+
baseUrl,
64+
})
65+
: [];
66+
5167
const messages = await convertToModelMessages(validatedMessages.data, {
5268
tools,
5369
ignoreIncompleteToolCalls: true,
5470
});
5571

5672
const result = streamText({
5773
model: llmRouter(llmRouterModel),
58-
system: systemPrompt,
74+
system: buildSystemPrompt({
75+
componentGuide: componentGuideIntent
76+
? {
77+
componentId: componentGuideIntent.component.id,
78+
userQuery: componentGuideIntent.question,
79+
links: componentGuideLinks,
80+
}
81+
: null,
82+
}),
5983
messages,
6084
tools,
6185
stopWhen: stepCountIs(8),

docs/content/ai-integration/llms-txt.mdx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,16 @@ SEED Design은 계층적인 llms.txt 구조를 제공합니다. 루트 진입점
3434
<Folder name="react" defaultOpen>
3535
<File name="llms.txt" />
3636
<File name="llms-full.txt" />
37-
<File name="llms-components.txt" />
38-
<File name="llms-getting-started.txt" />
39-
<File name="llms-stackflow.txt" />
40-
<File name="llms-developer-tools.txt" />
41-
<File name="llms-migration.txt" />
42-
<File name="llms-updates.txt" />
43-
<Folder name="llms-components">
44-
<File name="action-button.txt" />
45-
<File name="..." />
37+
</Folder>
38+
<Folder name="llms" defaultOpen>
39+
<Folder name="react" defaultOpen>
40+
<Folder name="components" defaultOpen>
41+
<File name="action-button.txt" />
42+
<Folder name="layout">
43+
<File name="box.txt" />
44+
</Folder>
45+
<File name="..." />
46+
</Folder>
4647
</Folder>
4748
</Folder>
4849
<Folder name="breeze">
@@ -75,6 +76,7 @@ SEED Design은 계층적인 llms.txt 구조를 제공합니다. 루트 진입점
7576
자주 사용되는 문서에 빠르게 접근할 수 있습니다.
7677

7778
- **전체 React 문서**: [/react/llms.txt](https://seed-design.io/react/llms.txt) - React 라이브러리의 모든 문서를 하나의 파일로 제공
79+
- **개별 React 컴포넌트 문서**: `/llms/react/components/{component}.txt` (예: [/llms/react/components/layout/box.txt](https://seed-design.io/llms/react/components/layout/box.txt))
7880
- **컴포넌트 가이드라인**: [/docs/llms.txt](https://seed-design.io/docs/llms.txt) - 디자인 가이드라인 문서 진입점
7981

8082
## 사용 예시
@@ -90,7 +92,7 @@ SEED Design React 라이브러리를 사용해서 버튼 컴포넌트를 구현
9092
### 특정 컴포넌트 문서 참조
9193

9294
```
93-
@https://seed-design.io/react/llms-components/action-button.txt
95+
@https://seed-design.io/llms/react/components/action-button.txt
9496
9597
ActionButton 컴포넌트의 variant 옵션에 대해 설명해주세요.
9698
```
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
2+
import {
3+
clearComponentGuideIntentCache,
4+
detectComponentGuideIntent,
5+
extractLatestUserText,
6+
} from "./component-guide-intent";
7+
import { clearLlmsPropsCache } from "./llms-props";
8+
9+
const SAMPLE_REACT_LLMS_INDEX = `# SEED Design React - LLM Reference
10+
11+
### components
12+
13+
- [Box](https://seed-design.io/llms/react/components/layout/box.txt)
14+
- [Alert Dialog](https://seed-design.io/llms/react/components/alert-dialog.txt)
15+
`;
16+
17+
describe("detectComponentGuideIntent", () => {
18+
const originalFetch = globalThis.fetch;
19+
const componentIds = ["alert-dialog", "action-button", "checkbox"];
20+
21+
beforeEach(() => {
22+
clearComponentGuideIntentCache();
23+
clearLlmsPropsCache();
24+
});
25+
26+
afterEach(() => {
27+
clearComponentGuideIntentCache();
28+
clearLlmsPropsCache();
29+
globalThis.fetch = originalFetch;
30+
});
31+
32+
it("detects Korean usage query with spaced component name", async () => {
33+
const result = await detectComponentGuideIntent("alert dialog 어떻게 사용해?", { componentIds });
34+
35+
expect(result?.type).toBe("component-guide");
36+
expect(result?.component.id).toBe("alert-dialog");
37+
});
38+
39+
it("detects PascalCase component query", async () => {
40+
const result = await detectComponentGuideIntent("AlertDialog 설치 방법 알려줘", { componentIds });
41+
42+
expect(result?.component.id).toBe("alert-dialog");
43+
});
44+
45+
it("detects kebab-case component props query", async () => {
46+
const result = await detectComponentGuideIntent("alert-dialog props 알려줘", { componentIds });
47+
48+
expect(result?.component.id).toBe("alert-dialog");
49+
});
50+
51+
it("returns null when query is not a guide question", async () => {
52+
const result = await detectComponentGuideIntent("최근 배포 이슈 요약해줘", { componentIds });
53+
54+
expect(result).toBeNull();
55+
});
56+
57+
it("loads component ids from react/llms.txt using baseUrl", async () => {
58+
globalThis.fetch = (async (input) => {
59+
const url = typeof input === "string" ? input : input.url;
60+
if (url === "https://seed-design.io/react/llms.txt") {
61+
return new Response(SAMPLE_REACT_LLMS_INDEX, { status: 200 });
62+
}
63+
64+
return new Response("not found", { status: 404 });
65+
}) as typeof fetch;
66+
67+
const result = await detectComponentGuideIntent("box props는 어떤게 있어?", {
68+
baseUrl: "https://seed-design.io",
69+
});
70+
71+
expect(result?.type).toBe("component-guide");
72+
expect(result?.component.id).toBe("box");
73+
});
74+
});
75+
76+
describe("extractLatestUserText", () => {
77+
it("extracts latest user text from message parts", () => {
78+
const messages = [
79+
{
80+
role: "user",
81+
parts: [{ type: "text", text: "이전 질문" }],
82+
},
83+
{
84+
role: "assistant",
85+
parts: [{ type: "text", text: "이전 답변" }],
86+
},
87+
{
88+
role: "user",
89+
parts: [{ type: "text", text: "alert dialog 어떻게 사용해?" }],
90+
},
91+
];
92+
93+
expect(extractLatestUserText(messages)).toBe("alert dialog 어떻게 사용해?");
94+
});
95+
});

0 commit comments

Comments
 (0)