Skip to content

Commit 9b8ed53

Browse files
XinyueDuduxinyue.dxy逍为
authored
feat(ava): add render demo (#829)
* feat(ava): add render demo * chore: fix issue --------- Co-authored-by: duxinyue.dxy <duxinyue.dxy@antgroup.com> Co-authored-by: 逍为 <xiaowei.wzw@antgroup.com>
1 parent b13cfdb commit 9b8ed53

17 files changed

Lines changed: 539 additions & 99 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { bindRenderer } from "../../src/";
2+
import { RENDERER } from "../../src/bind";
3+
4+
describe('bindRenderer', () => {
5+
it('bindRenderer', async () => {
6+
expect(RENDERER).toBeNull();
7+
const mockRenderer = jest.fn();
8+
bindRenderer(mockRenderer);
9+
expect(RENDERER).toBe(mockRenderer);
10+
});
11+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Advisor, bindRenderer } from "../../src/";
2+
3+
describe('index', () => {
4+
it('Advisor', async () => {
5+
expect(Advisor).toBeDefined();
6+
});
7+
8+
it('bindRenderer', async () => {
9+
expect(bindRenderer).toBeInstanceOf(Function);
10+
});
11+
});

packages/ava/src/advisor/advisor.ts

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,18 @@
1-
import { logError } from '@ava/utils';
1+
import { logError } from '../utils';
22
import {
33
AdviseChartParams,
44
AdviseStageOutput,
55
AdviseText,
66
AdviseTextParams,
77
AdvisorConfig,
88
BasePipeline,
9-
Renderer,
10-
RenderParams,
11-
} from '@ava/types';
12-
import { extractData } from '@ava/data';
13-
9+
} from '../types';
10+
import { extractData } from '../data';
1411
import { AdviseChartPipeline } from './advise-chart-pipeline/pipeline';
1512
import { AdviseTextPipeline } from './advise-text-pipeline/pipeline';
13+
import { RENDERER, type Spec } from '../bind';
1614

1715
export class Advisor {
18-
static RENDERER: Renderer;
19-
20-
static bindRenderer(renderer: Renderer) {
21-
Advisor.RENDERER = renderer;
22-
}
2316

2417
config!: AdvisorConfig;
2518

@@ -61,9 +54,10 @@ export class Advisor {
6154
// return {} as AdviseText;
6255
}
6356

64-
render(params: RenderParams) {
65-
if (Advisor.RENDERER) {
66-
return Advisor.RENDERER(params);
57+
render(params: { container: string; spec: Spec }) {
58+
if (RENDERER) {
59+
const { container, spec } = params;
60+
return RENDERER(container, spec);
6761
}
6862
logError('Chart render not configured');
6963
return null;

packages/ava/src/advisor/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export { Advisor } from './advisor';
1+
export { Advisor, bindRenderer } from './advisor';

packages/ava/src/bind.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { Spec } from '@antv/gpt-vis';
2+
3+
export { Spec };
4+
export type Renderer = (container: string, spec: Spec) => void;
5+
6+
// The renderer function bound from outside.
7+
// Default is null.
8+
// We suggest to bind the renderer function during initialization with AntV GPT-Vis and mcp.
9+
export let RENDERER: Renderer | null = null;
10+
11+
export function bindRenderer(fn: Renderer) {
12+
RENDERER = fn;
13+
}

packages/ava/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* advisor */
22
export { Advisor } from './advisor';
3+
export { bindRenderer, type Spec } from './bind';
34

45
/* CKB */
56
export {
@@ -66,4 +67,4 @@ export type {
6667
CkbConfig,
6768
} from './ckb';
6869

69-
export type { AdviseChart, Data, Meta, DataTypeMap, RenderParams } from './types';
70+
export type { AdviseChart, Meta, DataTypeMap } from './types';

packages/ava/src/types/advisor.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { FieldDataType, Meta } from '@ava/types/data';
22

3-
import { UiConfig } from './render';
3+
import { UiConfig, Renderer } from './render';
44

55
/**
66
* Common LLM configuration properties
@@ -39,6 +39,8 @@ export interface AdvisorConfig {
3939
includes?: string[];
4040
/** Excluded charts from recommendation */
4141
excludes?: string[];
42+
/** Custom renderer for this advisor instance */
43+
renderer?: Renderer;
4244
}
4345

4446
export interface AdviseChartParams {

packages/ava/src/types/render.ts

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
import { Spec } from '@antv/gpt-vis';
21

3-
import { CHART_NAME, CHART_PURPOSE } from '../constants/advisor';
4-
5-
import { Data, Meta } from './data';
2+
import { CHART_PURPOSE } from '../constants/advisor';
63

74
export type TrendData = Array<{
85
time: string;
@@ -28,20 +25,6 @@ export type DataTypeMap = {
2825
[CHART_PURPOSE.Comparison]: ComparisonData;
2926
};
3027

31-
export interface RenderParams {
32-
container: string;
33-
type: CHART_NAME;
34-
encode: {
35-
[property: string]: string[];
36-
};
37-
/** AntV-spec */
38-
spec: Spec;
39-
data: Data;
40-
metas: Meta[];
41-
}
42-
43-
export type Renderer = (params: RenderParams) => void;
44-
4528
export type UiConfig = {
4629
palette?: string[];
4730
width?: number;

playground/src/App.tsx

Lines changed: 115 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,120 @@ import React from 'react';
22

33
import { BrowserRouter as Router, Routes, Route, Link, useLocation } from 'react-router-dom';
44
import { Layout, Menu } from 'antd';
5-
import { Advisor } from '@antv/ava';
5+
import type { MenuProps } from 'antd';
66

7-
import AdviseSummary from './examples/advise-summary';
8-
import MultipleChartsDemo from './examples/advise-summary/multiple';
9-
import { chartRenderer } from './utils/renderer';
7+
import AdviseSummary from './examples/advise';
8+
import MultipleChartsDemo from './examples/advise/multiple';
109

1110
import './App.css';
11+
import RenderCustom from './examples/render/custom';
12+
import RenderDefault from './examples/render/default';
13+
import RenderDemand from './examples/render/demand';
1214

1315
const { Header, Content, Sider } = Layout;
1416

15-
// 全局只绑定一次渲染器
16-
Advisor.bindRenderer(chartRenderer as any);
17+
// 菜单项配置类型
18+
interface MenuItemConfig {
19+
key: string;
20+
label: string;
21+
path: string;
22+
component: React.ComponentType;
23+
}
1724

18-
// 路由到菜单项的映射
19-
const routeToMenuKey: Record<string, string> = {
20-
'/': 'advise-summary-basic',
21-
'/advise-summary': 'advise-summary-basic',
22-
'/advise-summary-multiple': 'advise-summary-multiple',
25+
interface SubMenuConfig {
26+
key: string;
27+
label: string;
28+
children: MenuItemConfig[];
29+
}
30+
31+
interface MenuConfig {
32+
key: string;
33+
label: string;
34+
children: SubMenuConfig[];
35+
}
36+
37+
// 菜单配置
38+
const menuConfig: MenuConfig[] = [
39+
{
40+
key: 'advice',
41+
label: '图表示例',
42+
children: [
43+
{
44+
key: 'advise-summary',
45+
label: '图表推荐',
46+
children: [
47+
{
48+
key: 'advise-summary-basic',
49+
label: '基础示例',
50+
path: '/advise-summary',
51+
component: AdviseSummary,
52+
},
53+
{
54+
key: 'advise-summary-multiple',
55+
label: '多图表示例',
56+
path: '/advise-summary-multiple',
57+
component: MultipleChartsDemo,
58+
},
59+
],
60+
},
61+
{
62+
key: 'render-summary',
63+
label: '图表渲染',
64+
children: [
65+
{
66+
key: 'render-summary-basic',
67+
label: '基础示例',
68+
path: '/render-summary',
69+
component: RenderDefault,
70+
},
71+
{
72+
key: 'render-summary-demand',
73+
label: 'GPT-Vis 按需引用',
74+
path: '/render-demand',
75+
component: RenderDemand,
76+
},
77+
{
78+
key: 'render-summary-custom',
79+
label: '自定义示例',
80+
path: '/render-custom',
81+
component: RenderCustom,
82+
},
83+
],
84+
},
85+
],
86+
},
87+
];
88+
89+
// 从配置生成路由映射
90+
const routeToMenuKey: Record<string, string> = {};
91+
const routeComponents: Array<{ path: string; component: React.ComponentType }> = [];
92+
93+
menuConfig.forEach((topMenu) => {
94+
topMenu.children.forEach((subMenu) => {
95+
subMenu.children.forEach((item) => {
96+
routeToMenuKey[item.path] = item.key;
97+
routeComponents.push({ path: item.path, component: item.component });
98+
});
99+
});
100+
});
101+
102+
// 设置默认路由
103+
routeToMenuKey['/'] = 'advise-summary-basic';
104+
105+
// 从配置生成菜单项
106+
const generateMenuItems = (config: MenuConfig[]): MenuProps['items'] => {
107+
return config.map((topMenu) => ({
108+
key: topMenu.key,
109+
label: topMenu.label,
110+
children: topMenu.children.map((subMenu) => ({
111+
key: subMenu.key,
112+
label: subMenu.label,
113+
children: subMenu.children.map((item) => ({
114+
key: item.key,
115+
label: <Link to={item.path}>{item.label}</Link>,
116+
})),
117+
})),
118+
}));
23119
};
24120

25121
const AppContent: React.FC = () => {
@@ -30,38 +126,28 @@ const AppContent: React.FC = () => {
30126
<Layout style={{ minHeight: '100vh' }}>
31127
<Header style={{ color: 'white', fontSize: '24px', fontWeight: 'bold' }}>AVA Playground</Header>
32128
<Layout>
33-
<Sider width={220} style={{ background: '#fff' }}>
129+
<Sider width={220}>
34130
<Menu
35131
mode="inline"
36132
selectedKeys={[selectedKey]}
37-
defaultOpenKeys={['advice', 'advise-summary']}
133+
defaultOpenKeys={['advice', 'advise-summary', 'render-summary']}
38134
style={{ height: '100%', borderRight: 0 }}
39-
>
40-
<Menu.SubMenu key="advice" title="Advice Examples">
41-
<Menu.SubMenu key="advise-summary" title="Advise Summary">
42-
<Menu.Item key="advise-summary-basic">
43-
<Link to="/advise-summary">基础示例</Link>
44-
</Menu.Item>
45-
<Menu.Item key="advise-summary-multiple">
46-
<Link to="/advise-summary-multiple">多图表示例</Link>
47-
</Menu.Item>
48-
</Menu.SubMenu>
49-
</Menu.SubMenu>
50-
</Menu>
135+
items={generateMenuItems(menuConfig)}
136+
/>
51137
</Sider>
52-
<Layout style={{ padding: '24px' }}>
138+
<Layout>
53139
<Content
54140
style={{
55141
background: '#fff',
56-
padding: 24,
57142
margin: 0,
58143
minHeight: 280,
59144
}}
60145
>
61146
<Routes>
62147
<Route path="/" element={<AdviseSummary />} />
63-
<Route path="/advise-summary" element={<AdviseSummary />} />
64-
<Route path="/advise-summary-multiple" element={<MultipleChartsDemo />} />
148+
{routeComponents.map(({ path, component: Component }) => (
149+
<Route key={path} path={path} element={<Component />} />
150+
))}
65151
</Routes>
66152
</Content>
67153
</Layout>

playground/src/examples/advise-summary/index.tsx renamed to playground/src/examples/advise/index.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import React, { useState } from 'react';
1+
import React, { useEffect, useState } from 'react';
22

33
import { Input, Button, Space, Card, message } from 'antd';
4-
import { Advisor } from '@antv/ava';
4+
import { Advisor, bindRenderer } from '@antv/ava';
5+
import { render } from '../../utils';
56

6-
// 渲染器已在 App.tsx 中全局绑定,这里直接使用
7+
// 创建 advisor 实例并为该实例绑定渲染器
78
const advisor = new Advisor({
89
llm: {
910
appId: '202510APxPmo00551539',
@@ -22,6 +23,10 @@ const sampleData = [
2223
const AdviseSummary: React.FC = () => {
2324
const [data, setData] = useState(JSON.stringify(sampleData, null, 2));
2425

26+
useEffect(() => {
27+
bindRenderer(render as any);
28+
}, []);
29+
2530
const advise = async () => {
2631
// 安全解析数据,失败不抛错
2732
let parsedData: any = null;

0 commit comments

Comments
 (0)