update
This commit is contained in:
@@ -25,7 +25,7 @@
|
|||||||
"@astrojs/vue": "^5.1.3",
|
"@astrojs/vue": "^5.1.3",
|
||||||
"@kevisual/cache": "^0.0.5",
|
"@kevisual/cache": "^0.0.5",
|
||||||
"@kevisual/context": "^0.0.4",
|
"@kevisual/context": "^0.0.4",
|
||||||
"@kevisual/query": "^0.0.33",
|
"@kevisual/query": "^0.0.34",
|
||||||
"@kevisual/query-login": "^0.0.7",
|
"@kevisual/query-login": "^0.0.7",
|
||||||
"@kevisual/registry": "^0.0.1",
|
"@kevisual/registry": "^0.0.1",
|
||||||
"@kevisual/router": "^0.0.52",
|
"@kevisual/router": "^0.0.52",
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
"@radix-ui/react-label": "^2.1.8",
|
"@radix-ui/react-label": "^2.1.8",
|
||||||
"@radix-ui/react-slot": "^1.2.4",
|
"@radix-ui/react-slot": "^1.2.4",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"@uiw/react-md-editor": "^4.0.11",
|
"@uiw/react-md-editor": "^4.0.11",
|
||||||
"antd": "^6.1.3",
|
"antd": "^6.1.3",
|
||||||
"astro": "^5.16.6",
|
"astro": "^5.16.6",
|
||||||
@@ -53,7 +54,7 @@
|
|||||||
"react": "^19.2.3",
|
"react": "^19.2.3",
|
||||||
"react-dom": "^19.2.3",
|
"react-dom": "^19.2.3",
|
||||||
"react-hook-form": "^7.69.0",
|
"react-hook-form": "^7.69.0",
|
||||||
"react-resizable-panels": "^4.1.0",
|
"react-resizable-panels": "^4.2.0",
|
||||||
"react-toastify": "^11.0.5",
|
"react-toastify": "^11.0.5",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"vue": "^3.5.26",
|
"vue": "^3.5.26",
|
||||||
@@ -63,10 +64,11 @@
|
|||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@kevisual/api": "^0.0.14",
|
"@kevisual/api": "^0.0.16",
|
||||||
"@kevisual/types": "^0.0.10",
|
"@kevisual/types": "^0.0.10",
|
||||||
"@types/react": "^19.2.7",
|
"@types/react": "^19.2.7",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
|
"baseline-browser-mapping": "^2.9.11",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
"tw-animate-css": "^1.4.0"
|
"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
|
specifier: ^0.0.4
|
||||||
version: 0.0.4
|
version: 0.0.4
|
||||||
'@kevisual/query':
|
'@kevisual/query':
|
||||||
specifier: ^0.0.33
|
specifier: ^0.0.34
|
||||||
version: 0.0.33
|
version: 0.0.34
|
||||||
'@kevisual/query-login':
|
'@kevisual/query-login':
|
||||||
specifier: ^0.0.7
|
specifier: ^0.0.7
|
||||||
version: 0.0.7(@kevisual/query@0.0.33)
|
version: 0.0.7(@kevisual/query@0.0.34)
|
||||||
'@kevisual/registry':
|
'@kevisual/registry':
|
||||||
specifier: ^0.0.1
|
specifier: ^0.0.1
|
||||||
version: 0.0.1(typescript@5.9.3)
|
version: 0.0.1(typescript@5.9.3)
|
||||||
@@ -53,6 +53,9 @@ importers:
|
|||||||
'@tailwindcss/vite':
|
'@tailwindcss/vite':
|
||||||
specifier: ^4.1.18
|
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))
|
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':
|
'@uiw/react-md-editor':
|
||||||
specifier: ^4.0.11
|
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)
|
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
|
specifier: ^7.69.0
|
||||||
version: 7.69.0(react@19.2.3)
|
version: 7.69.0(react@19.2.3)
|
||||||
react-resizable-panels:
|
react-resizable-panels:
|
||||||
specifier: ^4.1.0
|
specifier: ^4.2.0
|
||||||
version: 4.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
version: 4.2.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
react-toastify:
|
react-toastify:
|
||||||
specifier: ^11.0.5
|
specifier: ^11.0.5
|
||||||
version: 11.0.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
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)
|
version: 5.0.9(@types/react@19.2.7)(react@19.2.3)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@kevisual/api':
|
'@kevisual/api':
|
||||||
specifier: ^0.0.14
|
specifier: ^0.0.16
|
||||||
version: 0.0.14
|
version: 0.0.16
|
||||||
'@kevisual/types':
|
'@kevisual/types':
|
||||||
specifier: ^0.0.10
|
specifier: ^0.0.10
|
||||||
version: 0.0.10
|
version: 0.0.10
|
||||||
@@ -138,6 +141,9 @@ importers:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
specifier: ^19.2.3
|
specifier: ^19.2.3
|
||||||
version: 19.2.3(@types/react@19.2.7)
|
version: 19.2.3(@types/react@19.2.7)
|
||||||
|
baseline-browser-mapping:
|
||||||
|
specifier: ^2.9.11
|
||||||
|
version: 2.9.11
|
||||||
dotenv:
|
dotenv:
|
||||||
specifier: ^17.2.3
|
specifier: ^17.2.3
|
||||||
version: 17.2.3
|
version: 17.2.3
|
||||||
@@ -723,8 +729,8 @@ packages:
|
|||||||
'@jridgewell/trace-mapping@0.3.31':
|
'@jridgewell/trace-mapping@0.3.31':
|
||||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||||
|
|
||||||
'@kevisual/api@0.0.14':
|
'@kevisual/api@0.0.16':
|
||||||
resolution: {integrity: sha512-GOs61Jvjxs+7PB8+iSPko9/RGeWENxltHueV75M6W0psRsnx/J+06I48/cO413FwCoqSOqpOoivdRgSENdHM9g==}
|
resolution: {integrity: sha512-JInnqWHjUxos1oWHe8dmwxWOMCRgv5nI/7HbSrzvHDQxHE6Egc3xA5iALUcRDdkNOnPz98ErZnLmSgHHJDOwYQ==}
|
||||||
|
|
||||||
'@kevisual/cache@0.0.3':
|
'@kevisual/cache@0.0.3':
|
||||||
resolution: {integrity: sha512-BWEck69KYL96/ywjYVkML974RHjDJTj2ITQND1zFPR+hlBV1H1p55QZgSYRJCObg3EAV1S9Zic/fR2T4pfe8yg==}
|
resolution: {integrity: sha512-BWEck69KYL96/ywjYVkML974RHjDJTj2ITQND1zFPR+hlBV1H1p55QZgSYRJCObg3EAV1S9Zic/fR2T4pfe8yg==}
|
||||||
@@ -746,8 +752,8 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@kevisual/query': ^0
|
'@kevisual/query': ^0
|
||||||
|
|
||||||
'@kevisual/query@0.0.33':
|
'@kevisual/query@0.0.34':
|
||||||
resolution: {integrity: sha512-3w74bcLpwV3z483eg8n0DgkftfjWC6iLONXBvfyjW6IZf6jMOuouFaM4Rk+uEsTgElU6XGMKseNTp6dlQdWYkg==}
|
resolution: {integrity: sha512-UHA0qEJYzU76pffUx0OhcOL5zKuxR/Kg269OHjrFm7+7RO85Qzv4ON1vUJDFp61hRuRVwiwOEKucQLHLE6UpMg==}
|
||||||
|
|
||||||
'@kevisual/registry@0.0.1':
|
'@kevisual/registry@0.0.1':
|
||||||
resolution: {integrity: sha512-//OHu9m4JDrMjgP8o8dcjZd3D3IAUkRVlkTSviouZEH7r5m7mccA3Hvzw0XJ/lelx6exC6LWsyv6c4uV0Dp+gw==}
|
resolution: {integrity: sha512-//OHu9m4JDrMjgP8o8dcjZd3D3IAUkRVlkTSviouZEH7r5m7mccA3Hvzw0XJ/lelx6exC6LWsyv6c4uV0Dp+gw==}
|
||||||
@@ -1667,6 +1673,17 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vite: ^5.2.0 || ^6 || ^7
|
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':
|
'@types/babel__core@7.20.5':
|
||||||
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
||||||
|
|
||||||
@@ -1925,8 +1942,8 @@ packages:
|
|||||||
base64-js@1.5.1:
|
base64-js@1.5.1:
|
||||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||||
|
|
||||||
baseline-browser-mapping@2.8.16:
|
baseline-browser-mapping@2.9.11:
|
||||||
resolution: {integrity: sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw==}
|
resolution: {integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
bcp-47-match@2.0.3:
|
bcp-47-match@2.0.3:
|
||||||
@@ -3101,8 +3118,8 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
react-resizable-panels@4.1.0:
|
react-resizable-panels@4.2.0:
|
||||||
resolution: {integrity: sha512-8ZpOwdKQz6bCs2LGnfS6HuBITxkOLelSMzBX4DrWsgHaU3ukTPxmBNAeK8Bsp3LAEdtXeG6ll6UPN7OJNua4sw==}
|
resolution: {integrity: sha512-X/WbnyT/bgx09KEGvtJvaTr3axRrcBGcJdELIoGXZipCxc2hPwFsH/pfpVgwNVq5LpQxF/E5pPXGTQdjBnidPw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^18.0.0 || ^19.0.0
|
react: ^18.0.0 || ^19.0.0
|
||||||
react-dom: ^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/resolve-uri': 3.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
'@kevisual/api@0.0.14':
|
'@kevisual/api@0.0.16':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@kevisual/js-filter': 0.0.3
|
'@kevisual/js-filter': 0.0.3
|
||||||
'@kevisual/load': 0.0.6
|
'@kevisual/load': 0.0.6
|
||||||
@@ -4430,13 +4447,15 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
eventemitter3: 5.0.1
|
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:
|
dependencies:
|
||||||
'@kevisual/cache': 0.0.3
|
'@kevisual/cache': 0.0.3
|
||||||
'@kevisual/query': 0.0.33
|
'@kevisual/query': 0.0.34
|
||||||
dotenv: 17.2.3
|
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)':
|
'@kevisual/registry@0.0.1(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5434,6 +5453,14 @@ snapshots:
|
|||||||
tailwindcss: 4.1.18
|
tailwindcss: 4.1.18
|
||||||
vite: 6.4.1(@types/node@24.7.2)(jiti@2.6.1)(lightningcss@1.30.2)
|
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':
|
'@types/babel__core@7.20.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/parser': 7.28.5
|
'@babel/parser': 7.28.5
|
||||||
@@ -5930,7 +5957,7 @@ snapshots:
|
|||||||
|
|
||||||
base64-js@1.5.1: {}
|
base64-js@1.5.1: {}
|
||||||
|
|
||||||
baseline-browser-mapping@2.8.16: {}
|
baseline-browser-mapping@2.9.11: {}
|
||||||
|
|
||||||
bcp-47-match@2.0.3: {}
|
bcp-47-match@2.0.3: {}
|
||||||
|
|
||||||
@@ -5957,7 +5984,7 @@ snapshots:
|
|||||||
|
|
||||||
browserslist@4.26.3:
|
browserslist@4.26.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
baseline-browser-mapping: 2.8.16
|
baseline-browser-mapping: 2.9.11
|
||||||
caniuse-lite: 1.0.30001750
|
caniuse-lite: 1.0.30001750
|
||||||
electron-to-chromium: 1.5.235
|
electron-to-chromium: 1.5.235
|
||||||
node-releases: 2.0.23
|
node-releases: 2.0.23
|
||||||
@@ -7424,7 +7451,7 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.7
|
'@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:
|
dependencies:
|
||||||
react: 19.2.3
|
react: 19.2.3
|
||||||
react-dom: 19.2.3(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 { Panel, Group } from 'react-resizable-panels'
|
||||||
import { ViewList } from '../view/list.tsx';
|
import { ViewList } from '../view/list.tsx';
|
||||||
import { useShallow } from 'zustand/shallow';
|
import { useShallow } from 'zustand/shallow';
|
||||||
|
import { Chat } from '../chat/index.tsx';
|
||||||
export const AppProvider = () => {
|
export const AppProvider = () => {
|
||||||
const { showLeftPanel } = useStudioStore(useShallow((state) => ({
|
const { showLeftPanel } = useStudioStore(useShallow((state) => ({
|
||||||
showLeftPanel: state.showLeftPanel,
|
showLeftPanel: state.showLeftPanel,
|
||||||
@@ -17,9 +17,17 @@ export const AppProvider = () => {
|
|||||||
</Panel>}
|
</Panel>}
|
||||||
<Panel>
|
<Panel>
|
||||||
<WrapperHeader>
|
<WrapperHeader>
|
||||||
|
<Group className="h-full overflow-hidden">
|
||||||
|
<Panel >
|
||||||
<App />
|
<App />
|
||||||
|
</Panel>
|
||||||
|
<Panel defaultSize={500} minSize={300} maxSize={600} className="border-l border-gray-300 overflow-auto">
|
||||||
|
<Chat />
|
||||||
|
</Panel>
|
||||||
|
</Group>
|
||||||
</WrapperHeader>
|
</WrapperHeader>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
||||||
</Group>
|
</Group>
|
||||||
<ToastContainer
|
<ToastContainer
|
||||||
position="top-right"
|
position="top-right"
|
||||||
@@ -59,12 +67,12 @@ interface RouteItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
const { routes, getRouteList, run, loading } = useStudioStore();
|
const { routes, queryRouteList, run, loading } = useStudioStore();
|
||||||
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set());
|
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set());
|
||||||
const [visibleIds, setVisibleIds] = useState<Set<string>>(new Set());
|
const [visibleIds, setVisibleIds] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getRouteList();
|
queryRouteList();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const toggleDescription = (id: string) => {
|
const toggleDescription = (id: string) => {
|
||||||
@@ -143,8 +151,7 @@ export const App = () => {
|
|||||||
className="cursor-pointer group"
|
className="cursor-pointer group"
|
||||||
>
|
>
|
||||||
<div
|
<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 ? (
|
{isExpanded ? (
|
||||||
<p className="text-sm leading-relaxed whitespace-pre-wrap">
|
<p className="text-sm leading-relaxed whitespace-pre-wrap">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { toast } from 'react-toastify';
|
|||||||
import { use } from '@kevisual/context'
|
import { use } from '@kevisual/context'
|
||||||
import { MyCache } from '@kevisual/cache'
|
import { MyCache } from '@kevisual/cache'
|
||||||
import { persist } from 'zustand/middleware';
|
import { persist } from 'zustand/middleware';
|
||||||
|
import { app } from '@/index.ts'
|
||||||
const historyReplace = (url: string) => {
|
const historyReplace = (url: string) => {
|
||||||
if (window.history.replaceState) {
|
if (window.history.replaceState) {
|
||||||
window.history.replaceState(null, '', url);
|
window.history.replaceState(null, '', url);
|
||||||
@@ -21,16 +21,17 @@ type RouteItem = {
|
|||||||
|
|
||||||
type RouteViewList = Array<RouterViewData>;
|
type RouteViewList = Array<RouterViewData>;
|
||||||
|
|
||||||
|
|
||||||
interface StudioState {
|
interface StudioState {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
setLoading: (loading: boolean) => void;
|
setLoading: (loading: boolean) => void;
|
||||||
routes: Array<RouteItem>;
|
routes: Array<RouteItem>;
|
||||||
getRouteList: (viewId?: string) => Promise<void>;
|
|
||||||
run: (route: RouteItem) => Promise<void>;
|
run: (route: RouteItem) => Promise<void>;
|
||||||
queryProxy?: QueryProxy;
|
queryProxy?: QueryProxy;
|
||||||
init: (force?: boolean) => Promise<{ queryProxy: QueryProxy }>;
|
init: (force?: boolean) => Promise<{ queryProxy: QueryProxy }>;
|
||||||
routeViewList: RouteViewList;
|
routeViewList: RouteViewList;
|
||||||
getViewList: () => Promise<void>;
|
getViewList: () => Promise<void>;
|
||||||
|
queryRouteList: () => Promise<void>;
|
||||||
getCurrentView: () => Promise<void>;
|
getCurrentView: () => Promise<void>;
|
||||||
updateRouteView: (view: RouterViewData) => Promise<void>;
|
updateRouteView: (view: RouterViewData) => Promise<void>;
|
||||||
deleteRouteView: (id: string) => Promise<void>;
|
deleteRouteView: (id: string) => Promise<void>;
|
||||||
@@ -38,6 +39,8 @@ interface StudioState {
|
|||||||
setCurrentView: (view?: RouterViewData) => Promise<void>;
|
setCurrentView: (view?: RouterViewData) => Promise<void>;
|
||||||
showLeftPanel: boolean;
|
showLeftPanel: boolean;
|
||||||
setShowLeftPanel: (show: boolean) => void;
|
setShowLeftPanel: (show: boolean) => void;
|
||||||
|
showRightPanel: boolean;
|
||||||
|
setShowRightPanel: (show: boolean) => void;
|
||||||
}
|
}
|
||||||
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
|
||||||
@@ -47,7 +50,7 @@ export const useStudioStore = create<StudioState>()(
|
|||||||
loading: false,
|
loading: false,
|
||||||
setLoading: (loading: boolean) => set({ loading }),
|
setLoading: (loading: boolean) => set({ loading }),
|
||||||
routes: [],
|
routes: [],
|
||||||
getRouteList: async () => {
|
queryRouteList: async () => {
|
||||||
await get().getCurrentView();
|
await get().getCurrentView();
|
||||||
const state = await get().init();
|
const state = await get().init();
|
||||||
let currentView: RouterViewData | undefined = get().currentView;
|
let currentView: RouterViewData | undefined = get().currentView;
|
||||||
@@ -67,8 +70,9 @@ export const useStudioStore = create<StudioState>()(
|
|||||||
url.searchParams.delete('viewId');
|
url.searchParams.delete('viewId');
|
||||||
}
|
}
|
||||||
historyReplace(url.toString());
|
historyReplace(url.toString());
|
||||||
|
console.log('视图切换', beforeView, view);
|
||||||
await get().init(beforeView?.id !== view?.id);
|
await get().init(beforeView?.id !== view?.id);
|
||||||
await get().getRouteList();
|
await get().queryRouteList();
|
||||||
},
|
},
|
||||||
getViewList: async () => {
|
getViewList: async () => {
|
||||||
const res = await query.post({ path: 'views', key: 'list' });
|
const res = await query.post({ path: 'views', key: 'list' });
|
||||||
@@ -154,26 +158,30 @@ export const useStudioStore = create<StudioState>()(
|
|||||||
},
|
},
|
||||||
viewId: '',
|
viewId: '',
|
||||||
}
|
}
|
||||||
|
console.log('初始化 QueryProxy', routerViewData);
|
||||||
queryProxy = new QueryProxy({
|
queryProxy = new QueryProxy({
|
||||||
routerViewData
|
routerViewData,
|
||||||
|
router: app as any,
|
||||||
});
|
});
|
||||||
set({ loading: true });
|
set({ loading: true });
|
||||||
|
await sleep(1000); // 保证 loading 状态更新
|
||||||
await queryProxy.init();
|
await queryProxy.init();
|
||||||
await sleep(500);
|
|
||||||
set({ loading: false });
|
set({ loading: false });
|
||||||
set({ queryProxy });
|
set({ queryProxy });
|
||||||
return { queryProxy }
|
return { queryProxy }
|
||||||
},
|
},
|
||||||
showLeftPanel: false,
|
showLeftPanel: false,
|
||||||
setShowLeftPanel: (show: boolean) => set({ showLeftPanel: show }),
|
setShowLeftPanel: (show: boolean) => set({ showLeftPanel: show }),
|
||||||
|
showRightPanel: false,
|
||||||
|
setShowRightPanel: (show: boolean) => set({ showRightPanel: show }),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'studio-storage',
|
name: 'studio-storage',
|
||||||
partialize: (state) => ({ showLeftPanel: state.showLeftPanel }),
|
partialize: (state) => ({ showLeftPanel: state.showLeftPanel, showRightPanel: state.showRightPanel }),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
use('studioStore', () => {
|
use('studioStore', () => {
|
||||||
return useStudioStore.getState();
|
return useStudioStore;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -63,11 +63,26 @@ export const ViewEditor = ({ open, onOpenChange, data, onSave }: ViewEditorProps
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleSave = () => {
|
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 = {
|
const viewData = {
|
||||||
id: data?.id,
|
id: data?.id,
|
||||||
title,
|
title,
|
||||||
data: {
|
data: {
|
||||||
items: dataItems
|
items: pickData
|
||||||
},
|
},
|
||||||
views
|
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