Skip to content

Commit 669014b

Browse files
authored
Merge branch 'chore/g2-skills' into refactor-cli
2 parents f17b8c2 + a58cccb commit 669014b

21 files changed

Lines changed: 1725 additions & 1413 deletions

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,9 @@ pnpm-lock.yaml
6161

6262
# skills index
6363
src/index/
64+
65+
# Playground (Next.js)
66+
playground/.next/
67+
playground/node_modules/
68+
playground/.env.local
69+
playground/out/

playground/.env.example

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# AI Provider Configuration
2+
# Set at least one API key to use the playground
3+
4+
# Default provider (qwen, anthropic, openai, deepseek, kimi, glm)
5+
AI_PROVIDER=qwen
6+
AI_MODEL=qwen3-coder-480b-a35b-instruct
7+
8+
# Qwen (DashScope)
9+
QWEN_API_KEY=
10+
QWEN_API_ENDPOINT=https://dashscope.aliyuncs.com
11+
12+
# Anthropic Claude
13+
ANTHROPIC_API_KEY=
14+
ANTHROPIC_API_ENDPOINT=https://api.anthropic.com
15+
16+
# OpenAI
17+
OPENAI_API_KEY=
18+
19+
# DeepSeek
20+
DEEPSEEK_API_KEY=
21+
22+
# Kimi
23+
KIMI_API_KEY=
24+
25+
# GLM
26+
GLM_API_KEY=

playground/next-env.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// <reference types="next" />
2+
/// <reference types="next/image-types/global" />
3+
/// <reference path="./.next/types/routes.d.ts" />
4+
5+
// NOTE: This file should not be edited
6+
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

playground/next.config.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { NextConfig } from 'next';
2+
3+
const nextConfig: NextConfig = {
4+
// Transpile the workspace package
5+
transpilePackages: ['chart-visualization-skills'],
6+
// Experimental features for server components
7+
experimental: {
8+
serverActions: {
9+
allowedOrigins: ['localhost:3000']
10+
}
11+
}
12+
};
13+
14+
export default nextConfig;

playground/package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "playground",
3+
"version": "1.0.0",
4+
"private": true,
5+
"scripts": {
6+
"dev": "next dev",
7+
"build": "next build",
8+
"start": "next start",
9+
"lint": "next lint"
10+
},
11+
"dependencies": {
12+
"@antv/g2": "^5.4.8",
13+
"@antv/g6": "^5.0.42",
14+
"@monaco-editor/react": "^4.6.0",
15+
"chart-visualization-skills": "workspace:*",
16+
"next": "^15.2.4",
17+
"react": "^19.0.0",
18+
"react-dom": "^19.0.0"
19+
},
20+
"devDependencies": {
21+
"@types/node": "^22.13.0",
22+
"@types/react": "^19.0.0",
23+
"@types/react-dom": "^19.0.0",
24+
"typescript": "^5.7.0"
25+
}
26+
}
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import path from 'path';
3+
import { SkillRetriever } from '@/lib/retriever';
4+
import { callAI, AgentLoop } from '@/lib/ai-sdk';
5+
import {
6+
TOOLS,
7+
toolListReferences,
8+
toolReadSkills,
9+
buildSystemPrompt
10+
} from '@/lib/skill-tools';
11+
import {
12+
detectIntent,
13+
buildMessages,
14+
extractCodeFromMarkdown
15+
} from '@/lib/intent';
16+
import ProviderRegistry, { PROVIDERS } from '@/lib/provider-registry';
17+
18+
// Initialize retriever
19+
const retriever = new SkillRetriever();
20+
21+
// Load skills index on first request
22+
let skillsLoaded = false;
23+
function ensureSkillsLoaded() {
24+
if (!skillsLoaded) {
25+
try {
26+
const { skills } = retriever.loadIndex();
27+
console.log(`✅ Loaded ${skills.length} skills from index`);
28+
skillsLoaded = true;
29+
} catch (e) {
30+
console.error(
31+
'❌ Skills index not found. Run: node cli/skills-antv.js build'
32+
);
33+
}
34+
}
35+
}
36+
37+
// Resolve provider and model from request
38+
function resolveProviderModel(
39+
reqProvider?: string,
40+
reqModel?: string
41+
): { provider: string; model: string } {
42+
const provider = reqProvider || process.env.AI_PROVIDER || 'qwen';
43+
const model =
44+
reqModel ||
45+
process.env.AI_MODEL ||
46+
PROVIDERS[provider]?.defaultModel ||
47+
'qwen3-coder-480b-a35b-instruct';
48+
return { provider, model };
49+
}
50+
51+
// BM25 mode generation
52+
async function generateWithBM25(
53+
query: string,
54+
library: string,
55+
currentCode: string | null,
56+
provider: string,
57+
model: string
58+
) {
59+
const selectedLibrary =
60+
library === 'auto' ? retriever.detectLibrary(query) : library;
61+
62+
const { systemPrompt, primarySkills, extraSkills } = retriever.buildPrompt(
63+
query,
64+
{ library: selectedLibrary, topK: 5, maxExtra: 2 }
65+
);
66+
const retrievedSkills = [...primarySkills, ...extraSkills];
67+
const intent = detectIntent(query, currentCode);
68+
const messages = buildMessages(query, systemPrompt, intent, currentCode);
69+
70+
const response = await callAI({
71+
provider,
72+
model,
73+
messages,
74+
temperature: 0.3,
75+
maxTokens: 4000
76+
});
77+
78+
return {
79+
code: extractCodeFromMarkdown(response.content),
80+
selectedLibrary,
81+
retrievedSkills,
82+
intent
83+
};
84+
}
85+
86+
// Tool-call mode generation
87+
async function generateWithToolCall(
88+
query: string,
89+
library: string,
90+
currentCode: string | null,
91+
provider: string,
92+
model: string
93+
) {
94+
const selectedLibrary =
95+
library === 'auto' ? retriever.detectLibrary(query) : library;
96+
97+
const systemPrompt = buildSystemPrompt(selectedLibrary);
98+
const intent = detectIntent(query, currentCode);
99+
100+
let userMessage = `请根据以下描述生成 AntV ${selectedLibrary.toUpperCase()} 代码,只输出一个完整的 javascript 代码块:\n\n${query}`;
101+
if (intent === 'tune') {
102+
userMessage =
103+
`当前图表代码:\n\`\`\`javascript\n${currentCode}\n\`\`\`\n\n` +
104+
`请基于上面的代码,${query}。只输出修改后的完整 javascript 代码块。`;
105+
}
106+
107+
const agent = new AgentLoop({
108+
provider,
109+
model,
110+
tools: TOOLS,
111+
maxRounds: 6,
112+
debug: false,
113+
toolHandlers: {
114+
list_references: toolListReferences,
115+
read_skills: toolReadSkills
116+
}
117+
});
118+
119+
const result = await agent.run(systemPrompt, userMessage);
120+
const code = extractCodeFromMarkdown(result.content);
121+
const loadedSkillPaths = result.toolCallsLog
122+
.filter((l) => l.tool === 'read_skills')
123+
.flatMap((l) => l.args.paths || []);
124+
125+
console.log(
126+
` Tool calls: ${result.toolCallsLog.length}, loaded: ${loadedSkillPaths.map((p) => path.basename(p, '.md')).join(', ')}`
127+
);
128+
129+
return {
130+
code,
131+
selectedLibrary,
132+
toolCallsLog: result.toolCallsLog,
133+
loadedSkillPaths
134+
};
135+
}
136+
137+
export async function POST(request: NextRequest) {
138+
ensureSkillsLoaded();
139+
140+
const body = await request.json();
141+
const {
142+
query,
143+
library = 'auto',
144+
currentCode = null,
145+
mode = 'bm25',
146+
provider: reqProvider,
147+
model: reqModel
148+
} = body;
149+
150+
if (!query) {
151+
return NextResponse.json({ error: 'Query is required' }, { status: 400 });
152+
}
153+
154+
const { provider, model } = resolveProviderModel(reqProvider, reqModel);
155+
156+
if (!ProviderRegistry.hasProvider(provider)) {
157+
return NextResponse.json(
158+
{ error: `Unknown provider: ${provider}` },
159+
{ status: 400 }
160+
);
161+
}
162+
if (!ProviderRegistry.hasApiKey(provider)) {
163+
return NextResponse.json(
164+
{ error: `Missing API key for provider: ${provider}` },
165+
{ status: 400 }
166+
);
167+
}
168+
169+
console.log(
170+
`\n[${mode}] "${query.substring(0, 60)}" (${library}) | ${provider}/${model}`
171+
);
172+
173+
try {
174+
if (mode === 'tool-call') {
175+
const { code, selectedLibrary, toolCallsLog, loadedSkillPaths } =
176+
await generateWithToolCall(
177+
query,
178+
library,
179+
currentCode,
180+
provider,
181+
model
182+
);
183+
184+
return NextResponse.json({
185+
code,
186+
library: selectedLibrary,
187+
mode: 'tool-call',
188+
provider,
189+
model,
190+
skills: loadedSkillPaths.map((p) => ({
191+
id: path.basename(p, '.md'),
192+
title: path.basename(p, '.md')
193+
})),
194+
toolCallsCount: toolCallsLog.length
195+
});
196+
}
197+
198+
const { code, selectedLibrary, retrievedSkills, intent } =
199+
await generateWithBM25(query, library, currentCode, provider, model);
200+
201+
console.log(
202+
`[bm25] ${selectedLibrary.toUpperCase()} [${intent}], skills: ${retrievedSkills.length}`
203+
);
204+
205+
return NextResponse.json({
206+
code,
207+
library: selectedLibrary,
208+
mode: 'bm25',
209+
provider,
210+
model,
211+
skills: retrievedSkills.map((s) => ({ id: s.id, title: s.title })),
212+
intent
213+
});
214+
} catch (error) {
215+
console.error('Generation error:', error);
216+
const message = error instanceof Error ? error.message : 'Unknown error';
217+
return NextResponse.json({ error: message }, { status: 500 });
218+
}
219+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { NextResponse } from 'next/server';
2+
import ProviderRegistry from '@/lib/provider-registry';
3+
4+
export async function GET() {
5+
const providers = ProviderRegistry.listProviders().map((p) => ({
6+
id: p.id,
7+
name: p.name,
8+
models: p.models,
9+
hasApiKey: p.hasApiKey
10+
}));
11+
12+
return NextResponse.json({ providers });
13+
}

0 commit comments

Comments
 (0)