init
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
|
||||
.env
|
||||
|
||||
pnpm-lock.yaml
|
||||
28
package.json
Normal file
28
package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "@kevisual/app",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "bun"
|
||||
},
|
||||
"files": [
|
||||
"src"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@10.14.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@kevisual/ai": "^0.0.19",
|
||||
"@kevisual/context": "^0.0.4",
|
||||
"@kevisual/query": "^0.0.31",
|
||||
"@kevisual/router": "^0.0.36",
|
||||
"@kevisual/use-config": "^1.0.21",
|
||||
"mitt": "^3.0.1"
|
||||
}
|
||||
}
|
||||
161
src/app.ts
Normal file
161
src/app.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { QueryRouterServer } from '@kevisual/router'
|
||||
import { use } from '@kevisual/context'
|
||||
import { Query } from '@kevisual/query'
|
||||
import { Kevisual, BailianChat } from '@kevisual/ai'
|
||||
import mitt from 'mitt';
|
||||
const isBrowser = typeof window !== 'undefined'
|
||||
type AppOptions = {
|
||||
router?: QueryRouterServer
|
||||
query?: Query
|
||||
queryOptions?: { url?: string }
|
||||
token?: string
|
||||
initAI?: boolean
|
||||
}
|
||||
export class App {
|
||||
#router: QueryRouterServer
|
||||
use = use;
|
||||
query: Query;
|
||||
#token = '';
|
||||
ai!: Kevisual;
|
||||
loading = false;
|
||||
emitter = mitt();
|
||||
constructor(opts?: AppOptions) {
|
||||
this.#router = opts?.router || new QueryRouterServer()
|
||||
const queryOptions = opts?.queryOptions || {}
|
||||
this.query = opts?.query || new Query({ url: queryOptions.url || 'https://kevisual.cn/api/router' })
|
||||
const initAI = opts?.initAI ?? true;
|
||||
if (opts?.token) {
|
||||
this.#token = opts.token
|
||||
if (initAI) {
|
||||
this.loading = true;
|
||||
this.initAI().finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
async initAI() {
|
||||
try {
|
||||
const config = await this.getConfig('ai.json');
|
||||
if (config.token) {
|
||||
this.ai = new Kevisual(config);
|
||||
}
|
||||
} catch (e) { }
|
||||
this.emitter.emit('ai-inited');
|
||||
}
|
||||
async loadAI() {
|
||||
if (!this.ai) {
|
||||
const that = this;
|
||||
return new Promise<Kevisual>(resolve => {
|
||||
const listen = () => {
|
||||
resolve(that.ai);
|
||||
this.emitter.off('ai-inited', listen);
|
||||
}
|
||||
this.emitter.on('ai-inited', listen);
|
||||
});
|
||||
}
|
||||
return this.ai;
|
||||
}
|
||||
get token() {
|
||||
if (isBrowser && !this.#token) {
|
||||
this.#token = localStorage.getItem('token') || ''
|
||||
return this.#token;
|
||||
}
|
||||
return this.#token;
|
||||
}
|
||||
set token(value: string) {
|
||||
this.#token = value;
|
||||
}
|
||||
get router() {
|
||||
return this.#router;
|
||||
}
|
||||
set router(value: QueryRouterServer) {
|
||||
this.#router = value;
|
||||
}
|
||||
async getConfig(key: string) {
|
||||
if (isBrowser) {
|
||||
const config = sessionStorage.getItem(`config_${key}`)
|
||||
if (config) {
|
||||
return Promise.resolve(JSON.parse(config))
|
||||
}
|
||||
}
|
||||
const res = await this.query.post({
|
||||
path: 'config',
|
||||
key: 'get',
|
||||
token: this.token,
|
||||
data: { key }
|
||||
})
|
||||
if (res.code !== 200) {
|
||||
throw new Error(res.message || '获取配置失败')
|
||||
}
|
||||
const data = res.data || {}
|
||||
const config = data.data || {}
|
||||
if (isBrowser) {
|
||||
sessionStorage.setItem(`config_${key}`, JSON.stringify(config))
|
||||
}
|
||||
return config;
|
||||
}
|
||||
async chat(message: string) {
|
||||
const routes = this.router.getList();
|
||||
let v = '';
|
||||
|
||||
if (routes.length > 0) {
|
||||
const toolsList = routes.map((r, index) =>
|
||||
`${index + 1}. 工具名称: ${r.id}\n 描述: ${r.description}`
|
||||
).join('\n\n');
|
||||
|
||||
v = `你是一个 AI 助手,你可以使用以下工具来帮助用户完成任务:
|
||||
|
||||
${toolsList}
|
||||
|
||||
## 回复规则
|
||||
1. 如果用户的请求可以使用上述工具完成,请返回 JSON 格式数据
|
||||
2. 如果没有合适的工具,请直接分析并回答用户问题
|
||||
|
||||
## JSON 数据格式
|
||||
\`\`\`json
|
||||
{
|
||||
"id": "工具的id",
|
||||
"payload": {
|
||||
// 工具所需的参数(如果需要)
|
||||
// 例如: "id": "xxx", "name": "xxx"
|
||||
}
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
注意:
|
||||
- payload 中包含工具执行所需的所有参数
|
||||
- 如果工具不需要参数,payload 可以为空对象 {}
|
||||
- 确保返回的 id 与上述工具列表中的工具名称完全匹配`
|
||||
}
|
||||
await this.ai.chat([
|
||||
{
|
||||
role: 'system',
|
||||
content: v
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
content: message
|
||||
}
|
||||
])
|
||||
const json = this.ai.utils.extractJsonFromMarkdown(this.ai.responseText);
|
||||
if (json.id) {
|
||||
const callRes = await this.router.call(json)
|
||||
const data = {
|
||||
code: callRes.code,
|
||||
data: callRes.body,
|
||||
message: callRes.message
|
||||
}
|
||||
return Promise.resolve(data);
|
||||
}
|
||||
return Promise.resolve({
|
||||
code: 200,
|
||||
data: {
|
||||
id: 'ai_response',
|
||||
description: 'AI 直接回复',
|
||||
response: this.ai.responseText
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
33
test/common.ts
Normal file
33
test/common.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { App } from '../src/app.ts';
|
||||
import { useConfig } from '@kevisual/use-config';
|
||||
|
||||
export const config = useConfig();
|
||||
export const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
const time = Date.now();
|
||||
const app = new App({ token: config.KEVISUAL_TOKEN || '' });
|
||||
|
||||
|
||||
console.log('time to init app', Date.now() - time);
|
||||
|
||||
await app.loadAI();
|
||||
console.log('app ai inited', Date.now() - time);
|
||||
const router = app.router;
|
||||
router.route({
|
||||
description: '获取天气,返回天气情况',
|
||||
}).define(async ctx => {
|
||||
ctx.body = '今天天气晴朗,气温25度,适合出行。';
|
||||
}).addTo(router)
|
||||
|
||||
router.route({
|
||||
description: '获取新闻,返回最新新闻',
|
||||
}).define(async (ctx) => {
|
||||
ctx.body = '今天的头条新闻是:科技公司发布了最新的智能手机。';
|
||||
}).addTo(router)
|
||||
|
||||
// await sleep(1000);
|
||||
|
||||
const run = await app.chat('今天的新闻');
|
||||
console.log('run', run);
|
||||
|
||||
console.log('all done', Date.now() - time);
|
||||
Reference in New Issue
Block a user