Compare commits
16 Commits
04a21d7178
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 31e8c0346f | |||
|
|
14a80013e1 | ||
| 67169fc0c6 | |||
| 9532cfbe2e | |||
| 99645e6260 | |||
| 4a2d2a7958 | |||
| 1d92662650 | |||
| 4bd17318a4 | |||
| e8bb129abc | |||
| e5c15bd70a | |||
| eea07b0a8d | |||
| 875195f6a2 | |||
| 2c74755c0d | |||
| 45eedd397b | |||
| abd17b886d | |||
| 7793764baa |
56
.cnb.yml
Normal file
56
.cnb.yml
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# .cnb.yml
|
||||||
|
$:
|
||||||
|
vscode:
|
||||||
|
- docker:
|
||||||
|
image: docker.cnb.cool/kevisual/dev-env:latest
|
||||||
|
services:
|
||||||
|
- vscode
|
||||||
|
- docker
|
||||||
|
imports: https://cnb.cool/kevisual/env/-/blob/main/env.yml
|
||||||
|
# 开发环境启动后会执行的任务
|
||||||
|
# stages:
|
||||||
|
# - name: pnpm install
|
||||||
|
# script: pnpm install
|
||||||
|
|
||||||
|
|
||||||
|
main:
|
||||||
|
web_trigger_sync_to_gitea:
|
||||||
|
- services:
|
||||||
|
- docker
|
||||||
|
imports:
|
||||||
|
- https://cnb.cool/kevisual/env/-/blob/main/env.yml
|
||||||
|
stages:
|
||||||
|
- name: 'show username'
|
||||||
|
script: echo "GITEA_USERNAME is ${GITEA_USERNAME} and GITEA_PASSWORD is ${GITEA_PASSWORD}"
|
||||||
|
- name: sync to gitea
|
||||||
|
image: tencentcom/git-sync
|
||||||
|
settings:
|
||||||
|
target_url: https://git.xiongxiao.me/kevisual/query-awesome.git
|
||||||
|
auth_type: https
|
||||||
|
username: "oauth2"
|
||||||
|
password: ${GITEA_TOKEN}
|
||||||
|
git_user: "abearxiong"
|
||||||
|
git_email: "xiongxiao@xiongxiao.me"
|
||||||
|
sync_mode: rebase
|
||||||
|
branch: main
|
||||||
|
web_trigger_sync_from_gitea:
|
||||||
|
- services:
|
||||||
|
- docker
|
||||||
|
imports:
|
||||||
|
- https://cnb.cool/kevisual/env/-/blob/main/env.yml
|
||||||
|
stages:
|
||||||
|
- name: '添加 gitea的origin'
|
||||||
|
script: |
|
||||||
|
git remote remove gitea 2>/dev/null || true
|
||||||
|
git remote add gitea https://oauth2:${GITEA_TOKEN}@git.xiongxiao.me/kevisual/query-awesome.git
|
||||||
|
- name: '同步gitea代码到当前仓库'
|
||||||
|
script: git pull gitea main
|
||||||
|
- name: '提交到原本的origin'
|
||||||
|
script: git push origin main
|
||||||
|
|
||||||
|
|
||||||
|
"**":
|
||||||
|
web_trigger_test:
|
||||||
|
- stages:
|
||||||
|
- name: 执行任务
|
||||||
|
script: echo "job"
|
||||||
11
.cnb/web_trigger.yml
Normal file
11
.cnb/web_trigger.yml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# .cnb/web_trigger.yml
|
||||||
|
branch:
|
||||||
|
# 如下按钮在分支名以 release 开头的分支详情页面显示
|
||||||
|
- reg: "^main"
|
||||||
|
buttons:
|
||||||
|
- name: 同步代码到gitea
|
||||||
|
desc: 同步代码到gitea
|
||||||
|
event: web_trigger_sync_to_gitea
|
||||||
|
- name: 同步gitea代码到当前仓库
|
||||||
|
desc: 同步gitea代码到当前仓库
|
||||||
|
event: web_trigger_sync_from_gitea
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
// @ts-check
|
|
||||||
import { execSync } from 'node:child_process';
|
|
||||||
import glob from 'fast-glob';
|
|
||||||
import fs from 'node:fs';
|
|
||||||
const files = await glob(['packages/*/dist', 'submodules/*/dist'], { onlyDirectories: true });
|
|
||||||
|
|
||||||
console.log('files', files);
|
|
||||||
const clean = 'rimraf dist && mkdir dist';
|
|
||||||
execSync(clean);
|
|
||||||
for (let dir of files) {
|
|
||||||
const rsync = `rsync ${dir}/* ./dist`;
|
|
||||||
execSync(rsync);
|
|
||||||
}
|
|
||||||
44
package.json
44
package.json
@@ -1,37 +1,49 @@
|
|||||||
{
|
{
|
||||||
"name": "@kevisual/query-awesome",
|
"name": "@kevisual/api",
|
||||||
"version": "0.0.2",
|
"version": "0.0.15",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "mod.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "turbo run build",
|
"build": "bun run bun.config.ts",
|
||||||
"postbuild": "bun bun.copy.config.mjs"
|
"postbuild": "bun bun.copy.config.mjs"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist",
|
||||||
|
"query",
|
||||||
|
"mod.ts"
|
||||||
],
|
],
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"packageManager": "pnpm@10.24.0",
|
"packageManager": "pnpm@10.27.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@kevisual/query": "^0.0.31",
|
"@kevisual/cache": "^0.0.4",
|
||||||
"@kevisual/router": "^0.0.36",
|
"@kevisual/query": "^0.0.33",
|
||||||
|
"@kevisual/router": "^0.0.52",
|
||||||
"@kevisual/types": "^0.0.10",
|
"@kevisual/types": "^0.0.10",
|
||||||
"@kevisual/use-config": "^1.0.21",
|
"@kevisual/use-config": "^1.0.21",
|
||||||
"fast-glob": "^3.3.3",
|
"@types/bun": "^1.3.5",
|
||||||
"tsup": "^8.5.1"
|
"@types/node": "^25.0.3",
|
||||||
|
"dotenv": "^17.2.3",
|
||||||
|
"fast-glob": "^3.3.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@kevisual/js-filter": "^0.0.3",
|
||||||
|
"@kevisual/load": "^0.0.6",
|
||||||
|
"es-toolkit": "^1.43.0",
|
||||||
|
"eventemitter3": "^5.0.1",
|
||||||
|
"nanoid": "^5.1.6"
|
||||||
},
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": "./mod.ts",
|
||||||
"import": "./dist/query-login-browser.js"
|
"./login": "./query/query-login/query-login-browser.ts",
|
||||||
},
|
"./login-node": "./query/query-login/query-login-node.ts",
|
||||||
"./*": {
|
"./config": "./query/query-config/query-config.ts",
|
||||||
"import": "./dist/*"
|
"./proxy": "./query/query-proxy/index.ts",
|
||||||
}
|
"./query/**/*.ts": "./query/**/*.ts"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
|
|
||||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
|
||||||
ignore-workspace-root-check=true
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://kevisual.xiongxiao.me/root/ai/kevisual/tools/kevisual-sync/schema.json?v=2",
|
|
||||||
"metadata": {
|
|
||||||
"share": "public"
|
|
||||||
},
|
|
||||||
"checkDir": {
|
|
||||||
"query": {
|
|
||||||
"url": "https://kevisual.xiongxiao.me/root/ai/code/registry/query",
|
|
||||||
"enabled": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"syncDirectory": [
|
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
"query/**/*"
|
|
||||||
],
|
|
||||||
"ignore": [],
|
|
||||||
"registry": "https://kevisual.xiongxiao.me/root/ai/code/registry"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sync": {}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@kevisual/api",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"build2": "bun bun.config.mjs",
|
|
||||||
"download": "ev sync download",
|
|
||||||
"upload": "ev sync upload"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"files": [
|
|
||||||
"src",
|
|
||||||
"query",
|
|
||||||
"dist"
|
|
||||||
],
|
|
||||||
"publishConfig": {
|
|
||||||
"access": "public"
|
|
||||||
},
|
|
||||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
|
||||||
"license": "MIT",
|
|
||||||
"packageManager": "pnpm@10.6.2",
|
|
||||||
"type": "module",
|
|
||||||
"dependencies": {
|
|
||||||
"@kevisual/query": "^0.0.18",
|
|
||||||
"@kevisual/router": "^0.0.20"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@kevisual/types": "^0.0.10",
|
|
||||||
"@types/node": "^22.15.27"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://kevisual.xiongxiao.me/root/ai/kevisual/tools/kevisual-sync/schema.json?v=2",
|
|
||||||
"metadata": {
|
|
||||||
"share": "public"
|
|
||||||
},
|
|
||||||
"checkDir": {
|
|
||||||
"src/query": {
|
|
||||||
"url": "https://kevisual.xiongxiao.me/root/ai/code/registry/query",
|
|
||||||
"enabled": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"syncDirectory": [
|
|
||||||
{
|
|
||||||
"files": [
|
|
||||||
"src/query/**/*"
|
|
||||||
],
|
|
||||||
"ignore": [],
|
|
||||||
"registry": "https://kevisual.xiongxiao.me/root/ai/code/registry",
|
|
||||||
"replace": {
|
|
||||||
"src/": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"sync": {}
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
import { Query } from '@kevisual/query';
|
|
||||||
import type { Result, DataOpts } from '@kevisual/query/query';
|
|
||||||
|
|
||||||
export type SimpleObject = Record<string, any>;
|
|
||||||
export const markType = ['simple', 'md', 'mdx', 'wallnote', 'excalidraw', 'chat'] as const;
|
|
||||||
export type MarkType = (typeof markType)[number];
|
|
||||||
export type MarkData = {
|
|
||||||
nodes?: any[];
|
|
||||||
edges?: any[];
|
|
||||||
elements?: any[];
|
|
||||||
permission?: any;
|
|
||||||
|
|
||||||
[key: string]: any;
|
|
||||||
};
|
|
||||||
export type Mark = {
|
|
||||||
id: string;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
markType: MarkType;
|
|
||||||
link: string;
|
|
||||||
data?: MarkData;
|
|
||||||
uid: string;
|
|
||||||
puid: string;
|
|
||||||
summary: string;
|
|
||||||
thumbnail?: string;
|
|
||||||
tags: string[];
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
version: number;
|
|
||||||
};
|
|
||||||
export type ShowMarkPick = Pick<Mark, 'id' | 'title' | 'description' | 'summary' | 'link' | 'tags' | 'thumbnail' | 'updatedAt'>;
|
|
||||||
|
|
||||||
export type SearchOpts = {
|
|
||||||
page?: number;
|
|
||||||
pageSize?: number;
|
|
||||||
search?: string;
|
|
||||||
sort?: string; // DESC, ASC
|
|
||||||
markType?: MarkType; // 类型
|
|
||||||
[key: string]: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type QueryMarkOpts<T extends SimpleObject = SimpleObject> = {
|
|
||||||
query?: Query;
|
|
||||||
isBrowser?: boolean;
|
|
||||||
onLoad?: () => void;
|
|
||||||
} & T;
|
|
||||||
|
|
||||||
export type ResultMarkList = {
|
|
||||||
list: Mark[];
|
|
||||||
pagination: {
|
|
||||||
pageSize: number;
|
|
||||||
current: number;
|
|
||||||
total: number;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
export type QueryMarkData = {
|
|
||||||
id?: string;
|
|
||||||
title?: string;
|
|
||||||
description?: string;
|
|
||||||
[key: string]: any;
|
|
||||||
};
|
|
||||||
export type QueryMarkResult = {
|
|
||||||
accessToken: string;
|
|
||||||
refreshToken: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class QueryMarkBase<T extends SimpleObject = SimpleObject> {
|
|
||||||
query: Query;
|
|
||||||
isBrowser: boolean;
|
|
||||||
load?: boolean;
|
|
||||||
storage?: Storage;
|
|
||||||
onLoad?: () => void;
|
|
||||||
|
|
||||||
constructor(opts?: QueryMarkOpts<T>) {
|
|
||||||
this.query = opts?.query || new Query();
|
|
||||||
this.isBrowser = opts?.isBrowser ?? true;
|
|
||||||
this.init();
|
|
||||||
this.onLoad = opts?.onLoad;
|
|
||||||
}
|
|
||||||
setQuery(query: Query) {
|
|
||||||
this.query = query;
|
|
||||||
}
|
|
||||||
private async init() {
|
|
||||||
this.load = true;
|
|
||||||
this.onLoad?.();
|
|
||||||
}
|
|
||||||
|
|
||||||
async post<T = Result<any>>(data: any, opts?: DataOpts): Promise<T> {
|
|
||||||
try {
|
|
||||||
return this.query.post({ path: 'mark', ...data }, opts) as Promise<T>;
|
|
||||||
} catch (error) {
|
|
||||||
console.log('error', error);
|
|
||||||
return {
|
|
||||||
code: 400,
|
|
||||||
} as any;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getMarkList(search: SearchOpts, opts?: DataOpts) {
|
|
||||||
return this.post<Result<ResultMarkList>>({ key: 'list', ...search }, opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getMark(id: string, opts?: DataOpts) {
|
|
||||||
return this.post<Result<Mark>>({ key: 'get', id }, opts);
|
|
||||||
}
|
|
||||||
async getVersion(id: string, opts?: DataOpts) {
|
|
||||||
return this.post<Result<{ version: number; id: string }>>({ key: 'getVersion', id }, opts);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 检查版本
|
|
||||||
* 当需要更新时,返回true
|
|
||||||
* @param id
|
|
||||||
* @param version
|
|
||||||
* @param opts
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async checkVersion(id: string, version?: number, opts?: DataOpts) {
|
|
||||||
if (!version) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const res = await this.getVersion(id, opts);
|
|
||||||
if (res.code === 200) {
|
|
||||||
if (res.data!.version > version) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateMark(data: any, opts?: DataOpts) {
|
|
||||||
return this.post<Result<Mark>>({ key: 'update', data }, opts);
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteMark(id: string, opts?: DataOpts) {
|
|
||||||
return this.post<Result<Mark>>({ key: 'delete', id }, opts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export class QueryMark extends QueryMarkBase<SimpleObject> {
|
|
||||||
markType: string;
|
|
||||||
constructor(opts?: QueryMarkOpts & { markType?: MarkType }) {
|
|
||||||
super(opts);
|
|
||||||
this.markType = opts?.markType || 'simple';
|
|
||||||
}
|
|
||||||
async getMarkList(search?: SearchOpts, opts?: DataOpts) {
|
|
||||||
return this.post<Result<ResultMarkList>>({ key: 'list', ...search, markType: this.markType }, opts);
|
|
||||||
}
|
|
||||||
async updateMark(data: any, opts?: DataOpts) {
|
|
||||||
if (!data.id) {
|
|
||||||
data.markType = this.markType || 'simple';
|
|
||||||
}
|
|
||||||
return super.updateMark(data, opts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@kevisual/types/json/frontend.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": ".",
|
|
||||||
"typeRoots": [
|
|
||||||
"./node_modules/@types",
|
|
||||||
"./node_modules/@kevisual"
|
|
||||||
],
|
|
||||||
"paths": {
|
|
||||||
"@/*": [
|
|
||||||
"src/*"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"src/**/*",
|
|
||||||
"query/**/*",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
// @ts-check
|
|
||||||
import { resolvePath, getDevInputs } from '@kevisual/use-config/env';
|
|
||||||
import { execSync } from 'node:child_process';
|
|
||||||
import glob from 'fast-glob';
|
|
||||||
|
|
||||||
const files = await glob(['src/defines/*.ts', 'src/query/*.ts', 'src/router/*.ts']);
|
|
||||||
const inputs = getDevInputs(files);
|
|
||||||
const external = ['@kevisual/router'];
|
|
||||||
for (let input of inputs) {
|
|
||||||
const entry = input.path;
|
|
||||||
const naming = input.naming;
|
|
||||||
/**
|
|
||||||
* @type {import('bun').BuildConfig}
|
|
||||||
*/
|
|
||||||
await Bun.build({
|
|
||||||
target: 'node',
|
|
||||||
format: 'esm',
|
|
||||||
entrypoints: [resolvePath(entry, { meta: import.meta })],
|
|
||||||
outdir: resolvePath('./dist', { meta: import.meta }),
|
|
||||||
|
|
||||||
naming: {
|
|
||||||
entry: naming + '.js',
|
|
||||||
},
|
|
||||||
external: external,
|
|
||||||
env: 'KEVISUAL_*',
|
|
||||||
});
|
|
||||||
|
|
||||||
const cmd = `dts -i ${entry} -o ${naming}.d.ts`;
|
|
||||||
execSync(cmd, { stdio: 'inherit' });
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@kevisual/query-list",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"build": "bun bun.config.mjs"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"files": [
|
|
||||||
"src",
|
|
||||||
"dist"
|
|
||||||
],
|
|
||||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
|
||||||
"license": "MIT",
|
|
||||||
"packageManager": "pnpm@10.6.2",
|
|
||||||
"type": "module"
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { QueryUtil } from '@kevisual/router/define';
|
|
||||||
|
|
||||||
export const shopDefine = QueryUtil.create({
|
|
||||||
getRegistry: {
|
|
||||||
path: 'shop',
|
|
||||||
key: 'get-registry',
|
|
||||||
description: '获取应用商店注册表信息',
|
|
||||||
},
|
|
||||||
|
|
||||||
listInstalled: {
|
|
||||||
path: 'shop',
|
|
||||||
key: 'list-installed',
|
|
||||||
description: '列出当前已安装的所有应用',
|
|
||||||
},
|
|
||||||
|
|
||||||
install: {
|
|
||||||
path: 'shop',
|
|
||||||
key: 'install',
|
|
||||||
description: '安装指定的应用,可以指定 id、type、force 和 yes 参数',
|
|
||||||
},
|
|
||||||
|
|
||||||
uninstall: {
|
|
||||||
path: 'shop',
|
|
||||||
key: 'uninstall',
|
|
||||||
description: '卸载指定的应用,可以指定 id 和 type 参数',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { shopDefine } from '../defines/query-shop-define.ts';
|
|
||||||
|
|
||||||
import { BaseQuery, DataOpts, Query } from '@kevisual/query/query';
|
|
||||||
|
|
||||||
export { shopDefine };
|
|
||||||
|
|
||||||
export class QueryApp extends BaseQuery {
|
|
||||||
appDefine = shopDefine;
|
|
||||||
constructor(opts?: { query: Query }) {
|
|
||||||
super(opts!);
|
|
||||||
this.appDefine.query = this.query;
|
|
||||||
}
|
|
||||||
get chain() {
|
|
||||||
return this.appDefine.queryChain;
|
|
||||||
}
|
|
||||||
getInstall(data: any, opts?: DataOpts) {
|
|
||||||
return this.appDefine.queryChain('install').post(data, opts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@kevisual/types/json/frontend.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": ".",
|
|
||||||
"typeRoots": [
|
|
||||||
"./node_modules/@types",
|
|
||||||
"./node_modules/@kevisual"
|
|
||||||
],
|
|
||||||
"paths": {
|
|
||||||
"@/*": [
|
|
||||||
"src/*"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"src/**/*",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
// @ts-check
|
|
||||||
import { resolvePath, getDevInputs } from '@kevisual/use-config/env';
|
|
||||||
import { execSync } from 'node:child_process';
|
|
||||||
import glob from 'fast-glob';
|
|
||||||
|
|
||||||
const files = await glob('src/*.ts');
|
|
||||||
const inputs = getDevInputs(files);
|
|
||||||
const external = ['@kevisual/router'];
|
|
||||||
for (let input of inputs) {
|
|
||||||
const entry = input.path;
|
|
||||||
const naming = input.naming;
|
|
||||||
/**
|
|
||||||
* @type {import('bun').BuildConfig}
|
|
||||||
*/
|
|
||||||
await Bun.build({
|
|
||||||
target: 'node',
|
|
||||||
format: 'esm',
|
|
||||||
entrypoints: [resolvePath(entry, { meta: import.meta })],
|
|
||||||
outdir: resolvePath('./dist', { meta: import.meta }),
|
|
||||||
|
|
||||||
naming: {
|
|
||||||
entry: naming + '.js',
|
|
||||||
},
|
|
||||||
external: external,
|
|
||||||
env: 'KEVISUAL_*',
|
|
||||||
});
|
|
||||||
|
|
||||||
const cmd = `dts -i ${entry} -o ${naming}.d.ts`;
|
|
||||||
execSync(cmd, { stdio: 'inherit' });
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@kevisual/query-app",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"description": "",
|
|
||||||
"main": "index.js",
|
|
||||||
"scripts": {
|
|
||||||
"build": "bun bun.config.mjs"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"files": [
|
|
||||||
"src",
|
|
||||||
"dist"
|
|
||||||
],
|
|
||||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
|
||||||
"license": "MIT",
|
|
||||||
"packageManager": "pnpm@10.6.2",
|
|
||||||
"type": "module"
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import { QueryUtil } from './common.ts';
|
|
||||||
|
|
||||||
export const appDefine = QueryUtil.create({
|
|
||||||
getApp: {
|
|
||||||
path: 'app',
|
|
||||||
key: 'get',
|
|
||||||
description: '获取应用信息',
|
|
||||||
},
|
|
||||||
|
|
||||||
updateApp: {
|
|
||||||
path: 'app',
|
|
||||||
key: 'update',
|
|
||||||
description: '更新应用信息',
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteApp: {
|
|
||||||
path: 'app',
|
|
||||||
key: 'delete',
|
|
||||||
description: '删除应用信息',
|
|
||||||
},
|
|
||||||
|
|
||||||
listApps: {
|
|
||||||
path: 'app',
|
|
||||||
key: 'list',
|
|
||||||
description: '列出所有应用信息',
|
|
||||||
},
|
|
||||||
|
|
||||||
canUploadFiles: {
|
|
||||||
path: 'app',
|
|
||||||
key: 'canUploadFiles',
|
|
||||||
description: '检查是否可以上传文件',
|
|
||||||
},
|
|
||||||
|
|
||||||
uploadFiles: {
|
|
||||||
path: 'app',
|
|
||||||
key: 'uploadFiles',
|
|
||||||
description: '上传文件',
|
|
||||||
},
|
|
||||||
|
|
||||||
publishApp: {
|
|
||||||
path: 'app',
|
|
||||||
key: 'publish',
|
|
||||||
description: '发布应用',
|
|
||||||
},
|
|
||||||
|
|
||||||
getMinioList: {
|
|
||||||
path: 'app',
|
|
||||||
key: 'get-minio-list',
|
|
||||||
description: '获取 MinIO 文件列表',
|
|
||||||
},
|
|
||||||
|
|
||||||
detectVersionList: {
|
|
||||||
path: 'app',
|
|
||||||
key: 'detectVersionList',
|
|
||||||
description: '检测版本列表并同步 MinIO 数据',
|
|
||||||
},
|
|
||||||
publicList: {
|
|
||||||
path: 'app',
|
|
||||||
key: 'public-list',
|
|
||||||
description: '获取公开应用列表',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { QueryUtil } from '@kevisual/router/define';
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import { appDefine } from './app';
|
|
||||||
import { userAppDefine } from './user-app';
|
|
||||||
export { appDefine, userAppDefine };
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { QueryUtil } from './common.ts';
|
|
||||||
|
|
||||||
export const userAppDefine = QueryUtil.create({
|
|
||||||
listUserApps: {
|
|
||||||
path: 'user-app',
|
|
||||||
key: 'list',
|
|
||||||
description: '列出当前用户的所有应用(不包含 data 字段)',
|
|
||||||
},
|
|
||||||
|
|
||||||
getUserApp: {
|
|
||||||
path: 'user-app',
|
|
||||||
key: 'get',
|
|
||||||
description: '获取用户应用信息,可以指定 id 或 key',
|
|
||||||
},
|
|
||||||
|
|
||||||
updateUserApp: {
|
|
||||||
path: 'user-app',
|
|
||||||
key: 'update',
|
|
||||||
description: '更新或创建用户应用',
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteUserApp: {
|
|
||||||
path: 'user-app',
|
|
||||||
key: 'delete',
|
|
||||||
description: '删除用户应用及关联数据',
|
|
||||||
},
|
|
||||||
|
|
||||||
testUserApp: {
|
|
||||||
path: 'user-app',
|
|
||||||
key: 'test',
|
|
||||||
description: '对 user-app 的数据进行测试,获取版本信息',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './defines/index.ts';
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { appDefine, userAppDefine } from './defines/index.ts';
|
|
||||||
|
|
||||||
import { BaseQuery, DataOpts, Query } from '@kevisual/query/query';
|
|
||||||
|
|
||||||
export { appDefine, userAppDefine };
|
|
||||||
|
|
||||||
export class QueryApp extends BaseQuery {
|
|
||||||
appDefine = appDefine;
|
|
||||||
userAppDefine = userAppDefine;
|
|
||||||
constructor(opts?: { query: Query }) {
|
|
||||||
super(opts!);
|
|
||||||
this.appDefine.query = this.query;
|
|
||||||
this.userAppDefine.query = this.query;
|
|
||||||
}
|
|
||||||
getList(data: any, opts?: DataOpts) {
|
|
||||||
return this.appDefine.queryChain('listApps').post(data, opts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { QueryApp } from '../query-app.ts';
|
|
||||||
import { Query } from '@kevisual/query/query';
|
|
||||||
const query = new Query();
|
|
||||||
const qa = new QueryApp({ query: query });
|
|
||||||
|
|
||||||
qa.appDefine.queryChain('getApp').post({});
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@kevisual/types/json/frontend.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": ".",
|
|
||||||
"typeRoots": [
|
|
||||||
"./node_modules/@types",
|
|
||||||
"./node_modules/@kevisual"
|
|
||||||
],
|
|
||||||
"paths": {
|
|
||||||
"@/*": [
|
|
||||||
"src/*"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"src/**/*",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
1684
pnpm-lock.yaml
generated
1684
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +0,0 @@
|
|||||||
packages:
|
|
||||||
- 'submodules/*'
|
|
||||||
- 'packages/*'
|
|
||||||
- 'apps/*'
|
|
||||||
- 'libs/*'
|
|
||||||
21
query/kevisual.json
Normal file
21
query/kevisual.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"name": "kevisual",
|
||||||
|
"share": "public"
|
||||||
|
},
|
||||||
|
"registry": "https://kevisual.cn/root/ai/kevisual/common/query/api",
|
||||||
|
"clone": {
|
||||||
|
".": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"syncd": [
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"**/*"
|
||||||
|
],
|
||||||
|
"registry": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sync": {}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { QueryUtil } from '@/query/index.ts';
|
import { QueryUtil } from '../../../query/index.ts';
|
||||||
|
|
||||||
type Message = {
|
type Message = {
|
||||||
role?: 'user' | 'assistant' | 'system' | 'tool';
|
role?: 'user' | 'assistant' | 'system' | 'tool';
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { QueryUtil } from '@/query/index.ts';
|
import { QueryUtil } from '../../../query/index.ts';
|
||||||
|
|
||||||
export const appDefine = QueryUtil.create({
|
export const appDefine = QueryUtil.create({
|
||||||
getApp: {
|
getApp: {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { QueryUtil } from '@/query/index.ts';
|
import { QueryUtil } from '../../../query/index.ts';
|
||||||
|
|
||||||
export const userAppDefine = QueryUtil.create({
|
export const userAppDefine = QueryUtil.create({
|
||||||
listUserApps: {
|
listUserApps: {
|
||||||
@@ -15,4 +15,18 @@ export class QueryApp extends BaseQuery {
|
|||||||
getList(data: any, opts?: DataOpts) {
|
getList(data: any, opts?: DataOpts) {
|
||||||
return this.appDefine.queryChain('listApps').post(data, opts);
|
return this.appDefine.queryChain('listApps').post(data, opts);
|
||||||
}
|
}
|
||||||
|
getPublicApp(data: any, opts?: DataOpts) {
|
||||||
|
return this.query.post({
|
||||||
|
path: 'app',
|
||||||
|
key: 'getApp',
|
||||||
|
data: data,
|
||||||
|
}, opts);
|
||||||
|
}
|
||||||
|
getApp(data: any, opts?: DataOpts) {
|
||||||
|
return this.query.post({
|
||||||
|
path: 'app',
|
||||||
|
key: 'get',
|
||||||
|
data: data,
|
||||||
|
}, opts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
121
query/query-config/query-config.ts
Normal file
121
query/query-config/query-config.ts
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
/**
|
||||||
|
* 配置查询
|
||||||
|
* @updatedAt 2025-12-03 10:33:00
|
||||||
|
*/
|
||||||
|
import { Query } from '@kevisual/query';
|
||||||
|
import type { Result } from '@kevisual/query/query';
|
||||||
|
type QueryConfigOpts = {
|
||||||
|
query?: Query;
|
||||||
|
};
|
||||||
|
export type Config<T = any> = {
|
||||||
|
id?: string;
|
||||||
|
title?: string;
|
||||||
|
key?: string;
|
||||||
|
description?: string;
|
||||||
|
data?: T;
|
||||||
|
createdAt?: string;
|
||||||
|
updatedAt?: string;
|
||||||
|
};
|
||||||
|
export type UploadConfig = {
|
||||||
|
key?: string;
|
||||||
|
version?: string;
|
||||||
|
};
|
||||||
|
type PostOpts = {
|
||||||
|
token?: string;
|
||||||
|
payload?: Record<string, any>;
|
||||||
|
};
|
||||||
|
export const defaultConfigKeys = ['upload.json', 'workspace.json', 'ai.json', 'user.json', 'life.json'] as const;
|
||||||
|
type DefaultConfigKey = (typeof defaultConfigKeys)[number];
|
||||||
|
|
||||||
|
export class QueryConfig {
|
||||||
|
query: Query;
|
||||||
|
constructor(opts?: QueryConfigOpts) {
|
||||||
|
this.query = opts?.query || new Query();
|
||||||
|
}
|
||||||
|
async post<T = Config>(data: any) {
|
||||||
|
return this.query.post<T>({ path: 'config', ...data });
|
||||||
|
}
|
||||||
|
async getConfig({ id, key }: { id?: string; key?: string }, opts?: PostOpts) {
|
||||||
|
return this.post({
|
||||||
|
key: 'get',
|
||||||
|
data: {
|
||||||
|
id,
|
||||||
|
key,
|
||||||
|
},
|
||||||
|
...opts,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async updateConfig(data: Config, opts?: PostOpts) {
|
||||||
|
return this.post({
|
||||||
|
key: 'update',
|
||||||
|
data,
|
||||||
|
...opts,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async deleteConfig(data: { id?: string, key?: string }, opts?: PostOpts) {
|
||||||
|
console.log('Delete Config Params:', data);
|
||||||
|
return this.post({
|
||||||
|
key: 'delete',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async listConfig(opts?: PostOpts) {
|
||||||
|
return this.post<{ list: Config[] }>({
|
||||||
|
key: 'list',
|
||||||
|
...opts,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取上传配置
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async getUploadConfig(opts?: PostOpts) {
|
||||||
|
return this.post<Result<Config<UploadConfig>>>({
|
||||||
|
key: 'getUploadConfig',
|
||||||
|
...opts,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 更新上传配置
|
||||||
|
* @param data
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async updateUploadConfig(data: Config, opts?: PostOpts) {
|
||||||
|
return this.post<Result<Config<UploadConfig>>>({
|
||||||
|
key: 'updateUploadConfig',
|
||||||
|
data,
|
||||||
|
...opts,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测配置是否存在
|
||||||
|
* @param id
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async detectConfig(opts?: PostOpts) {
|
||||||
|
return this.post<{ updateList: Config[] }>({
|
||||||
|
key: 'detect',
|
||||||
|
...opts,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 获取配置, 获取默认的配置项
|
||||||
|
* @param key
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async getConfigByKey(key: DefaultConfigKey, opts?: PostOpts) {
|
||||||
|
return this.post<Result<Config>>({
|
||||||
|
key: 'defaultConfig',
|
||||||
|
configKey: key,
|
||||||
|
...opts,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async getByKey<T = any>(key: string, opts?: PostOpts) {
|
||||||
|
return this.post<Result<Config<T>>>({
|
||||||
|
key: 'get',
|
||||||
|
...opts,
|
||||||
|
data: { key },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ import type { Result, DataOpts } from '@kevisual/query/query';
|
|||||||
import { setBaseResponse } from '@kevisual/query/query';
|
import { setBaseResponse } from '@kevisual/query/query';
|
||||||
import { LoginCacheStore, CacheStore } from './login-cache.ts';
|
import { LoginCacheStore, CacheStore } from './login-cache.ts';
|
||||||
import { Cache } from './login-cache.ts';
|
import { Cache } from './login-cache.ts';
|
||||||
|
import { BaseLoad } from '@kevisual/load';
|
||||||
export type QueryLoginOpts = {
|
export type QueryLoginOpts = {
|
||||||
query?: Query;
|
query?: Query;
|
||||||
isBrowser?: boolean;
|
isBrowser?: boolean;
|
||||||
@@ -418,17 +418,74 @@ export class QueryLogin extends BaseQuery {
|
|||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* 使用web登录,创建url地址, 需要MD5和jsonwebtoken
|
* 使用web登录,创建url地址, 需要MD5和jsonwebtoken
|
||||||
|
*
|
||||||
|
*
|
||||||
|
|
||||||
|
import MD5 from 'crypto-js/md5.js';
|
||||||
|
import jsonwebtoken from 'jsonwebtoken';
|
||||||
|
|
||||||
*/
|
*/
|
||||||
loginWithWeb(baseURL: string, { MD5, jsonwebtoken }: { MD5: any; jsonwebtoken: any }) {
|
loginWithWeb(baseURL: string, { MD5, jsonwebtoken }: { MD5?: any; jsonwebtoken?: any }) {
|
||||||
const randomId = Math.random().toString(36).substring(2, 15);
|
const randomId = Math.random().toString(36).substring(2, 15);
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
const tokenSecret = 'xiao' + randomId;
|
const tokenSecret = 'xiao' + randomId;
|
||||||
const sign = MD5(`${tokenSecret}${timestamp}`).toString();
|
let sign = '';
|
||||||
const token = jsonwebtoken.sign({ randomId, timestamp, sign }, tokenSecret, {
|
if (MD5) {
|
||||||
// 10分钟过期
|
sign = MD5(`${tokenSecret}${timestamp}`).toString();
|
||||||
expiresIn: 60 * 10, // 10分钟
|
}
|
||||||
});
|
let token = '';
|
||||||
|
if (jsonwebtoken) {
|
||||||
|
token = jsonwebtoken.sign({ randomId, timestamp, sign }, tokenSecret, {
|
||||||
|
// 10分钟过期
|
||||||
|
expiresIn: 60 * 10, // 10分钟
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
token = tokenSecret;
|
||||||
|
}
|
||||||
const url = `${baseURL}/api/router?path=user&key=webLogin&p&loginToken=${token}&sign=${sign}&randomId=${randomId}`;
|
const url = `${baseURL}/api/router?path=user&key=webLogin&p&loginToken=${token}&sign=${sign}&randomId=${randomId}`;
|
||||||
return { url, token, tokenSecret };
|
return { url, token, tokenSecret };
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*轮询登录状态
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
const res = queryLogin.loginWithWeb(baseURL, { MD5, jsonwebtoken });
|
||||||
|
await pollLoginStatus(res.token, { tokenSecret: res.tokenSecret });
|
||||||
|
* 轮询登录状态
|
||||||
|
*/
|
||||||
|
async pollLoginStatus(data: { token: string; tokenSecret: string }) {
|
||||||
|
const token = data.token;
|
||||||
|
const load = new BaseLoad();
|
||||||
|
load.load(
|
||||||
|
async () => {
|
||||||
|
const res = await this.checkLoginStatus(token);
|
||||||
|
if (res.code === 500) {
|
||||||
|
load.cancel('check-login-status');
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'check-login-status',
|
||||||
|
isReRun: true,
|
||||||
|
checkSuccess: (data) => {
|
||||||
|
return data?.code === 200;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const res = await load.hasLoaded('check-login-status', {
|
||||||
|
timeout: 60 * 3 * 1000, // 3分钟超时
|
||||||
|
});
|
||||||
|
if (res.code === 200 && res.data?.code === 200) {
|
||||||
|
try {
|
||||||
|
console.log('网页登录成功');
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.log('登录失败', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('登录失败', res);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
405
query/query-proxy/index.ts
Normal file
405
query/query-proxy/index.ts
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
import { Query, Result } from '@kevisual/query/query';
|
||||||
|
import { QueryRouterServer, Route } from '@kevisual/router/src/route.ts';
|
||||||
|
import { filter } from '@kevisual/js-filter'
|
||||||
|
import { EventEmitter } from 'eventemitter3';
|
||||||
|
|
||||||
|
export const RouteTypeList = ['api', 'context', 'worker', 'page'] as const;
|
||||||
|
export type RouterViewItem = RouterViewApi | RouterViewContext | RouterViewWorker | RouteViewPage;
|
||||||
|
|
||||||
|
type RouteViewBase = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
enabled?: boolean;
|
||||||
|
}
|
||||||
|
export type RouterViewApi = {
|
||||||
|
type: 'api',
|
||||||
|
api: {
|
||||||
|
url: string,
|
||||||
|
// 已初始化的query实例,不需要编辑配置
|
||||||
|
query?: Query
|
||||||
|
}
|
||||||
|
} & RouteViewBase;
|
||||||
|
|
||||||
|
export type RouterViewContext = {
|
||||||
|
type: 'context',
|
||||||
|
context: {
|
||||||
|
key: string,
|
||||||
|
// 从context中获取router,不需要编辑配置
|
||||||
|
router?: QueryRouterServer
|
||||||
|
}
|
||||||
|
} & RouteViewBase;
|
||||||
|
export type RouterViewWorker = {
|
||||||
|
type: 'worker',
|
||||||
|
worker: {
|
||||||
|
type: 'Worker' | 'SharedWorker' | 'serviceWorker',
|
||||||
|
url: string,
|
||||||
|
// 已初始化的worker实例,不需要编辑配置
|
||||||
|
worker?: Worker | SharedWorker | ServiceWorker,
|
||||||
|
/**
|
||||||
|
* worker选项
|
||||||
|
* default: { type: 'module' }
|
||||||
|
*/
|
||||||
|
workerOptions?: {
|
||||||
|
type: 'module' | 'classic'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} & RouteViewBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注入 js 的url地址,使用importScripts加载
|
||||||
|
*/
|
||||||
|
export type RouteViewPage = {
|
||||||
|
type: 'page',
|
||||||
|
page: {
|
||||||
|
url: string,
|
||||||
|
}
|
||||||
|
} & RouteViewBase;
|
||||||
|
|
||||||
|
export type RouterViewQuery = {
|
||||||
|
id: string,
|
||||||
|
query: string,
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
export type RouterViewData = {
|
||||||
|
data: { items: RouterViewItem[]; }
|
||||||
|
views: RouterViewQuery[];
|
||||||
|
viewId?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class QueryProxy {
|
||||||
|
router: QueryRouterServer;
|
||||||
|
token?: string;
|
||||||
|
routerViewItems: RouterViewItem[];
|
||||||
|
views: RouterViewQuery[];
|
||||||
|
emitter: EventEmitter;
|
||||||
|
constructor(opts?: { router?: QueryRouterServer, token?: string, routerViewData?: RouterViewData }) {
|
||||||
|
this.router = opts?.router || new QueryRouterServer();
|
||||||
|
this.token = opts?.token || this.getDefulatToken();
|
||||||
|
this.routerViewItems = opts?.routerViewData?.data?.items || [];
|
||||||
|
this.views = opts?.routerViewData?.views || [];
|
||||||
|
this.initRouterViewQuery();
|
||||||
|
this.emitter = new EventEmitter();
|
||||||
|
}
|
||||||
|
getDefulatToken() {
|
||||||
|
try {
|
||||||
|
if (typeof window !== 'undefined' && typeof window.localStorage !== 'undefined') {
|
||||||
|
return localStorage.getItem('token') || undefined;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async initRouterViewQuery() {
|
||||||
|
this.routerViewItems = this.routerViewItems.map(item => {
|
||||||
|
if (item.type === 'api' && item.api?.url) {
|
||||||
|
const url = item.api.url;
|
||||||
|
if (item?.api?.query) return item;
|
||||||
|
item['api'] = { url: url, query: new Query({ url: url }) };
|
||||||
|
}
|
||||||
|
if (item.type === 'worker' && item.worker?.url) {
|
||||||
|
let viewItem = item as RouterViewWorker;
|
||||||
|
if (!item.worker?.workerOptions?.type) {
|
||||||
|
item.worker.workerOptions = { ...item.worker.workerOptions, type: 'module' };
|
||||||
|
}
|
||||||
|
if (item.worker.worker) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
let worker: Worker | SharedWorker | ServiceWorker | undefined = undefined;
|
||||||
|
if (item.worker.type === 'SharedWorker') {
|
||||||
|
worker = new SharedWorker(item.worker.url, item.worker.workerOptions);
|
||||||
|
worker.port.start();
|
||||||
|
} else if (viewItem.worker.type === 'serviceWorker') {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.register(viewItem.worker.url, item.worker.workerOptions).then(function (registration) {
|
||||||
|
console.debug('注册serviceWorker成功 ', registration.scope);
|
||||||
|
}, function (err) {
|
||||||
|
console.debug('注册 serviceWorker 失败: ', err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.warn('当前浏览器不支持serviceWorker');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
worker = new Worker(viewItem.worker.url, item.worker.workerOptions);
|
||||||
|
}
|
||||||
|
viewItem['worker']['worker'] = worker;
|
||||||
|
}
|
||||||
|
if (item.type === 'context' && item.context?.key) {
|
||||||
|
if (item.context?.router) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
const context = globalThis['context'] || {}
|
||||||
|
const router = context[item.context.key] as QueryRouterServer;
|
||||||
|
if (router) {
|
||||||
|
item['context']['router'] = router;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}).filter(item => {
|
||||||
|
const enabled = item.enabled ?? true;
|
||||||
|
return enabled;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化路由
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async init() {
|
||||||
|
const routerViewItems = this.routerViewItems || [];
|
||||||
|
if (routerViewItems.length === 0) {
|
||||||
|
// 默认初始化api类型路由
|
||||||
|
await this.initApi();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const item of routerViewItems) {
|
||||||
|
switch (item.type) {
|
||||||
|
case 'api':
|
||||||
|
await this.initApi(item);
|
||||||
|
break;
|
||||||
|
case 'context':
|
||||||
|
await this.initContext(item);
|
||||||
|
break;
|
||||||
|
case 'worker':
|
||||||
|
await this.initWorker(item);
|
||||||
|
break;
|
||||||
|
case 'page':
|
||||||
|
await this.initPage(item);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.emitter.emit('initComplete');
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 监听初始化完成
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async listenInitComplete(): Promise<boolean> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
this.emitter.removeAllListeners('initComplete');
|
||||||
|
resolve(false);
|
||||||
|
}, 3 * 60000); // 3分钟超时
|
||||||
|
const func = () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
this.emitter.once('initComplete', func);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async initApi(item?: RouterViewApi) {
|
||||||
|
const that = this;
|
||||||
|
const query = item?.api?.query || new Query({ url: item?.api?.url || '/api/router' })
|
||||||
|
const res = await query.post<{ list: RouterItem[] }>({ path: "router", key: 'list', token: this.token });
|
||||||
|
if (res.code !== 200) {
|
||||||
|
console.error('Failed to init query proxy router:', res.message);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const _list = res.data?.list || []
|
||||||
|
for (const r of _list) {
|
||||||
|
if (r.path || r.id) {
|
||||||
|
console.debug(`注册路由: [${r.path}] ${r?.key}`, 'API');
|
||||||
|
let metadata = r.metadata || {};
|
||||||
|
metadata.viewItem = item;
|
||||||
|
this.router.route({
|
||||||
|
path: r.path,
|
||||||
|
key: r.key || '',
|
||||||
|
id: r.id,
|
||||||
|
description: r.description,
|
||||||
|
metadata: metadata,
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
const msg = { ...ctx.query };
|
||||||
|
if (msg.token === undefined && that.token !== undefined) {
|
||||||
|
msg.token = that.token;
|
||||||
|
}
|
||||||
|
const res = await query.post<any>({ path: r.path, key: r.key, ...msg });
|
||||||
|
ctx.forward(res)
|
||||||
|
}).addTo(that.router);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async initContext(item?: RouterViewContext) {
|
||||||
|
// @ts-ignore
|
||||||
|
const context = globalThis['context'] || {}
|
||||||
|
const router = item?.context?.router || context[item?.context?.key] as QueryRouterServer;
|
||||||
|
if (!router) {
|
||||||
|
console.warn(`未发现Context router ${item?.context?.key}`);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const routes = router.getList();
|
||||||
|
for (const r of routes) {
|
||||||
|
console.debug(`注册路由: [${r.path}] ${r?.key}`, 'Context');
|
||||||
|
let metadata = r.metadata || {};
|
||||||
|
metadata.viewItem = item;
|
||||||
|
this.router.route({
|
||||||
|
path: r.path,
|
||||||
|
key: r.key || '',
|
||||||
|
id: r.id,
|
||||||
|
description: r.description,
|
||||||
|
metadata: metadata,
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
const res = await router.run({ path: r.path, key: r.key, ...ctx.query });
|
||||||
|
ctx.forward(res)
|
||||||
|
}).addTo(this.router);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
generateId() {
|
||||||
|
return 'route_' + Math.random().toString(36).substring(2, 9);
|
||||||
|
}
|
||||||
|
async initWorker(item?: RouterViewWorker) {
|
||||||
|
const that = this;
|
||||||
|
if (!item?.worker?.url) {
|
||||||
|
console.warn('Worker URL not provided');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const viewItem = item.worker;
|
||||||
|
const worker = viewItem?.worker;
|
||||||
|
if (!worker) {
|
||||||
|
console.warn('Worker not initialized');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const callResponse = (e: MessageEvent) => {
|
||||||
|
const msg = e.data;
|
||||||
|
if (msg.requestId) {
|
||||||
|
const requestId = msg.requestId;
|
||||||
|
that.emitter.emit(requestId, msg);
|
||||||
|
} else {
|
||||||
|
that.router.run(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (item.worker.type === 'SharedWorker') {
|
||||||
|
const port = (worker as SharedWorker).port;
|
||||||
|
port.onmessage = callResponse;
|
||||||
|
port.start();
|
||||||
|
} else if (item.worker.type === 'serviceWorker') {
|
||||||
|
navigator.serviceWorker.addEventListener('message', callResponse);
|
||||||
|
} else {
|
||||||
|
(worker as Worker).onmessage = callResponse;
|
||||||
|
}
|
||||||
|
const callWorker = async (msg: any, viewItem: RouterViewWorker['worker']): Promise<Result> => {
|
||||||
|
const requestId = this.generateId();
|
||||||
|
const worker = viewItem?.worker;
|
||||||
|
if (!worker) {
|
||||||
|
return { code: 500, message: 'Worker未初始化' };
|
||||||
|
}
|
||||||
|
let port: MessagePort | Worker | ServiceWorker;
|
||||||
|
if (viewItem.type === 'SharedWorker') {
|
||||||
|
port = (worker as SharedWorker).port;
|
||||||
|
} else {
|
||||||
|
port = worker as Worker | ServiceWorker;
|
||||||
|
}
|
||||||
|
port.postMessage({
|
||||||
|
...msg,
|
||||||
|
requestId: requestId,
|
||||||
|
})
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
that.emitter.removeAllListeners(requestId);
|
||||||
|
resolve({ code: 500, message: '请求超时' });
|
||||||
|
}, 3 * 60 * 1000); // 3分钟超时
|
||||||
|
that.emitter.once(requestId, (res: any) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
resolve(res);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await callWorker({
|
||||||
|
path: "router",
|
||||||
|
key: 'list',
|
||||||
|
token: this.token,
|
||||||
|
}, viewItem);
|
||||||
|
|
||||||
|
if (res.code !== 200) {
|
||||||
|
console.error('Failed to init query proxy router:', res.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const _list = res.data?.list || []
|
||||||
|
for (const r of _list) {
|
||||||
|
if (r.path || r.id) {
|
||||||
|
console.debug(`注册路由: [${r.path}] ${r?.key}`, 'API');
|
||||||
|
let metadata = r.metadata || {};
|
||||||
|
metadata.viewItem = item;
|
||||||
|
this.router.route({
|
||||||
|
path: r.path,
|
||||||
|
key: r.key || '',
|
||||||
|
id: r.id,
|
||||||
|
description: r.description,
|
||||||
|
metadata: metadata,
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
const msg = { ...ctx.query };
|
||||||
|
if (msg.token === undefined && that.token !== undefined) {
|
||||||
|
msg.token = that.token;
|
||||||
|
}
|
||||||
|
const res = await callWorker({ path: r.path, key: r.key, ...msg }, viewItem);
|
||||||
|
ctx.forward(res)
|
||||||
|
}).addTo(that.router);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async initPage(item?: RouteViewPage) {
|
||||||
|
if (!item?.page?.url) {
|
||||||
|
console.warn('Page地址未提供');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const url = item.page.url;
|
||||||
|
try {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
await import(url).then((module) => { }).catch((err) => {
|
||||||
|
console.error('引入Page脚本失败:', url, err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('引入Page脚本失败:', url, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 列出路由
|
||||||
|
* @param filter
|
||||||
|
* @param query WHERE metadata.tags CONTAINS 'premium'
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async listRoutes(filterFn?: (item: Route) => boolean, opts?: { viewId?: string, query?: string }) {
|
||||||
|
let query = opts?.query;
|
||||||
|
if (opts?.viewId) {
|
||||||
|
const view = this.views.find(v => v.id === opts.viewId);
|
||||||
|
if (view) {
|
||||||
|
query = view.query;
|
||||||
|
}
|
||||||
|
} if (opts?.query) {
|
||||||
|
query = opts.query;
|
||||||
|
}
|
||||||
|
const routes = this.router.routes.filter(filterFn || (() => true));
|
||||||
|
if (query) {
|
||||||
|
return filter(routes, query);
|
||||||
|
}
|
||||||
|
return routes;
|
||||||
|
}
|
||||||
|
async getViewQuery(viewId: string) {
|
||||||
|
const view = this.views.find(v => v.id === viewId);
|
||||||
|
if (view) {
|
||||||
|
return view.query;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 运行路由
|
||||||
|
* @param msg
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async run(msg: { id?: string, path?: string, key?: string }) {
|
||||||
|
return await this.router.run(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RouterItem = {
|
||||||
|
id?: string;
|
||||||
|
path?: string;
|
||||||
|
key?: string;
|
||||||
|
description?: string;
|
||||||
|
middleware?: string[];
|
||||||
|
metadata?: Record<string, any>;
|
||||||
|
}
|
||||||
64
query/query-secret/query-secret.ts
Normal file
64
query/query-secret/query-secret.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* 配置查询
|
||||||
|
* @updatedAt 2025-12-03 10:33:00
|
||||||
|
*/
|
||||||
|
import { Query } from '@kevisual/query';
|
||||||
|
type QueryConfigOpts = {
|
||||||
|
query?: Query;
|
||||||
|
};
|
||||||
|
export type Config<T = any> = {
|
||||||
|
id?: string;
|
||||||
|
title?: string;
|
||||||
|
key?: string;
|
||||||
|
description?: string;
|
||||||
|
data?: T;
|
||||||
|
createdAt?: string;
|
||||||
|
updatedAt?: string;
|
||||||
|
};
|
||||||
|
export type UploadConfig = {
|
||||||
|
key?: string;
|
||||||
|
version?: string;
|
||||||
|
};
|
||||||
|
type PostOpts = {
|
||||||
|
token?: string;
|
||||||
|
payload?: Record<string, any>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class QueryConfig {
|
||||||
|
query: Query;
|
||||||
|
constructor(opts?: QueryConfigOpts) {
|
||||||
|
this.query = opts?.query || new Query();
|
||||||
|
}
|
||||||
|
async post<T = Config>(data: any) {
|
||||||
|
return this.query.post<T>({ path: 'secret', ...data });
|
||||||
|
}
|
||||||
|
async getItem({ id, key }: { id?: string; key?: string }, opts?: PostOpts) {
|
||||||
|
return this.post({
|
||||||
|
key: 'get',
|
||||||
|
data: {
|
||||||
|
id,
|
||||||
|
key,
|
||||||
|
},
|
||||||
|
...opts,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async updateItem(data: Config, opts?: PostOpts) {
|
||||||
|
return this.post({
|
||||||
|
key: 'update',
|
||||||
|
data,
|
||||||
|
...opts,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async deleteItem(data: { id?: string, key?: string }, opts?: PostOpts) {
|
||||||
|
return this.post({
|
||||||
|
key: 'delete',
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async listItems(opts?: PostOpts) {
|
||||||
|
return this.post<{ list: Config[] }>({
|
||||||
|
key: 'list',
|
||||||
|
...opts,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { QueryUtil } from '@/query/index.ts';
|
import { QueryUtil } from '../../../query/index.ts';
|
||||||
|
|
||||||
export const shopDefine = QueryUtil.create({
|
export const shopDefine = QueryUtil.create({
|
||||||
getRegistry: {
|
getRegistry: {
|
||||||
@@ -4,14 +4,13 @@ import { BaseQuery, DataOpts, Query } from '@kevisual/query/query';
|
|||||||
|
|
||||||
export { shopDefine };
|
export { shopDefine };
|
||||||
|
|
||||||
export class QueryShop<T extends Query = Query> extends BaseQuery<T, typeof shopDefine> {
|
export class QueryShop<T extends Query = Query> extends BaseQuery<T> {
|
||||||
constructor(opts?: { query: T }) {
|
constructor(opts?: { query: T }) {
|
||||||
super({
|
super({
|
||||||
query: opts?.query!,
|
query: opts?.query!,
|
||||||
queryDefine: shopDefine,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
getInstall(data: any, opts?: DataOpts) {
|
getInstall(data: any, opts?: DataOpts) {
|
||||||
return this.queryDefine.queryChain('install').post(data, opts);
|
return this.query.post(data, opts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,8 @@ import { UploadProgress } from './core/upload-progress.ts';
|
|||||||
|
|
||||||
export { uploadFiles, uploadFileChunked, UploadProgress };
|
export { uploadFiles, uploadFileChunked, UploadProgress };
|
||||||
|
|
||||||
export * from './utils/to-file.ts';
|
export { toTextFile, toFile, getDirectoryAndName } from './utils/to-file.ts';
|
||||||
|
|
||||||
export { randomId } from './utils/random-id.ts';
|
export { randomId } from './utils/random-id.ts';
|
||||||
|
|
||||||
export { filterFiles } from './utils/filter-files.ts';
|
export { filterFiles } from './utils/filter-files.ts';
|
||||||
168
readme.md
168
readme.md
@@ -1,3 +1,169 @@
|
|||||||
# query-awesome
|
# @kevisual/api
|
||||||
|
|
||||||
对 kevisual 相关的query router 的模块整理
|
对 kevisual 相关的query router 的模块整理
|
||||||
|
|
||||||
|
包含的模块:
|
||||||
|
|
||||||
|
## query-config - 配置管理模块
|
||||||
|
|
||||||
|
提供配置的增删改查功能,支持默认配置项管理。
|
||||||
|
|
||||||
|
**主要功能:**
|
||||||
|
- 配置的获取、更新、删除
|
||||||
|
- 上传配置管理
|
||||||
|
- 默认配置项支持(upload.json, workspace.json, ai.json, user.json, life.json)
|
||||||
|
- 配置检测功能
|
||||||
|
|
||||||
|
**使用示例:**
|
||||||
|
```typescript
|
||||||
|
import { QueryConfig } from './query-config/query-config';
|
||||||
|
|
||||||
|
const config = new QueryConfig();
|
||||||
|
await config.getConfig({ key: 'upload.json' });
|
||||||
|
await config.updateConfig({ key: 'config.json', data: { setting: true } });
|
||||||
|
```
|
||||||
|
|
||||||
|
## query-secret - 密钥管理模块
|
||||||
|
|
||||||
|
用于管理敏感信息和密钥的存储与检索。
|
||||||
|
|
||||||
|
**主要功能:**
|
||||||
|
- 密钥的存储和获取
|
||||||
|
- 支持按ID或key检索
|
||||||
|
- 密钥列表管理
|
||||||
|
|
||||||
|
## query-proxy - 代理路由模块
|
||||||
|
|
||||||
|
提供动态路由代理功能,可以将请求转发到不同的后端服务。
|
||||||
|
|
||||||
|
**主要功能:**
|
||||||
|
- 动态路由注册
|
||||||
|
- 请求转发代理
|
||||||
|
- 路由列表管理
|
||||||
|
- Token认证支持
|
||||||
|
|
||||||
|
**使用示例:**
|
||||||
|
```typescript
|
||||||
|
import { QueryProxy } from './query-proxy/index';
|
||||||
|
|
||||||
|
const proxy = new QueryProxy({
|
||||||
|
query: new Query(),
|
||||||
|
token: 'your-token'
|
||||||
|
});
|
||||||
|
await proxy.init(); // 初始化路由
|
||||||
|
const result = await proxy.run({ path: 'api', key: 'getData' });
|
||||||
|
```
|
||||||
|
|
||||||
|
## query-upload - 文件上传模块
|
||||||
|
|
||||||
|
功能完整的文件上传解决方案,支持多种上传方式。
|
||||||
|
|
||||||
|
**主要功能:**
|
||||||
|
- 普通文件上传
|
||||||
|
- 分片上传(大文件)
|
||||||
|
- 上传进度跟踪
|
||||||
|
- 文件过滤工具
|
||||||
|
- 文件格式转换
|
||||||
|
|
||||||
|
**核心组件:**
|
||||||
|
- `uploadFiles` - 基础文件上传
|
||||||
|
- `uploadFileChunked` - 分片上传
|
||||||
|
- `UploadProgress` - 进度管理
|
||||||
|
- `filterFiles` - 文件过滤
|
||||||
|
- `toFile` - 格式转换
|
||||||
|
|
||||||
|
**使用示例:**
|
||||||
|
```typescript
|
||||||
|
import { uploadFiles, UploadProgress } from './query-upload/query-upload';
|
||||||
|
|
||||||
|
const progress = new UploadProgress();
|
||||||
|
await uploadFiles(files, {
|
||||||
|
onProgress: (loaded, total) => {
|
||||||
|
console.log(`上传进度: ${loaded}/${total}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## query-app - 应用管理模块
|
||||||
|
|
||||||
|
管理应用的注册、获取和列表功能。
|
||||||
|
|
||||||
|
**主要功能:**
|
||||||
|
- 获取应用列表
|
||||||
|
- 获取公开应用
|
||||||
|
- 获取私有应用
|
||||||
|
- 应用定义管理
|
||||||
|
|
||||||
|
## query-shop - 应用商店模块
|
||||||
|
|
||||||
|
应用商店功能,处理应用的安装和获取。
|
||||||
|
|
||||||
|
**主要功能:**
|
||||||
|
- 应用安装
|
||||||
|
- 商店应用管理
|
||||||
|
|
||||||
|
## query-ai - AI对话模块
|
||||||
|
|
||||||
|
集成AI聊天功能,支持多种模型和对话管理。
|
||||||
|
|
||||||
|
**主要功能:**
|
||||||
|
- AI对话(支持GPT等模型)
|
||||||
|
- 模型列表获取
|
||||||
|
- 聊天使用统计
|
||||||
|
- 缓存管理
|
||||||
|
- 使用限制管理
|
||||||
|
|
||||||
|
**使用示例:**
|
||||||
|
```typescript
|
||||||
|
import { QueryAI } from './query-ai/query-ai';
|
||||||
|
|
||||||
|
const ai = new QueryAI({ query: new Query() });
|
||||||
|
const models = await ai.getModelList();
|
||||||
|
const response = await ai.chat(
|
||||||
|
{ message: '你好' },
|
||||||
|
{ username: 'user', model: 'gpt-3.5', group: 'default' }
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## query-login - 登录认证模块
|
||||||
|
|
||||||
|
完整的登录认证解决方案,支持token管理和缓存。
|
||||||
|
|
||||||
|
**主要功能:**
|
||||||
|
- 用户登录认证
|
||||||
|
- Token管理(access/refresh token)
|
||||||
|
- 登录状态缓存
|
||||||
|
- 浏览器/Node.js环境支持
|
||||||
|
- 自动token刷新
|
||||||
|
|
||||||
|
**核心组件:**
|
||||||
|
- `QueryLogin` - 主登录类
|
||||||
|
- `LoginCacheStore` - 登录缓存
|
||||||
|
- `Cache` - 通用缓存接口
|
||||||
|
|
||||||
|
## query-resources - 资源管理模块
|
||||||
|
|
||||||
|
管理用户资源的访问和获取。
|
||||||
|
|
||||||
|
**主要功能:**
|
||||||
|
- 资源文件获取
|
||||||
|
- 资源列表管理
|
||||||
|
- 用户认证支持
|
||||||
|
- 文件预览功能
|
||||||
|
|
||||||
|
**使用示例:**
|
||||||
|
```typescript
|
||||||
|
import { QueryResources } from './query-resources/index';
|
||||||
|
|
||||||
|
const resources = new QueryResources({ username: 'user' });
|
||||||
|
const fileList = await resources.getList('images/');
|
||||||
|
const fileContent = await resources.fetchFile('document.pdf');
|
||||||
|
```
|
||||||
|
|
||||||
|
## 架构特点
|
||||||
|
|
||||||
|
- **模块化设计**:每个模块职责单一,可独立使用
|
||||||
|
- **统一接口**:基于 `@kevisual/query` 的统一查询接口
|
||||||
|
- **环境兼容**:支持浏览器和Node.js环境
|
||||||
|
- **类型安全**:完整的TypeScript类型定义
|
||||||
|
- **扩展性**:易于扩展和自定义
|
||||||
|
|||||||
28
test/proxy.ts
Normal file
28
test/proxy.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { QueryProxy } from "../query/query-proxy";
|
||||||
|
import { Query } from "@kevisual/query/query";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
import util from 'node:util'
|
||||||
|
dotenv.config();
|
||||||
|
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
const token = process.env.KEVISUAL_TOKEN;
|
||||||
|
const url = process.env.KEVISUAL_URL;
|
||||||
|
export const showMore = (obj: any) => {
|
||||||
|
return util.inspect(obj, { showHidden: false, depth: null, colors: true });
|
||||||
|
}
|
||||||
|
const query = new Query({ url: url + '/api/router' });
|
||||||
|
const proxy = new QueryProxy({ query, token });
|
||||||
|
const res = await proxy.init();
|
||||||
|
|
||||||
|
console.log('Proxy Init Result:', showMore(res));
|
||||||
|
|
||||||
|
const routes = await proxy.listRoutes((item) => item.path?.startsWith('router') || false);
|
||||||
|
console.log('Filtered Routes:', showMore(routes));
|
||||||
|
|
||||||
|
await sleep(1000);
|
||||||
|
|
||||||
|
// console.log('Running route [user/me]...', proxy.router.routes.length);
|
||||||
|
|
||||||
|
const run = await proxy.run({ path: 'user', key: 'me' });
|
||||||
|
// const run = await proxy.run({ path: 'router', key: 'list' });
|
||||||
|
console.log('Run Result:', showMore(run));
|
||||||
22
turbo.json
22
turbo.json
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://turbo.build/schema.json",
|
|
||||||
"tasks": {
|
|
||||||
"build": {
|
|
||||||
"dependsOn": [
|
|
||||||
"^build"
|
|
||||||
],
|
|
||||||
"outputs": [
|
|
||||||
"dist/**"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"dev:lib": {
|
|
||||||
"persistent": true,
|
|
||||||
"cache": true
|
|
||||||
},
|
|
||||||
"build:lib": {
|
|
||||||
"dependsOn": [
|
|
||||||
"^build:lib"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user