diff --git a/agents/noco/common/base-table.ts b/agents/noco/common/base-table.ts index 7006721..c5ea3db 100644 --- a/agents/noco/common/base-table.ts +++ b/agents/noco/common/base-table.ts @@ -1,8 +1,7 @@ import { CreateColumnData } from "@kevisual/noco" export const columns: CreateColumnData[] = [ { - title: 'Id2', - // uidt: "ID", + title: 'Id', uidt: "ID", pk: true, pv: true, diff --git a/agents/noco/common/core.ts b/agents/noco/common/core.ts index 5473178..c5dad5a 100644 --- a/agents/noco/common/core.ts +++ b/agents/noco/common/core.ts @@ -68,6 +68,9 @@ export class Core { tableId = res?.data?.id; } this.tableId = tableId; + if(this.nocoApi.record) { + this.nocoApi.record.table = tableId; + } return { code, data: { @@ -88,4 +91,7 @@ export class Core { updateItem(data: Partial) { return this.nocoApi.record.update(data); } + creatItem(data: Partial) { + return this.nocoApi.record.create(data); + } } \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index 3d2759f..bb2f375 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,12 +1,11 @@ { - "name": "@kevisual/router-template-server", + "name": "@kevisual/noco-auto-backend", "version": "0.0.1", "description": "", "main": "index.js", - "basename": "/root/router-template-server", + "basename": "/root/noco-auto-backend", "app": { "type": "system-app", - "key": "router-template-server", "entry": "app.js", "runtime": [ "server" @@ -15,10 +14,8 @@ "scripts": { "dev": "bun --watch src/main.ts ", "build": "pnpm run clean && bun run bun.config.mjs", - "postbuild": "ev pack", - "compile": "bun build --compile ./src/main.ts --outfile router-template", - "compile:win": "bun build --compile ./src/main.ts --target=bun-windows-x64 --outfile router-template.exe", "clean": "rm -rf dist && rimraf pack-dist", + "prepub": "pnpm build", "pub": "envision pack -p -u" }, "files": [ @@ -42,7 +39,7 @@ "nanoid": "^5.1.6" }, "devDependencies": { - "@kevisual/ai": "^0.0.12", + "@kevisual/ai": "^0.0.15", "@kevisual/types": "^0.0.10", "@types/archiver": "^7.0.0", "@types/bun": "^1.3.3", diff --git a/backend/src/routes/noco/config.ts b/backend/src/routes/noco/config.ts index ccb863a..354925c 100644 --- a/backend/src/routes/noco/config.ts +++ b/backend/src/routes/noco/config.ts @@ -1,9 +1,8 @@ import { app } from '@/app.ts' import { NocoLifeService } from './services/life.ts'; -import { useContextKey } from '@kevisual/context'; -import { BailianProvider } from '@kevisual/ai'; -import { createLunarDate, LunarDate, toGregorian } from 'lunar'; import dayjs from 'dayjs'; +import { useContextKey } from '@kevisual/context'; +import { BaseChat, AIUtils } from '@kevisual/ai'; app.route({ path: 'noco-life', key: 'config-update', @@ -11,12 +10,25 @@ app.route({ `, middleware: ['auth'] }).define(async (ctx) => { - const data = ctx.query.data || {}; + const token = ctx.query.token || ''; + const question = ctx.query.question || ''; + let data = ctx.query.data || {}; + if (question) { + const ai: BaseChat = useContextKey('ai'); + await ai.chat([ + { role: 'system', content: `你是一个多维表格配置助理,你的任务是帮助用户解析多维表格的配置信息。用户会提供配置信息,你需要从中提取出 baseURL, token, baseId, tableId 等字段,并以 JSON 格式返回。如果某个字段缺失,可以不返回该字段。请确保返回的 JSON 格式正确且易于解析。` }, + { role: 'user', content: question } + ]) + let msg = AIUtils.extractJsonFromMarkdown(ai.responseText || ''); + if (msg == null) { + ctx.throw(500, 'AI 返回结果解析失败'); + } + data = msg; + } if (!data?.baseURL || !data?.token || !data?.baseId) { ctx.throw(400, '缺少参数 baseURL, token, baseId, tableId'); } - const token = ctx.query.token || ''; const nocoLifeService = new NocoLifeService({ token }); const config = await nocoLifeService.getLifeConfig() if (data.baseURL) { @@ -36,7 +48,7 @@ app.route({ if (res.code !== 200) { ctx.throw(500, '保存配置失败'); } - ctx.body = '配置更新成功'; + ctx.body = { content: '配置更新成功' }; }).addTo(app); @@ -50,5 +62,12 @@ app.route({ const token = ctx.query.token || ''; const nocoLifeService = new NocoLifeService({ token }); const config = await nocoLifeService.getLifeConfig() - ctx.body = config; + ctx.body = { + data: config, + content: `当前多维表格配置如下: +Base URL: ${config.baseURL} +Token: ${config.token} +Base ID: ${config.baseId} +Table ID: ${config.tableId || '未设置'}` + }; }).addTo(app); \ No newline at end of file diff --git a/backend/src/routes/noco/index.ts b/backend/src/routes/noco/index.ts index 283d119..6ba95b6 100644 --- a/backend/src/routes/noco/index.ts +++ b/backend/src/routes/noco/index.ts @@ -1,8 +1,9 @@ import { app } from '@/app.ts' import { NocoLifeService } from './services/life.ts'; import { useContextKey } from '@kevisual/context'; -import { BailianProvider } from '@kevisual/ai'; -import { createLunarDate, LunarDate, toGregorian } from 'lunar'; +import { BaseChat } from '@kevisual/ai'; +import { AIUtils } from '@kevisual/ai'; +import { createLunarDate, toGregorian } from 'lunar'; import dayjs from 'dayjs'; app.route({ path: 'noco-life', @@ -15,27 +16,43 @@ app.route({ ctx.throw(400, '缺少参数 question'); } const token = ctx.query.token || ''; + + if (question.startsWith('配置多维表格')) { + const res = await ctx.call({ + path: 'noco-life', + key: 'config-update', + token: token, + payload: { question } + }) + ctx.body = res.body; + return; + } const nocoLifeService = new NocoLifeService({ token }); await nocoLifeService.initConfig() const routes = ctx.queryRouter.getList().filter(r => r.path.startsWith('noco-life') && r.key !== 'chat'); const v = `${routes.map((r, index) => `${index + 1}工具名称: ${r.id}\n描述: ${r.description}\n`).join('\n')}\n\n当用户询问时,如果拥有工具,请返回 JSON 数据,不存在工具,则返回分析判断,数据JSON数据类型是{id,payload},外面的id是工具的id。如果工具有参数,在 payload 当中,默认不需要参数,如果工具内部需要id,在payload当中。` - const ai = useContextKey('ai'); + const ai: BaseChat = useContextKey('ai'); const answer = await ai.chat([ { role: 'system', content: `你是一个多维表格助理,你的任务是帮助用户操作和查询多维表格的数据。你可以使用以下工具来完成任务:\n\n${v}` }, { role: 'user', content: question } ]) - console.log('answer', ai.responseText); - let msg: any; - try { - msg = JSON.parse(ai.responseText || '{}'); - } catch (e) { + let msg = AIUtils.extractJsonFromMarkdown(ai.responseText || ''); + if (msg == null) { ctx.throw(500, 'AI 返回结果解析失败'); } console.log('msg', msg); - const res = await ctx.call(msg); + const route = routes.find(r => r.id === msg.id || r.key === msg.id); + console.log('route============', route.id, route.path, route.key); + const res = await ctx.call({ + ...msg, + token: token + }); if (res.code !== 200) { - ctx.throw(500, '调用工具失败'); + console.log('调用工具失败', res.message); + ctx.throw(500, res.message || '调用工具失败'); } + console.log('con=============', res?.data); + console.log('res', res.code, res.body?.content); ctx.body = res.body; }).addTo(app); @@ -50,16 +67,30 @@ app.route({ const nocoLifeService = new NocoLifeService({ token }); await nocoLifeService.initConfig() const life = nocoLifeService.life; + + const tomorrow = dayjs().add(1, 'day').startOf('day').toISOString(); + const tomorrowDate = dayjs(tomorrow).format('YYYY-MM-DD'); const res = await life.getList({ fields: ['Id', '标题', '总结', '启动时间', '标签', '任务'], - where: "(任务,eq,运行中)~and(启动时间,lt,today)", + where: `(任务,eq,运行中)~and(启动时间,lt,exactDate,${tomorrowDate})`, + // where: "(任务,eq,运行中)~and(启动时间,le,today)", + // where: "(任务,eq,运行中)~and(启动时间,le,daysAgo,-1)", sort: '启动时间', }); + console.log('today res', res.data?.list?.map(i => i['标题'])); if (res.code === 200) { const list = res.data.list || [] - ctx.body = list; + ctx.body = { + list, + content: list.map(item => { + return `任务: ${item['标题']}[${item['Id']}], 启动时间: ${dayjs(item['启动时间']).format('YYYY-MM-DD HH:mm:ss')}, 标签: ${item['标签'] || '无'} \n总结: ${item['总结'] || '无'}`; + }).join('\n') + }; if (list.length === 0) { - ctx.message = '今天没有需要做的事情,休息一下吧'; + ctx.body = { + list, + content: '今天没有需要做的事情了,休息一下吧' + } } return; } @@ -76,6 +107,7 @@ app.route({ if (!id) { ctx.throw(400, '缺少参数 id'); } + console.log('id', id); const token = ctx.query.token || ''; const nocoLifeService = new NocoLifeService({ token }); await nocoLifeService.initConfig() @@ -148,7 +180,8 @@ ${prompt ? `这是我给你的提示词,帮你更好地理解我的需求:${ ctx.body = { id, nextTime, - showCNTime: dayjs(nextTime).format('YYYY-MM-DD HH:mm:ss') + showCNTime: dayjs(nextTime).format('YYYY-MM-DD HH:mm:ss'), + content: `任务 "${record['标题']}" 已标记为完成。下一次运行时间是 ${dayjs(nextTime).format('YYYY-MM-DD HH:mm:ss')}` }; }).addTo(app); diff --git a/backend/src/routes/noco/services/life.ts b/backend/src/routes/noco/services/life.ts index 2a5bd2b..2d5d15f 100644 --- a/backend/src/routes/noco/services/life.ts +++ b/backend/src/routes/noco/services/life.ts @@ -25,7 +25,7 @@ export class NocoLifeService { } initEnv() { const config = new QueryConfig({ - query: new Query({ url: "https://kevisual.xiongxiao.me/api/router" }) + query: new Query({ url: "https://kevisual.cn/api/router" }) }); this.queryConfig = config; } diff --git a/backend/test/extract.ts b/backend/test/extract.ts new file mode 100644 index 0000000..7f815f2 --- /dev/null +++ b/backend/test/extract.ts @@ -0,0 +1,11 @@ +import { ai } from './common.ts'; +import { AIUtils } from '@kevisual/ai'; + +const text = `\`\`\`\`json +{ + "id": "7y6FHnPSzwhDJGyizsTQU", + "payload": {} +} +\`\`\`` + +const json = AIUtils.extractJsonFromMarkdown(text); \ No newline at end of file diff --git a/package.json b/package.json index 42c7615..ea4e307 100644 --- a/package.json +++ b/package.json @@ -29,5 +29,8 @@ }, "exports": { ".": "./mod.ts" + }, + "devDependencies": { + "@types/node": "^24.10.1" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e914748..77226b8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,10 @@ importers: '@kevisual/use-config': specifier: ^1.0.21 version: 1.0.21(dotenv@17.2.3) + devDependencies: + '@types/node': + specifier: ^24.10.1 + version: 24.10.1 backend: dependencies: @@ -58,8 +62,8 @@ importers: version: 5.1.6 devDependencies: '@kevisual/ai': - specifier: ^0.0.12 - version: 0.0.12 + specifier: ^0.0.15 + version: 0.0.15 '@kevisual/types': specifier: ^0.0.10 version: 0.0.10 @@ -660,8 +664,8 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@kevisual/ai@0.0.12': - resolution: {integrity: sha512-c02ozy4B+1utJsjQee4nnQZ2vDKTMDYJxbya5CIpghm+ujs7jSiB05cyIREELAHErSOzJUQXlHC3Dr8rdb5F0A==} + '@kevisual/ai@0.0.15': + resolution: {integrity: sha512-7oX/wHUKJCfvphFJq7fLBGpl4f6ASEJooQVvmgHZ7fZiYBEeVAEYAB28BNqk36iOItEyWlhuOCxq1oQz3wN+XQ==} '@kevisual/cache@0.0.3': resolution: {integrity: sha512-BWEck69KYL96/ywjYVkML974RHjDJTj2ITQND1zFPR+hlBV1H1p55QZgSYRJCObg3EAV1S9Zic/fR2T4pfe8yg==} @@ -681,6 +685,9 @@ packages: '@kevisual/noco@0.0.8': resolution: {integrity: sha512-N8v2b9R1NAtdmcP+LB+bHJEi/KQlbzPzva7RLEP3TfFStKADIXHdZdIHsatH3BQRBgRd0cuhS+rcq7CF66QFJA==} + '@kevisual/permission@0.0.3': + resolution: {integrity: sha512-8JsA/5O5Ax/z+M+MYpFYdlioHE6jNmWMuFSokBWYs9CCAHNiSKMR01YLkoVDoPvncfH/Y8F5K/IEXRCbptuMNA==} + '@kevisual/query-login@0.0.7': resolution: {integrity: sha512-oOyPIz337cdTt7WncFj7Wr7nxUHh0pBB6KSAJlas+lQiWBPwQEZhpEd7YciydCRlMc9IJMcZRV1Bw3qgy8FFqQ==} peerDependencies: @@ -3685,9 +3692,10 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@kevisual/ai@0.0.12': + '@kevisual/ai@0.0.15': dependencies: '@kevisual/logger': 0.0.4 + '@kevisual/permission': 0.0.3 '@kevisual/cache@0.0.3': dependencies: @@ -3705,6 +3713,8 @@ snapshots: '@kevisual/noco@0.0.8': {} + '@kevisual/permission@0.0.3': {} + '@kevisual/query-login@0.0.7(@kevisual/query@0.0.29(zod@3.25.76))': dependencies: '@kevisual/cache': 0.0.3 diff --git a/scripts/create.ts b/scripts/create.ts index 0c329c6..de93976 100644 --- a/scripts/create.ts +++ b/scripts/create.ts @@ -1,12 +1,11 @@ +// import { Life, NocoApi } from '@kevisual/noco/mod.ts'; import { Life, NocoApi } from '../mod.ts'; import { generateMultipleRandomItems } from './random-data'; // const token = 'your' // const baseId = 'your_base_id'; - -const token = 'CPrDfoajtCjayEYK17wOljE7R2E7DoTIw_7oUnjb' -const baseId = 'p1pderowf0zhugt' -let tableId = 'mierezd5z73kj26' +const token = 'bMLb3rzj9Qz_nOuQ0dj9PRYQbPK_79C2Yfbq5Xae' +const baseId = 'p7k66s6p7lss31j' const nocoApi = new NocoApi({ baseURL: 'http://localhost:8080', token @@ -14,18 +13,12 @@ const nocoApi = new NocoApi({ const life = new Life({ nocoApi, baseId }); -// const tableRes = await life.createTable({ -// title: '测试表格2', -// }); -// tableId = tableRes.data?.id || ''; -// console.log('tableId', tableId); - -life.nocoApi.record.table = tableId; -const datas = generateMultipleRandomItems(1); +const tableRes = await life.createTable(); +console.log('tableRes', tableRes); +const datas = generateMultipleRandomItems(100); for (const data of datas) { - console.log('data', data); - const createRes = await life.nocoApi.record.create(data); - console.log('createRes', createRes); + const createRes = await life.creatItem(data); + console.log(createRes); } diff --git a/scripts/get-list.ts b/scripts/get-list.ts new file mode 100644 index 0000000..518c017 --- /dev/null +++ b/scripts/get-list.ts @@ -0,0 +1,21 @@ +import { Life, NocoApi } from '../mod.ts'; +import { generateMultipleRandomItems } from './random-data'; +import util from 'node:util'; + +// const token = 'your' +// const baseId = 'your_base_id'; + +const token = 'Qd7Xvz6Ui3SlzjdaqONZxHd6Gw_STk4sa4HqDKC5' +const baseId = 'paqa769bfeh467p' +let tableId = 'mz5hmnw8o6qfccf' +const nocoApi = new NocoApi({ + baseURL: 'http://localhost:8080', + token, +}); + +const life = new Life({ nocoApi, baseId, tableId }); + +nocoApi.record.table = tableId; +const res = await life.nocoApi.record.list() + +console.log('uitl', util.inspect(res, { depth: 4 })); \ No newline at end of file diff --git a/scripts/random-data.ts b/scripts/random-data.ts index 7485619..5bb0b54 100644 --- a/scripts/random-data.ts +++ b/scripts/random-data.ts @@ -3,8 +3,52 @@ type Item = { '标题': string; '描述': string; '总结': string; - '标签': string[]; + '类型': string; + '启动时间'?: string; } +const tasks = [{ + title: '非任务', +}, +{ + title: '运行中', +}, +{ + title: '已停止', +}, +{ + title: '个人计划', +}, +{ + title: '已完成', +}, +{ + title: 'AI自动化' +} +]; +const types = [{ + title: '每日', +}, +{ + title: '每周', +}, +{ + title: '每月', +}, +{ + title: '每年', +}, +{ + title: '每年农历', +}, +{ + title: '备忘', +}, +{ + title: '归档', +}, +{ + title: '智能', +},] export function generateRandomItem(): Item { const titles = [ '探索未知的宇宙', @@ -29,23 +73,25 @@ export function generateRandomItem(): Item { '回顾了历史伟人对社会的贡献和影响。', '总结了科技进步对生活方式的改变。' ]; - - const tagsList = [ - ['宇宙', '探索', '科学'], - ['人工智能', '技术', '未来'], - ['可持续发展', '环境', '社会'], - ['历史', '人物', '影响'], - ['科技', '生活', '进步'] - ]; - const randomIndex = Math.floor(Math.random() * titles.length); + // 生成本月的随机时间 + const now = new Date(); + const year = now.getFullYear(); + const month = now.getMonth(); + const daysInMonth = new Date(year, month + 1, 0).getDate(); + const randomDay = Math.floor(Math.random() * daysInMonth) + 1; + const randomHour = Math.floor(Math.random() * 24); + const randomMinute = Math.floor(Math.random() * 60); + const randomSecond = Math.floor(Math.random() * 60); + const randomDate = new Date(year, month, randomDay, randomHour, randomMinute, randomSecond); + return { '标题': titles[randomIndex], '描述': descriptions[randomIndex], '总结': summaries[randomIndex], - // '标签': tagsList[randomIndex], - '标签': ['b'], + '类型': types[Math.floor(Math.random() * types.length)].title, + '启动时间': randomDate.toISOString(), }; } @@ -53,7 +99,6 @@ export function generateMultipleRandomItems(count: number): Item[] { const items: Item[] = []; for (let i = 0; i < count; i++) { const item = generateRandomItem(); - item.Id = i + 1; items.push(item); } return items;