update
This commit is contained in:
@@ -25,7 +25,7 @@
|
||||
"@astrojs/vue": "^5.1.3",
|
||||
"@kevisual/cache": "^0.0.5",
|
||||
"@kevisual/context": "^0.0.4",
|
||||
"@kevisual/query": "^0.0.33",
|
||||
"@kevisual/query": "^0.0.34",
|
||||
"@kevisual/query-login": "^0.0.7",
|
||||
"@kevisual/registry": "^0.0.1",
|
||||
"@kevisual/router": "^0.0.52",
|
||||
@@ -34,6 +34,7 @@
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@uiw/react-md-editor": "^4.0.11",
|
||||
"antd": "^6.1.3",
|
||||
"astro": "^5.16.6",
|
||||
@@ -53,7 +54,7 @@
|
||||
"react": "^19.2.3",
|
||||
"react-dom": "^19.2.3",
|
||||
"react-hook-form": "^7.69.0",
|
||||
"react-resizable-panels": "^4.1.0",
|
||||
"react-resizable-panels": "^4.2.0",
|
||||
"react-toastify": "^11.0.5",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"vue": "^3.5.26",
|
||||
@@ -63,10 +64,11 @@
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kevisual/api": "^0.0.14",
|
||||
"@kevisual/api": "^0.0.16",
|
||||
"@kevisual/types": "^0.0.10",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"baseline-browser-mapping": "^2.9.11",
|
||||
"dotenv": "^17.2.3",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"tw-animate-css": "^1.4.0"
|
||||
|
||||
71
web/pnpm-lock.yaml
generated
71
web/pnpm-lock.yaml
generated
@@ -27,11 +27,11 @@ importers:
|
||||
specifier: ^0.0.4
|
||||
version: 0.0.4
|
||||
'@kevisual/query':
|
||||
specifier: ^0.0.33
|
||||
version: 0.0.33
|
||||
specifier: ^0.0.34
|
||||
version: 0.0.34
|
||||
'@kevisual/query-login':
|
||||
specifier: ^0.0.7
|
||||
version: 0.0.7(@kevisual/query@0.0.33)
|
||||
version: 0.0.7(@kevisual/query@0.0.34)
|
||||
'@kevisual/registry':
|
||||
specifier: ^0.0.1
|
||||
version: 0.0.1(typescript@5.9.3)
|
||||
@@ -53,6 +53,9 @@ importers:
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.1.18
|
||||
version: 4.1.18(vite@6.4.1(@types/node@24.7.2)(jiti@2.6.1)(lightningcss@1.30.2))
|
||||
'@tanstack/react-table':
|
||||
specifier: ^8.21.3
|
||||
version: 8.21.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
'@uiw/react-md-editor':
|
||||
specifier: ^4.0.11
|
||||
version: 4.0.11(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
@@ -111,8 +114,8 @@ importers:
|
||||
specifier: ^7.69.0
|
||||
version: 7.69.0(react@19.2.3)
|
||||
react-resizable-panels:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
react-toastify:
|
||||
specifier: ^11.0.5
|
||||
version: 11.0.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
@@ -127,8 +130,8 @@ importers:
|
||||
version: 5.0.9(@types/react@19.2.7)(react@19.2.3)
|
||||
devDependencies:
|
||||
'@kevisual/api':
|
||||
specifier: ^0.0.14
|
||||
version: 0.0.14
|
||||
specifier: ^0.0.16
|
||||
version: 0.0.16
|
||||
'@kevisual/types':
|
||||
specifier: ^0.0.10
|
||||
version: 0.0.10
|
||||
@@ -138,6 +141,9 @@ importers:
|
||||
'@types/react-dom':
|
||||
specifier: ^19.2.3
|
||||
version: 19.2.3(@types/react@19.2.7)
|
||||
baseline-browser-mapping:
|
||||
specifier: ^2.9.11
|
||||
version: 2.9.11
|
||||
dotenv:
|
||||
specifier: ^17.2.3
|
||||
version: 17.2.3
|
||||
@@ -723,8 +729,8 @@ packages:
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||
|
||||
'@kevisual/api@0.0.14':
|
||||
resolution: {integrity: sha512-GOs61Jvjxs+7PB8+iSPko9/RGeWENxltHueV75M6W0psRsnx/J+06I48/cO413FwCoqSOqpOoivdRgSENdHM9g==}
|
||||
'@kevisual/api@0.0.16':
|
||||
resolution: {integrity: sha512-JInnqWHjUxos1oWHe8dmwxWOMCRgv5nI/7HbSrzvHDQxHE6Egc3xA5iALUcRDdkNOnPz98ErZnLmSgHHJDOwYQ==}
|
||||
|
||||
'@kevisual/cache@0.0.3':
|
||||
resolution: {integrity: sha512-BWEck69KYL96/ywjYVkML974RHjDJTj2ITQND1zFPR+hlBV1H1p55QZgSYRJCObg3EAV1S9Zic/fR2T4pfe8yg==}
|
||||
@@ -746,8 +752,8 @@ packages:
|
||||
peerDependencies:
|
||||
'@kevisual/query': ^0
|
||||
|
||||
'@kevisual/query@0.0.33':
|
||||
resolution: {integrity: sha512-3w74bcLpwV3z483eg8n0DgkftfjWC6iLONXBvfyjW6IZf6jMOuouFaM4Rk+uEsTgElU6XGMKseNTp6dlQdWYkg==}
|
||||
'@kevisual/query@0.0.34':
|
||||
resolution: {integrity: sha512-UHA0qEJYzU76pffUx0OhcOL5zKuxR/Kg269OHjrFm7+7RO85Qzv4ON1vUJDFp61hRuRVwiwOEKucQLHLE6UpMg==}
|
||||
|
||||
'@kevisual/registry@0.0.1':
|
||||
resolution: {integrity: sha512-//OHu9m4JDrMjgP8o8dcjZd3D3IAUkRVlkTSviouZEH7r5m7mccA3Hvzw0XJ/lelx6exC6LWsyv6c4uV0Dp+gw==}
|
||||
@@ -1667,6 +1673,17 @@ packages:
|
||||
peerDependencies:
|
||||
vite: ^5.2.0 || ^6 || ^7
|
||||
|
||||
'@tanstack/react-table@8.21.3':
|
||||
resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==}
|
||||
engines: {node: '>=12'}
|
||||
peerDependencies:
|
||||
react: '>=16.8'
|
||||
react-dom: '>=16.8'
|
||||
|
||||
'@tanstack/table-core@8.21.3':
|
||||
resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
||||
|
||||
@@ -1925,8 +1942,8 @@ packages:
|
||||
base64-js@1.5.1:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
|
||||
baseline-browser-mapping@2.8.16:
|
||||
resolution: {integrity: sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw==}
|
||||
baseline-browser-mapping@2.9.11:
|
||||
resolution: {integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==}
|
||||
hasBin: true
|
||||
|
||||
bcp-47-match@2.0.3:
|
||||
@@ -3101,8 +3118,8 @@ packages:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
react-resizable-panels@4.1.0:
|
||||
resolution: {integrity: sha512-8ZpOwdKQz6bCs2LGnfS6HuBITxkOLelSMzBX4DrWsgHaU3ukTPxmBNAeK8Bsp3LAEdtXeG6ll6UPN7OJNua4sw==}
|
||||
react-resizable-panels@4.2.0:
|
||||
resolution: {integrity: sha512-X/WbnyT/bgx09KEGvtJvaTr3axRrcBGcJdELIoGXZipCxc2hPwFsH/pfpVgwNVq5LpQxF/E5pPXGTQdjBnidPw==}
|
||||
peerDependencies:
|
||||
react: ^18.0.0 || ^19.0.0
|
||||
react-dom: ^18.0.0 || ^19.0.0
|
||||
@@ -4404,7 +4421,7 @@ snapshots:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
'@kevisual/api@0.0.14':
|
||||
'@kevisual/api@0.0.16':
|
||||
dependencies:
|
||||
'@kevisual/js-filter': 0.0.3
|
||||
'@kevisual/load': 0.0.6
|
||||
@@ -4430,13 +4447,15 @@ snapshots:
|
||||
dependencies:
|
||||
eventemitter3: 5.0.1
|
||||
|
||||
'@kevisual/query-login@0.0.7(@kevisual/query@0.0.33)':
|
||||
'@kevisual/query-login@0.0.7(@kevisual/query@0.0.34)':
|
||||
dependencies:
|
||||
'@kevisual/cache': 0.0.3
|
||||
'@kevisual/query': 0.0.33
|
||||
'@kevisual/query': 0.0.34
|
||||
dotenv: 17.2.3
|
||||
|
||||
'@kevisual/query@0.0.33': {}
|
||||
'@kevisual/query@0.0.34':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@kevisual/registry@0.0.1(typescript@5.9.3)':
|
||||
dependencies:
|
||||
@@ -5434,6 +5453,14 @@ snapshots:
|
||||
tailwindcss: 4.1.18
|
||||
vite: 6.4.1(@types/node@24.7.2)(jiti@2.6.1)(lightningcss@1.30.2)
|
||||
|
||||
'@tanstack/react-table@8.21.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||
dependencies:
|
||||
'@tanstack/table-core': 8.21.3
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
|
||||
'@tanstack/table-core@8.21.3': {}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.5
|
||||
@@ -5930,7 +5957,7 @@ snapshots:
|
||||
|
||||
base64-js@1.5.1: {}
|
||||
|
||||
baseline-browser-mapping@2.8.16: {}
|
||||
baseline-browser-mapping@2.9.11: {}
|
||||
|
||||
bcp-47-match@2.0.3: {}
|
||||
|
||||
@@ -5957,7 +5984,7 @@ snapshots:
|
||||
|
||||
browserslist@4.26.3:
|
||||
dependencies:
|
||||
baseline-browser-mapping: 2.8.16
|
||||
baseline-browser-mapping: 2.9.11
|
||||
caniuse-lite: 1.0.30001750
|
||||
electron-to-chromium: 1.5.235
|
||||
node-releases: 2.0.23
|
||||
@@ -7424,7 +7451,7 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.7
|
||||
|
||||
react-resizable-panels@4.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
||||
react-resizable-panels@4.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
||||
dependencies:
|
||||
react: 19.2.3
|
||||
react-dom: 19.2.3(react@19.2.3)
|
||||
|
||||
103
web/src/apps/chat/index.tsx
Normal file
103
web/src/apps/chat/index.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import { app } from '@/index.ts'
|
||||
import { useStudioStore } from '../studio/store';
|
||||
import { useShallow } from 'zustand/shallow';
|
||||
import { useState } from 'react';
|
||||
import { query } from '@/modules/query.ts'
|
||||
export const Chat = () => {
|
||||
const studioStore = useStudioStore(useShallow((state) => ({
|
||||
routes: state.routes,
|
||||
})));
|
||||
const [text, setText] = useState('');
|
||||
const onSend = async () => {
|
||||
const { routes } = studioStore;
|
||||
let callPrompts = '';
|
||||
const toolsList = routes.map((r, index) =>
|
||||
`${index + 1}. 工具名称: ${r.id}\n 描述: ${r.description}`
|
||||
).join('\n\n');
|
||||
|
||||
callPrompts = `你是一个 AI 助手,你可以使用以下工具来帮助用户完成任务:
|
||||
|
||||
${toolsList}
|
||||
|
||||
## 回复规则
|
||||
1. 如果用户的请求可以使用上述工具完成,请返回 JSON 格式数据
|
||||
2. 如果没有合适的工具,请直接分析并回答用户问题
|
||||
|
||||
## JSON 数据格式
|
||||
\`\`\`json
|
||||
{
|
||||
"id": "工具的id",
|
||||
"payload": {
|
||||
// 工具所需的参数(如果需要)
|
||||
// 例如: "id": "xxx", "name": "xxx"
|
||||
}
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
注意:
|
||||
- payload 中包含工具执行所需的所有参数
|
||||
- 如果工具不需要参数,payload 可以为空对象 {}
|
||||
- 确保返回的 id 与上述工具列表中的工具名称完全匹配`
|
||||
|
||||
const res = await query.post({
|
||||
path: 'ai',
|
||||
payload: {
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: callPrompts
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: text
|
||||
}
|
||||
],
|
||||
isJson: true
|
||||
}
|
||||
})
|
||||
console.log('发送消息', text, res);
|
||||
if (res.code === 200) {
|
||||
// 处理返回结果
|
||||
const payload = res.data?.action;
|
||||
if (payload) {
|
||||
const route = routes.find(r => r.id === payload.id);
|
||||
console.log('找到工具', route);
|
||||
const { path, key } = route || {};
|
||||
const { id, ...otherParams } = payload.payload || {};
|
||||
if (route) {
|
||||
const r = await app.run({ path, key, ...otherParams });
|
||||
console.log('工具调用结果', r);
|
||||
} else {
|
||||
console.error('未找到对应工具', payload.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return <div className="h-full flex flex-col border-l border-gray-300 bg-white">
|
||||
<div className="h-12 flex items-center justify-between px-4 border-b border-gray-300 bg-white">
|
||||
<div className="text-lg font-medium text-black">聊天</div>
|
||||
</div>
|
||||
<div style={{ height: '3rem' }} className="flex items-center justify-between px-4 border-b border-gray-300 bg-gray-50">
|
||||
<div className="text-sm text-gray-600">欢迎使用聊天功能!</div>
|
||||
</div>
|
||||
<div style={{ height: 'calc(100% - 6rem)' }} className="overflow-auto">
|
||||
{/* 聊天内容区域 */}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 px-4 py-3 border-t border-gray-300 bg-white">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="输入消息..."
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
onKeyDown={(e) => e.key === 'Enter' && onSend()}
|
||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-md bg-white text-black placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-800 transition-all"
|
||||
/>
|
||||
<button
|
||||
onClick={onSend}
|
||||
className="px-4 py-2 bg-black hover:bg-gray-900 text-white font-medium rounded-md transition-colors duration-200 flex-shrink-0"
|
||||
>
|
||||
发送
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
46
web/src/apps/chat/modules/messages.tsx
Normal file
46
web/src/apps/chat/modules/messages.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
type MessageData = {
|
||||
id: string;
|
||||
type: 'md' | 'api';
|
||||
api?: {
|
||||
url?: string;
|
||||
/**
|
||||
* 默认不存在,动态生成
|
||||
*/
|
||||
query?: any;
|
||||
};
|
||||
question?: string;
|
||||
action?: any;
|
||||
/**
|
||||
* 默认不存在,动态生成
|
||||
*/
|
||||
response?: any;
|
||||
}
|
||||
|
||||
type MessageProps = {
|
||||
data: MessageData;
|
||||
}
|
||||
export const Message = (props: MessageProps) => {
|
||||
const { data } = props;
|
||||
if (data.type === 'md') {
|
||||
return <div className="prose prose-sm max-w-full">
|
||||
{/* Markdown 渲染组件 */}
|
||||
Markdown 内容
|
||||
</div>
|
||||
}
|
||||
if (data.type === 'api') {
|
||||
return <div>
|
||||
{/* 查询结果渲染组件 */}
|
||||
查询结果内容
|
||||
</div>
|
||||
}
|
||||
return <div>未知消息类型</div>
|
||||
}
|
||||
|
||||
export const Messages = (props: { items: MessageData[] }) => {
|
||||
const items = props.items || [];
|
||||
return <div className="p-4 space-y-4">
|
||||
{items.map((item) => (
|
||||
<Message key={item.id} data={item} />
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
0
web/src/apps/chat/store.ts
Normal file
0
web/src/apps/chat/store.ts
Normal file
101
web/src/apps/query-view/index.tsx
Normal file
101
web/src/apps/query-view/index.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import { QueryProxy } from '@kevisual/api/proxy'
|
||||
import { app } from '@/index.ts'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { flexRender, useReactTable, getCoreRowModel } from '@tanstack/react-table';
|
||||
type Props = {
|
||||
data: any
|
||||
type: 'component' | 'page'
|
||||
}
|
||||
export const QueryView = (props: Props) => {
|
||||
|
||||
return <div>API 视图 </div>
|
||||
}
|
||||
|
||||
|
||||
const queryProxy = new QueryProxy({
|
||||
router: app as any
|
||||
});
|
||||
export const App = () => {
|
||||
const [data, setData] = useState<any[]>([])
|
||||
const [columns, setColumns] = useState<any[]>([])
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns: columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
})
|
||||
const main = async () => {
|
||||
const res = await queryProxy.runByRouteView({
|
||||
id: 'getData',
|
||||
description: '获取数据',
|
||||
title: '获取数据',
|
||||
type: 'api',
|
||||
api: {
|
||||
url: "/api/router",
|
||||
},
|
||||
action: {
|
||||
path: 'router',
|
||||
key: 'list'
|
||||
}
|
||||
})
|
||||
const response = res.response;
|
||||
setData(response.data.list)
|
||||
console.log('res', res);
|
||||
const [firstItem] = response.data.list || []
|
||||
if (firstItem) {
|
||||
const cols = Object.keys(firstItem).map(key => ({
|
||||
accessorKey: key,
|
||||
header: key.toUpperCase(),
|
||||
}))
|
||||
setColumns(cols)
|
||||
}
|
||||
}
|
||||
useEffect(() => {
|
||||
main()
|
||||
}, [])
|
||||
return <div id='route-view' className='w-full h-full overflow-auto p-4'>
|
||||
<table className='w-full border-collapse border border-gray-300 rounded-lg overflow-hidden shadow-md'>
|
||||
<thead className='bg-gray-100 border-b-2 border-gray-300'>
|
||||
{table.getHeaderGroups().map(headerGroup => (
|
||||
<tr key={headerGroup.id}>
|
||||
{headerGroup.headers.map(header => (
|
||||
<th
|
||||
key={header.id}
|
||||
className='px-4 py-3 text-left text-sm font-semibold text-gray-700 whitespace-nowrap'
|
||||
>
|
||||
{flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody>
|
||||
{table.getRowModel().rows.map((row, idx) => (
|
||||
<tr
|
||||
key={row.id}
|
||||
className={`border-b border-gray-200 transition-colors duration-200 ${
|
||||
idx % 2 === 0 ? 'bg-white' : 'bg-gray-50'
|
||||
} hover:bg-blue-50`}
|
||||
>
|
||||
{row.getVisibleCells().map(cell => (
|
||||
<td
|
||||
key={cell.id}
|
||||
className='px-4 py-3 text-sm text-gray-600'
|
||||
>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
|
||||
export const AppProvider = (props: { children?: React.ReactNode }) => {
|
||||
return <main className='w-full h-screen flex flex-col overflow-auto'>
|
||||
<App />
|
||||
</main>
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { MonitorPlay, Play, PanelLeft, PanelLeftClose } from 'lucide-react';
|
||||
import { Panel, Group } from 'react-resizable-panels'
|
||||
import { ViewList } from '../view/list.tsx';
|
||||
import { useShallow } from 'zustand/shallow';
|
||||
|
||||
import { Chat } from '../chat/index.tsx';
|
||||
export const AppProvider = () => {
|
||||
const { showLeftPanel } = useStudioStore(useShallow((state) => ({
|
||||
showLeftPanel: state.showLeftPanel,
|
||||
@@ -17,9 +17,17 @@ export const AppProvider = () => {
|
||||
</Panel>}
|
||||
<Panel>
|
||||
<WrapperHeader>
|
||||
<App />
|
||||
<Group className="h-full overflow-hidden">
|
||||
<Panel >
|
||||
<App />
|
||||
</Panel>
|
||||
<Panel defaultSize={500} minSize={300} maxSize={600} className="border-l border-gray-300 overflow-auto">
|
||||
<Chat />
|
||||
</Panel>
|
||||
</Group>
|
||||
</WrapperHeader>
|
||||
</Panel>
|
||||
|
||||
</Group>
|
||||
<ToastContainer
|
||||
position="top-right"
|
||||
@@ -59,12 +67,12 @@ interface RouteItem {
|
||||
}
|
||||
|
||||
export const App = () => {
|
||||
const { routes, getRouteList, run, loading } = useStudioStore();
|
||||
const { routes, queryRouteList, run, loading } = useStudioStore();
|
||||
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set());
|
||||
const [visibleIds, setVisibleIds] = useState<Set<string>>(new Set());
|
||||
|
||||
useEffect(() => {
|
||||
getRouteList();
|
||||
queryRouteList();
|
||||
}, []);
|
||||
|
||||
const toggleDescription = (id: string) => {
|
||||
@@ -143,8 +151,7 @@ export const App = () => {
|
||||
className="cursor-pointer group"
|
||||
>
|
||||
<div
|
||||
className={`text-gray-700 transition-colors duration-200 cursor-pointer overflow-hidden ${isExpanded ? 'text-gray-900 animate-expand-in' : 'group-hover:text-gray-900 max-h-0 opacity-0'
|
||||
}`}
|
||||
className={`text-gray-700 transition-colors duration-200 cursor-pointer overflow-hidden`}
|
||||
>
|
||||
{isExpanded ? (
|
||||
<p className="text-sm leading-relaxed whitespace-pre-wrap">
|
||||
|
||||
@@ -5,7 +5,7 @@ import { toast } from 'react-toastify';
|
||||
import { use } from '@kevisual/context'
|
||||
import { MyCache } from '@kevisual/cache'
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
import { app } from '@/index.ts'
|
||||
const historyReplace = (url: string) => {
|
||||
if (window.history.replaceState) {
|
||||
window.history.replaceState(null, '', url);
|
||||
@@ -21,16 +21,17 @@ type RouteItem = {
|
||||
|
||||
type RouteViewList = Array<RouterViewData>;
|
||||
|
||||
|
||||
interface StudioState {
|
||||
loading: boolean;
|
||||
setLoading: (loading: boolean) => void;
|
||||
routes: Array<RouteItem>;
|
||||
getRouteList: (viewId?: string) => Promise<void>;
|
||||
run: (route: RouteItem) => Promise<void>;
|
||||
queryProxy?: QueryProxy;
|
||||
init: (force?: boolean) => Promise<{ queryProxy: QueryProxy }>;
|
||||
routeViewList: RouteViewList;
|
||||
getViewList: () => Promise<void>;
|
||||
queryRouteList: () => Promise<void>;
|
||||
getCurrentView: () => Promise<void>;
|
||||
updateRouteView: (view: RouterViewData) => Promise<void>;
|
||||
deleteRouteView: (id: string) => Promise<void>;
|
||||
@@ -38,6 +39,8 @@ interface StudioState {
|
||||
setCurrentView: (view?: RouterViewData) => Promise<void>;
|
||||
showLeftPanel: boolean;
|
||||
setShowLeftPanel: (show: boolean) => void;
|
||||
showRightPanel: boolean;
|
||||
setShowRightPanel: (show: boolean) => void;
|
||||
}
|
||||
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
@@ -47,7 +50,7 @@ export const useStudioStore = create<StudioState>()(
|
||||
loading: false,
|
||||
setLoading: (loading: boolean) => set({ loading }),
|
||||
routes: [],
|
||||
getRouteList: async () => {
|
||||
queryRouteList: async () => {
|
||||
await get().getCurrentView();
|
||||
const state = await get().init();
|
||||
let currentView: RouterViewData | undefined = get().currentView;
|
||||
@@ -67,8 +70,9 @@ export const useStudioStore = create<StudioState>()(
|
||||
url.searchParams.delete('viewId');
|
||||
}
|
||||
historyReplace(url.toString());
|
||||
console.log('视图切换', beforeView, view);
|
||||
await get().init(beforeView?.id !== view?.id);
|
||||
await get().getRouteList();
|
||||
await get().queryRouteList();
|
||||
},
|
||||
getViewList: async () => {
|
||||
const res = await query.post({ path: 'views', key: 'list' });
|
||||
@@ -154,26 +158,30 @@ export const useStudioStore = create<StudioState>()(
|
||||
},
|
||||
viewId: '',
|
||||
}
|
||||
console.log('初始化 QueryProxy', routerViewData);
|
||||
queryProxy = new QueryProxy({
|
||||
routerViewData
|
||||
routerViewData,
|
||||
router: app as any,
|
||||
});
|
||||
set({ loading: true });
|
||||
await sleep(1000); // 保证 loading 状态更新
|
||||
await queryProxy.init();
|
||||
await sleep(500);
|
||||
set({ loading: false });
|
||||
set({ queryProxy });
|
||||
return { queryProxy }
|
||||
},
|
||||
showLeftPanel: false,
|
||||
setShowLeftPanel: (show: boolean) => set({ showLeftPanel: show }),
|
||||
showRightPanel: false,
|
||||
setShowRightPanel: (show: boolean) => set({ showRightPanel: show }),
|
||||
}),
|
||||
{
|
||||
name: 'studio-storage',
|
||||
partialize: (state) => ({ showLeftPanel: state.showLeftPanel }),
|
||||
partialize: (state) => ({ showLeftPanel: state.showLeftPanel, showRightPanel: state.showRightPanel }),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
use('studioStore', () => {
|
||||
return useStudioStore.getState();
|
||||
return useStudioStore;
|
||||
});
|
||||
|
||||
@@ -63,11 +63,26 @@ export const ViewEditor = ({ open, onOpenChange, data, onSave }: ViewEditorProps
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
const pickData = dataItems.map(item => {
|
||||
if (item.type === 'api') {
|
||||
delete item.api.query
|
||||
}
|
||||
if (item.type === 'worker') {
|
||||
delete item.worker.worker
|
||||
}
|
||||
if (item.type === 'context') {
|
||||
delete item.context.router
|
||||
}
|
||||
if (item.type === 'page') {
|
||||
|
||||
}
|
||||
return item
|
||||
})
|
||||
const viewData = {
|
||||
id: data?.id,
|
||||
title,
|
||||
data: {
|
||||
items: dataItems
|
||||
items: pickData
|
||||
},
|
||||
views
|
||||
}
|
||||
|
||||
5
web/src/index.ts
Normal file
5
web/src/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { app } from '@/app.ts';
|
||||
|
||||
import './routes/left-panel.ts';
|
||||
|
||||
export { app };
|
||||
8
web/src/pages/query-view.astro
Normal file
8
web/src/pages/query-view.astro
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
import Html from '@/components/html.astro';
|
||||
import { AppProvider } from '@/apps/query-view/index.tsx';
|
||||
---
|
||||
|
||||
<Html title='Router Studio'>
|
||||
<AppProvider client:only />
|
||||
</Html>
|
||||
22
web/src/routes/left-panel.ts
Normal file
22
web/src/routes/left-panel.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { app } from '@/app.ts';
|
||||
import { use } from '@kevisual/context'
|
||||
|
||||
app.route({
|
||||
path: 'web',
|
||||
key: 'togglePanel',
|
||||
description: '当前的网页页面功能,切换左侧面板显示与隐藏',
|
||||
metadata: {
|
||||
tags: ['web', 'studio', 'page'],
|
||||
}
|
||||
}).define(async (ctx) => {
|
||||
const store = use('studioStore');
|
||||
try {
|
||||
|
||||
const state = store.getState();
|
||||
state.setShowLeftPanel(!state.showLeftPanel);
|
||||
ctx.body = { success: true };
|
||||
} catch (error) {
|
||||
ctx.body = { success: false, message: (error as Error).message };
|
||||
}
|
||||
|
||||
}).addTo(app)
|
||||
Reference in New Issue
Block a user