generated from template/apps-template
Compare commits
15 Commits
081add5316
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 719d7e76e1 | |||
| 9cd1de87dd | |||
| 32db5ec306 | |||
| 63e853641b | |||
| 22fac88d01 | |||
| df385f00ed | |||
| 0b0482a217 | |||
| ac0e980f34 | |||
| c02ecc9d85 | |||
| 7d8123a76d | |||
| 37d78c742b | |||
| 9a64ab25f4 | |||
| d49ecc83fa | |||
| 3d50fde0eb | |||
| 664350ebf6 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,3 +6,6 @@ node_modules
|
||||
dist
|
||||
|
||||
public/root
|
||||
|
||||
.env
|
||||
!.env*example
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -1,6 +0,0 @@
|
||||
[submodule "frontend"]
|
||||
path = frontend
|
||||
url = git@git.xiongxiao.me:template/astro-simple-template.git
|
||||
[submodule "backend"]
|
||||
path = backend
|
||||
url = git@git.xiongxiao.me:template/router-template.git
|
||||
2
.npmrc
Normal file
2
.npmrc
Normal file
@@ -0,0 +1,2 @@
|
||||
//npm.xiongxiao.me/:_authToken=${ME_NPM_TOKEN}
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
3
agents/app.ts
Normal file
3
agents/app.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { QueryRouterServer } from "@kevisual/router";
|
||||
import { useContextKey } from "@kevisual/context";
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
const frontend = 'git@git.xiongxiao.me:template/astro-simple-template.git';
|
||||
const backend = 'git@git.xiongxiao.me:template/router-template.git';
|
||||
|
||||
|
||||
// submodule add frontend to frontend and backend to backend
|
||||
export const cliInitSubmodules = [
|
||||
`git submodule add ${frontend} frontend`,
|
||||
`git submodule add ${backend} backend`,
|
||||
];
|
||||
|
||||
cliInitSubmodules.forEach((cmd) => {
|
||||
console.log(`${cmd}\n`);
|
||||
});
|
||||
|
||||
// init submodules
|
||||
export const cliUpdateSubmodules = `git submodule update --init --recursive`;
|
||||
console.log(`${cliUpdateSubmodules}\n`);
|
||||
|
||||
// 清理submodule,保留模板的内容
|
||||
export const cliRemoveGitModule = `rm .gitmodules -rf && rm -rf .git/modules`;
|
||||
console.log(`${cliRemoveGitModule}\n`);
|
||||
5
agents/modules/query.ts
Normal file
5
agents/modules/query.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Query } from '@kevisual/query'
|
||||
|
||||
export const query = new Query({
|
||||
url: 'https://kevisual.cn/api/router',
|
||||
})
|
||||
21
agents/noco/callback/index.ts
Normal file
21
agents/noco/callback/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
export type BaseNocoItem<T = {}> = {
|
||||
Id: number,
|
||||
CreatedAt: string
|
||||
UpdatedAt: string
|
||||
} & T;
|
||||
export type NocoWehookPayload<NocoItem = {}> = {
|
||||
/** 请求id */
|
||||
id: string;
|
||||
type: "records.after.trigger";
|
||||
/**
|
||||
* 多维表base id
|
||||
**/
|
||||
base_id: string;
|
||||
version: "v3",
|
||||
data: {
|
||||
table_id: string;
|
||||
table_name: string;
|
||||
rows: BaseNocoItem<NocoItem>[];
|
||||
}
|
||||
}
|
||||
137
agents/noco/common/base-table.ts
Normal file
137
agents/noco/common/base-table.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { CreateColumnData } from "@kevisual/noco"
|
||||
export const columns: CreateColumnData[] = [
|
||||
{
|
||||
title: 'Id',
|
||||
// @ts-ignore
|
||||
uidt: "ID",
|
||||
pk: true,
|
||||
pv: true,
|
||||
},
|
||||
{
|
||||
|
||||
title: '标题',
|
||||
uidt: 'SingleLineText',
|
||||
description: '简单的标题, 最简单的介绍',
|
||||
},
|
||||
{
|
||||
title: '标签',
|
||||
uidt: 'MultiSelect',
|
||||
description: '标签分类,对每一条数据的标签定义,快速分类和筛选',
|
||||
},
|
||||
{
|
||||
title: '总结',
|
||||
uidt: 'LongText',
|
||||
description: '概览性总结',
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
uidt: 'LongText',
|
||||
description: '长文本描述',
|
||||
},
|
||||
{
|
||||
title: '数据',
|
||||
uidt: 'JSON',
|
||||
description: '扩列数据,存储更多的自定义信息',
|
||||
},
|
||||
{
|
||||
title: '链接',
|
||||
uidt: 'URL',
|
||||
description: '快速跳转链接,默认为空,比如我这里是一个人生日程的链接,在外部打开',
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
uidt: 'SingleSelect',
|
||||
description: '任务类型:备忘和其他,如果是备忘,只做记录,如果是其他的,属于任务管理,到达对应的时间,进行任务提醒,归档是自己不再查询。',
|
||||
cdf: '备忘',
|
||||
colOptions: {
|
||||
// 每日,每周,每月,每年,一次性,备忘,归档,智能
|
||||
options: [
|
||||
{
|
||||
title: '每日',
|
||||
},
|
||||
{
|
||||
title: '每周',
|
||||
},
|
||||
{
|
||||
title: '每月',
|
||||
},
|
||||
{
|
||||
title: '每年',
|
||||
},
|
||||
{
|
||||
title: '每年农历',
|
||||
},
|
||||
{
|
||||
title: '备忘',
|
||||
},
|
||||
{
|
||||
title: '归档',
|
||||
},
|
||||
{
|
||||
title: '智能',
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '启动时间',
|
||||
description: '任务启动的时间点, 下次启动的时间点。到达当天,显示当天的任务,然后如果执行了,如果是循环周期任务,更新下次启动时间。',
|
||||
uidt: 'DateTime',
|
||||
},
|
||||
{
|
||||
title: '任务',
|
||||
uidt: 'MultiSelect',
|
||||
description: '任务状态,如果是任务,需要判断运行还是非运行中',
|
||||
cdf: '非任务',
|
||||
colOptions: {
|
||||
// 非任务, 运行中,已停止,个人计划,已完成
|
||||
options: [
|
||||
{
|
||||
title: '非任务',
|
||||
},
|
||||
{
|
||||
title: '运行中',
|
||||
},
|
||||
{
|
||||
title: '已停止',
|
||||
},
|
||||
{
|
||||
title: '个人计划',
|
||||
},
|
||||
{
|
||||
title: '已完成',
|
||||
},
|
||||
{
|
||||
title: 'AI自动化'
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '任务结果',
|
||||
description: '任务结果描述, 执行后回馈',
|
||||
uidt: 'LongText'
|
||||
},
|
||||
{
|
||||
title: '提示词',
|
||||
uidt: 'LongText',
|
||||
description: '和AI交互时候简单的实时提示词',
|
||||
}]
|
||||
|
||||
|
||||
export type ColumnItem<T = {}> = {
|
||||
'Id': number,
|
||||
'CreatedAt': string,
|
||||
'UpdatedAt': string,
|
||||
"标题": string,
|
||||
"标签"?: string,
|
||||
"总结"?: string,
|
||||
"描述"?: string,
|
||||
"数据"?: string,
|
||||
"链接"?: string,
|
||||
"类型"?: string,
|
||||
"启动时间"?: string,
|
||||
"任务"?: "非任务" | "运行中" | "已停止" | "个人计划" | "已完成" | "AI自动化",
|
||||
"任务结果"?: string,
|
||||
"提示词"?: string,
|
||||
} & T;
|
||||
99
agents/noco/common/core.ts
Normal file
99
agents/noco/common/core.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { NocoApi } from "@kevisual/noco";
|
||||
import { columns } from "../common/base-table.ts";
|
||||
import { ColumnItem } from "./base-table.ts";
|
||||
type ReponseData<T = {}> = {
|
||||
code: number,
|
||||
message?: string,
|
||||
data?: T
|
||||
}
|
||||
export type CoreOptions<T = {}> = {
|
||||
nocoApi: NocoApi,
|
||||
baseId?: string
|
||||
} & T
|
||||
|
||||
export class Core {
|
||||
nocoApi: NocoApi;
|
||||
#baseId?: string;
|
||||
key = 'core';
|
||||
title = '默认表';
|
||||
description = '默认表描述';
|
||||
#tableId?: string;
|
||||
constructor(opts: {
|
||||
nocoApi: NocoApi,
|
||||
baseId?: string,
|
||||
tableId?: string
|
||||
}) {
|
||||
this.nocoApi = opts.nocoApi;
|
||||
this.baseId = opts.baseId;
|
||||
this.tableId = opts.tableId;
|
||||
}
|
||||
get tableId() {
|
||||
return this.#tableId;
|
||||
}
|
||||
set tableId(id: string | undefined) {
|
||||
this.#tableId = id;
|
||||
if (this.nocoApi.record && id) {
|
||||
this.nocoApi.record.table = id;
|
||||
}
|
||||
}
|
||||
get baseId() {
|
||||
return this.#baseId;
|
||||
}
|
||||
set baseId(id: string | undefined) {
|
||||
this.#baseId = id;
|
||||
}
|
||||
async createTable(opts?: { columns?: any[], title?: string, description?: string, baseId?: string }): Promise<ReponseData<{ id: string, title: string }>> {
|
||||
const baseId = opts?.baseId ?? this.baseId!;
|
||||
const title = opts?.title ?? this.title;
|
||||
const description = opts?.description ?? this.description;
|
||||
const _columns = opts?.columns ?? columns;
|
||||
let tableId = '';
|
||||
const res = await this.nocoApi.meta.tables.createTable(baseId, {
|
||||
title,
|
||||
description,
|
||||
columns: _columns,
|
||||
})
|
||||
let code = 200;
|
||||
if (res.code !== 200) {
|
||||
const res = await this.nocoApi.meta.tables.list(baseId);
|
||||
const list = res.data?.list || [];
|
||||
const existTable = list.find(t => t.title === title);
|
||||
if (existTable) {
|
||||
tableId = existTable.id;
|
||||
} else {
|
||||
return {
|
||||
code: res.code,
|
||||
message: `创建表失败,且未找到同名表`,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tableId = res?.data?.id;
|
||||
}
|
||||
this.tableId = tableId;
|
||||
if (this.nocoApi.record) {
|
||||
this.nocoApi.record.table = tableId;
|
||||
}
|
||||
return {
|
||||
code,
|
||||
data: {
|
||||
id: tableId,
|
||||
title,
|
||||
}
|
||||
};
|
||||
}
|
||||
getItem(id: number): Promise<ReponseData<ColumnItem>> {
|
||||
return this.nocoApi.record.read(id);
|
||||
}
|
||||
|
||||
getList(params: any): Promise<ReponseData<{ list: ColumnItem[] }>> {
|
||||
return this.nocoApi.record.list({
|
||||
...params,
|
||||
});
|
||||
}
|
||||
updateItem(data: Partial<ColumnItem>) {
|
||||
return this.nocoApi.record.update(data);
|
||||
}
|
||||
createItem(data: Partial<ColumnItem>) {
|
||||
return this.nocoApi.record.create(data);
|
||||
}
|
||||
}
|
||||
2
agents/noco/common/index.ts
Normal file
2
agents/noco/common/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./core.ts";
|
||||
export * from "./base-table.ts";
|
||||
7
agents/noco/control/index.ts
Normal file
7
agents/noco/control/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Core } from "../common/index.ts";
|
||||
|
||||
export class Control extends Core {
|
||||
key = 'control';
|
||||
title = '控制中枢'
|
||||
description = '管理和控制系统的运行'
|
||||
}
|
||||
19
agents/noco/index.ts
Normal file
19
agents/noco/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { NocoApi } from "@kevisual/noco";
|
||||
import { ColumnItem, columns, } from "./common/base-table.ts";
|
||||
import { Life } from "../noco/life/index.ts";
|
||||
import { Control } from "../noco/control/index.ts";
|
||||
import { Core } from "./common/core.ts";
|
||||
|
||||
import { NocoWehookPayload } from "./callback/index.ts";
|
||||
export {
|
||||
NocoApi,
|
||||
columns,
|
||||
Control,
|
||||
Life,
|
||||
Core,
|
||||
}
|
||||
|
||||
export type {
|
||||
NocoWehookPayload,
|
||||
ColumnItem
|
||||
}
|
||||
7
agents/noco/life/index.ts
Normal file
7
agents/noco/life/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { Core } from "../common/index.ts";
|
||||
|
||||
export class Life extends Core {
|
||||
key = 'life';
|
||||
title = '人生备忘录'
|
||||
description = '记录和管理你的人生大事小事'
|
||||
}
|
||||
108
agents/query/query-config.ts
Normal file
108
agents/query/query-config.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
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;
|
||||
};
|
||||
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(id: string, opts?: PostOpts) {
|
||||
return this.post({
|
||||
key: 'delete',
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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,9 @@
|
||||
"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",
|
||||
"create": "bun run test/create-json.ts",
|
||||
"clean": "rm -rf dist && rimraf pack-dist",
|
||||
"prepub": "pnpm build",
|
||||
"pub": "envision pack -p -u"
|
||||
},
|
||||
"files": [
|
||||
@@ -27,23 +25,29 @@
|
||||
"keywords": [],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@10.22.0",
|
||||
"packageManager": "pnpm@10.25.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@kevisual/ai": "^0.0.11",
|
||||
"@kevisual/app": "^0.0.1",
|
||||
"@kevisual/context": "^0.0.4",
|
||||
"@kevisual/local-proxy": "^0.0.8",
|
||||
"@kevisual/query": "^0.0.29",
|
||||
"@kevisual/router": "0.0.33",
|
||||
"@kevisual/use-config": "^1.0.19",
|
||||
"@kevisual/noco-auto": "../",
|
||||
"@kevisual/query": "^0.0.32",
|
||||
"@kevisual/router": "0.0.37",
|
||||
"@kevisual/use-config": "^1.0.21",
|
||||
"archiver": "^7.0.1",
|
||||
"dayjs": "^1.11.19",
|
||||
"es-toolkit": "^1.42.0",
|
||||
"lunar": "^2.0.0",
|
||||
"nanoid": "^5.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kevisual/ai": "^0.0.19",
|
||||
"@kevisual/types": "^0.0.10",
|
||||
"@types/archiver": "^7.0.0",
|
||||
"@types/bun": "^1.3.3",
|
||||
"@types/node": "^24.10.1"
|
||||
"@types/bun": "^1.3.4",
|
||||
"@types/node": "^25.0.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"fast-glob": "^3.3.3"
|
||||
}
|
||||
}
|
||||
18
backend/prompts/markdown-files.json
Normal file
18
backend/prompts/markdown-files.json
Normal file
@@ -0,0 +1,18 @@
|
||||
[
|
||||
{
|
||||
"title": "应用脚本",
|
||||
"content": "应用脚本"
|
||||
},
|
||||
{
|
||||
"title": "智能家居HA",
|
||||
"content": "请根据用户输入生成符合规范的快捷键 JSON 数据,格式如下:\n\n```json\n{ \"type\": \"ha\", \"ha\": { \"entity_id\": \"string\", \"task\": \"\" } }\n```\n"
|
||||
},
|
||||
{
|
||||
"title": "快捷键",
|
||||
"content": "请根据用户输入生成符合规范的快捷键 JSON 数据,格式如下:\n```json\n{\"type\":\"hotkeys\",\"hotkeys\":\"ctrlOrCommand+h\"}\n```\n### 规则说明:\n\n1. **数据类型固定为**:`\"hotkeys\"`,字段名为 `hotkeys`,值为**标准快捷键字符串**,使用小写字母和 `+` 连接。\n1. **操作系统适配**:所有修饰符的 `Ctrl` 键必须替换为 `ctrlOrCommand`, 但是win默认为windows的按键,alt键保留\n2. 如果用户没有提供指令,但是说要复制,则根据对应的情况生成一个快捷键,比如ctrlOrCommand+c\n3. *优先级规则**:\n - 如果用户**明确提供了快捷键指令**(如“按 Ctrl+H”、“设置快捷键为 Ctrl+Shift+A”),则**优先解析并生成对应的快捷键**。\n - 如果用户**未明确提供快捷键**,但表达了**常见操作意图**(如“复制”、\"粘贴\"、ps快捷键等),则根据标准自动映射\n\n比如,生成一个ctrl+h的快捷键,生成的json数据是\n```json\n{”type\":\"hotkeys\",\"hotkeys\":\"ctrlOrCommnd+h\"}\n```\n### 用户输入内容是\n"
|
||||
},
|
||||
{
|
||||
"title": "文档",
|
||||
"content": "对当前内容进行美化"
|
||||
}
|
||||
]
|
||||
1
backend/prompts/应用脚本.md
Normal file
1
backend/prompts/应用脚本.md
Normal file
@@ -0,0 +1 @@
|
||||
应用脚本
|
||||
18
backend/prompts/快捷键.md
Normal file
18
backend/prompts/快捷键.md
Normal file
@@ -0,0 +1,18 @@
|
||||
请根据用户输入生成符合规范的快捷键 JSON 数据,格式如下:
|
||||
```json
|
||||
{"type":"hotkeys","hotkeys":"ctrlOrCommand+h"}
|
||||
```
|
||||
### 规则说明:
|
||||
|
||||
1. **数据类型固定为**:`"hotkeys"`,字段名为 `hotkeys`,值为**标准快捷键字符串**,使用小写字母和 `+` 连接。
|
||||
1. **操作系统适配**:所有修饰符的 `Ctrl` 键必须替换为 `ctrlOrCommand`, 但是win默认为windows的按键,alt键保留
|
||||
2. 如果用户没有提供指令,但是说要复制,则根据对应的情况生成一个快捷键,比如ctrlOrCommand+c
|
||||
3. *优先级规则**:
|
||||
- 如果用户**明确提供了快捷键指令**(如“按 Ctrl+H”、“设置快捷键为 Ctrl+Shift+A”),则**优先解析并生成对应的快捷键**。
|
||||
- 如果用户**未明确提供快捷键**,但表达了**常见操作意图**(如“复制”、"粘贴"、ps快捷键等),则根据标准自动映射
|
||||
|
||||
比如,生成一个ctrl+h的快捷键,生成的json数据是
|
||||
```json
|
||||
{”type":"hotkeys","hotkeys":"ctrlOrCommnd+h"}
|
||||
```
|
||||
### 用户输入内容是
|
||||
1
backend/prompts/文档.md
Normal file
1
backend/prompts/文档.md
Normal file
@@ -0,0 +1 @@
|
||||
对当前内容进行美化
|
||||
5
backend/prompts/智能家居HA.md
Normal file
5
backend/prompts/智能家居HA.md
Normal file
@@ -0,0 +1,5 @@
|
||||
请根据用户输入生成符合规范的快捷键 JSON 数据,格式如下:
|
||||
|
||||
```json
|
||||
{ "type": "ha", "ha": { "entity_id": "string", "task": "" } }
|
||||
```
|
||||
@@ -1,4 +1,4 @@
|
||||
import { app } from './app.ts'
|
||||
import './router/index.ts';
|
||||
import './routes/index.ts';
|
||||
|
||||
export { app }
|
||||
@@ -1,5 +1,5 @@
|
||||
import { app } from './app.ts'
|
||||
import './router/index.ts';
|
||||
import './routes/index.ts';
|
||||
import { HOME } from './config.ts';
|
||||
import { proxyRoute, initProxy } from '@kevisual/local-proxy/proxy.ts';
|
||||
|
||||
|
||||
120
backend/src/query/query-config/query-config.ts
Normal file
120
backend/src/query/query-config/query-config.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 配置查询
|
||||
* @updatedAt 2025-12-03 11:05: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) {
|
||||
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 },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
|
||||
// base
|
||||
import { app } from '../app.ts';
|
||||
import './noco/index.ts';
|
||||
|
||||
// 添加认证中间件路由
|
||||
const hasAuth = app.router.routes.some(r => r.id === 'auth');
|
||||
if (!hasAuth) {
|
||||
console.log('添加认证中间件路由');
|
||||
@@ -11,5 +14,9 @@ if (!hasAuth) {
|
||||
id: 'auth'
|
||||
}).define(async (ctx) => {
|
||||
// 这里可以添加实际的认证逻辑
|
||||
if (!ctx.query.token) {
|
||||
ctx.query.token = process.env.KEVISUAL_API_TOKEN || ' ';
|
||||
console.log('本地测试认证通过,设置 token');
|
||||
}
|
||||
}).addTo(app);
|
||||
}
|
||||
106
backend/src/routes/noco/auto-generate-data.ts
Normal file
106
backend/src/routes/noco/auto-generate-data.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { app } from '@/app.ts'
|
||||
import { NocoLifeService } from './services/life.ts';
|
||||
import { useContextKey } from '@kevisual/context';
|
||||
import { NocoWehookPayload, ColumnItem } from '@kevisual/noco-auto';
|
||||
import { AIUtils, BaseChat } from '@kevisual/ai';
|
||||
export const reportErrors = (errors: any[]) => {
|
||||
// TODO
|
||||
}
|
||||
app.route({
|
||||
path: 'noco-control',
|
||||
key: 'generateData',
|
||||
description: `多维表格自动生成数据接口, 根据用户需求,自动生成字段"数据"的内容`,
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const query = ctx.query as NocoWehookPayload<ColumnItem>;
|
||||
const token = ctx.query.token || '';
|
||||
const baseId = query?.base_id;
|
||||
const tableId = query?.data?.table_id;
|
||||
let question = ctx.query.question || '';
|
||||
const lifeService = new NocoLifeService({ token });
|
||||
const config = await lifeService.getLifeConfig();
|
||||
console.log('rows', query.data.rows);
|
||||
if (config.baseId !== baseId) {
|
||||
ctx.throw(400, 'baseId 不匹配');
|
||||
}
|
||||
const life = await lifeService.createLife({ ...config, tableId });
|
||||
|
||||
const row = query.data.rows?.[0];
|
||||
if (!row) {
|
||||
ctx.throw(400, '没有数据行');
|
||||
}
|
||||
const columnKeys = Object.keys(row).filter(k => !['Id', 'CreatedAt', 'UpdatedAt'].includes(k));
|
||||
if (columnKeys.length === 0) {
|
||||
ctx.throw(400, '没有可用的字段');
|
||||
}
|
||||
let prompt = question || row['提示词'] || ''
|
||||
const id = row['Id'];
|
||||
if (!id) {
|
||||
ctx.throw(400, '数据行没有 Id');
|
||||
}
|
||||
const title = row['标题'] || '';
|
||||
const summary = row['总结'] || '';
|
||||
const type = row['类型'] || '';
|
||||
const _data = row['数据'] || '';
|
||||
let systemPrompt = getPrompt({ type });
|
||||
let other = `\n相关资料是:
|
||||
标题: ${title}
|
||||
总结: ${summary}`
|
||||
if (_data) {
|
||||
other += `
|
||||
已有数据: ${_data}`
|
||||
}
|
||||
|
||||
if (title) {
|
||||
systemPrompt += other
|
||||
}
|
||||
const ai: BaseChat = useContextKey('ai');
|
||||
const answer = await ai.chat([
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{ role: 'user', content: prompt ? prompt : '请生成对应的数据' }
|
||||
])
|
||||
let msg = ai.responseText || '';
|
||||
console.log('生成的数据内容:', msg);
|
||||
const data = await AIUtils.extractJsonFromMarkdown(msg);
|
||||
if (data == null) {
|
||||
ctx.throw(500, 'AI 返回结果解析失败');
|
||||
}
|
||||
// 更新数据到多维表格
|
||||
const itme = await life.updateItem({
|
||||
Id: id,
|
||||
['数据']: data,
|
||||
});
|
||||
console.log('更新后的数据行:', itme);
|
||||
ctx.body = 'ok'
|
||||
}).addTo(app)
|
||||
|
||||
const DATA_TYPES = ['快捷键', '应用脚本', '智能家居HA', '文档'];
|
||||
type DataType = typeof DATA_TYPES[number];
|
||||
const getPrompt = (opts?: { type: DataType }) => {
|
||||
const type = opts?.type || '通用';
|
||||
|
||||
const data = [
|
||||
{
|
||||
"title": "应用脚本",
|
||||
"content": "应用脚本"
|
||||
},
|
||||
{
|
||||
"title": "智能家居HA",
|
||||
"content": "请根据用户输入生成符合规范的快捷键 JSON 数据,格式如下:\n\n```json\n{ \"type\": \"ha\", \"ha\": { \"entity_id\": \"string\", \"task\": \"\" } }\n```\n"
|
||||
},
|
||||
{
|
||||
"title": "快捷键",
|
||||
"content": "请根据用户输入生成符合规范的快捷键 JSON 数据,格式如下:\n```json\n{\"type\":\"hotkeys\",\"hotkeys\":\"ctrlOrCommand+h\"}\n```\n### 规则说明:\n\n1. **数据类型固定为**:`\"hotkeys\"`,字段名为 `hotkeys`,值为**标准快捷键字符串**,使用小写字母和 `+` 连接。\n1. **操作系统适配**:所有修饰符的 `Ctrl` 键必须替换为 `ctrlOrCommand`, 但是win默认为windows的按键,alt键保留\n2. 如果用户没有提供指令,但是说要复制,则根据对应的情况生成一个快捷键,比如ctrlOrCommand+c\n3. *优先级规则**:\n - 如果用户**明确提供了快捷键指令**(如“按 Ctrl+H”、“设置快捷键为 Ctrl+Shift+A”),则**优先解析并生成对应的快捷键**。\n - 如果用户**未明确提供快捷键**,但表达了**常见操作意图**(如“复制”、\"粘贴\"、ps快捷键等),则根据标准自动映射\n\n比如,生成一个ctrl+h的快捷键,生成的json数据是\n```json\n{”type\":\"hotkeys\",\"hotkeys\":\"ctrlOrCommnd+h\"}\n```\n### 用户输入内容是\n"
|
||||
},
|
||||
{
|
||||
"title": "文档",
|
||||
"content": "对当前内容进行美化"
|
||||
}
|
||||
]
|
||||
|
||||
const item = data.find(d => d.title === type);
|
||||
if (item) {
|
||||
return item.content;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
79
backend/src/routes/noco/config.ts
Normal file
79
backend/src/routes/noco/config.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { app } from '@/app.ts'
|
||||
import { NocoLifeService } from './services/life.ts';
|
||||
import dayjs from 'dayjs';
|
||||
import { useContextKey } from '@kevisual/context';
|
||||
import { BaseChat, AIUtils } from '@kevisual/ai';
|
||||
import { pick } from 'es-toolkit'
|
||||
app.route({
|
||||
path: 'noco-life',
|
||||
key: 'config-update',
|
||||
description: `多维表格配置更新内容, 参数是{data: {baseURL:string; token:string; baseId:string; tableId?:string}}
|
||||
`,
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const token = ctx.query.token || '';
|
||||
const question = ctx.query.question || '';
|
||||
let data = ctx.query.data || {};
|
||||
|
||||
const nocoLifeService = new NocoLifeService({ token });
|
||||
const config = await nocoLifeService.getLifeConfig()
|
||||
if (question) {
|
||||
const ai: BaseChat = useContextKey('ai');
|
||||
const pickData = pick(config, ['baseURL', 'token', 'baseId', 'tableId']);
|
||||
const originConfig = JSON.stringify(pickData);
|
||||
await ai.chat([
|
||||
{
|
||||
role: 'system', content: `你是一个多维表格配置助理,你的任务是帮助用户解析多维表格的配置信息。用户会提供配置信息,你需要从中提取出 baseURL, token, baseId, tableId 等字段,并以 JSON 格式返回。如果某个字段缺失,可以不返回该字段。请确保返回的 JSON 格式正确且易于解析。
|
||||
返回的数据例子: { "baseURL": "https://example.com", "token": "abc123", "baseId": "base_01", "tableId": "table_01" }` },
|
||||
{ role: 'user', content: `当前已有的多维表格配置信息是: ${originConfig}` },
|
||||
{ role: 'user', content: question },
|
||||
])
|
||||
let msg = AIUtils.extractJsonFromMarkdown(ai.responseText || '');
|
||||
if (msg == null) {
|
||||
ctx.throw(500, 'AI 返回结果解析失败');
|
||||
}
|
||||
data = { ...data, ...config, ...msg };
|
||||
}
|
||||
if (!data?.baseURL || !data?.token || !data?.baseId) {
|
||||
ctx.throw(400, '缺少参数 baseURL, token, baseId, tableId');
|
||||
}
|
||||
if (data.baseURL) {
|
||||
config.baseURL = data.baseURL;
|
||||
}
|
||||
if (data.token) {
|
||||
config.token = data.token;
|
||||
}
|
||||
if (data.baseId) {
|
||||
config.baseId = data.baseId;
|
||||
}
|
||||
if (data.tableId) {
|
||||
config.tableId = data.tableId;
|
||||
}
|
||||
// 保存配置
|
||||
const res = await nocoLifeService.updateLifeConfig(config);
|
||||
if (res.code !== 200) {
|
||||
ctx.throw(500, '保存配置失败');
|
||||
}
|
||||
ctx.body = { content: '配置更新成功,当前配置是: ' + (JSON.stringify(config)), data: config };
|
||||
}).addTo(app);
|
||||
|
||||
|
||||
app.route({
|
||||
path: 'noco-life',
|
||||
key: 'config-get',
|
||||
description: `多维表格配置获取
|
||||
`,
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const token = ctx.query.token || '';
|
||||
const nocoLifeService = new NocoLifeService({ token });
|
||||
const config = await nocoLifeService.getLifeConfig()
|
||||
ctx.body = {
|
||||
data: config,
|
||||
content: `当前多维表格配置如下:
|
||||
Base URL: ${config.baseURL}
|
||||
Token: ${config.token}
|
||||
Base ID: ${config.baseId}
|
||||
Table ID: ${config.tableId || '未设置'}`
|
||||
};
|
||||
}).addTo(app);
|
||||
4
backend/src/routes/noco/index.ts
Normal file
4
backend/src/routes/noco/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import './noco-life.ts'
|
||||
import './config.ts'
|
||||
|
||||
import './auto-generate-data.ts'
|
||||
295
backend/src/routes/noco/noco-life.ts
Normal file
295
backend/src/routes/noco/noco-life.ts
Normal file
@@ -0,0 +1,295 @@
|
||||
import { app } from '@/app.ts'
|
||||
import { NocoLifeService } from './services/life.ts';
|
||||
import { useContextKey } from '@kevisual/context';
|
||||
import { BaseChat } from '@kevisual/ai';
|
||||
import { AIUtils } from '@kevisual/ai';
|
||||
import { createLunarDate, toGregorian } from 'lunar';
|
||||
import dayjs from 'dayjs';
|
||||
app.route({
|
||||
path: 'noco-life',
|
||||
key: 'chat',
|
||||
description: `多维表格聊天接口, 对自己的多维表格的数据进行操作,参数是 question, `,
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const question = ctx.query.question || '';
|
||||
if (!question) {
|
||||
ctx.throw(400, '缺少参数 question');
|
||||
}
|
||||
const token = ctx.query.token || '';
|
||||
const tableId = ctx.query.tableId || '';
|
||||
const slicedQuestion = question.slice(0, 10);
|
||||
if (slicedQuestion.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, tableId });
|
||||
await nocoLifeService.initConfig()
|
||||
const routes = ctx.app.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: BaseChat = useContextKey('ai');
|
||||
const slicedQuestion2 = question.slice(0, 1000);
|
||||
const answer = await ai.chat([
|
||||
{ role: 'system', content: `你是一个多维表格助理,你的任务是帮助用户操作和查询多维表格的数据。你可以使用以下工具来完成任务:\n\n${v}` },
|
||||
{ role: 'user', content: question }
|
||||
])
|
||||
let msg = AIUtils.extractJsonFromMarkdown(ai.responseText || '');
|
||||
if (msg == null) {
|
||||
ctx.throw(500, 'AI 返回结果解析失败');
|
||||
}
|
||||
console.log('msg', 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) {
|
||||
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);
|
||||
|
||||
|
||||
app.route({
|
||||
path: 'noco-life',
|
||||
key: 'today',
|
||||
description: `获取今天需要做的事情列表`,
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const token = ctx.query.token || '';
|
||||
const tableId = ctx.query.tableId || '';
|
||||
const nocoLifeService = new NocoLifeService({ token, tableId });
|
||||
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,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,
|
||||
content: list.map(item => {
|
||||
return `任务[${item['Id']}]: ${item['标题']}。\n启动时间: ${dayjs(item['启动时间']).format('YYYY-MM-DD HH:mm:ss')}。标签: ${item['标签'] || '无'} \n总结: ${item['总结'] || '无'}`;
|
||||
}).join('\n')
|
||||
};
|
||||
if (list.length === 0) {
|
||||
ctx.body = {
|
||||
list,
|
||||
content: '今天没有需要做的事情了,休息一下吧'
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
ctx.throw(500, '获取记录列表失败');
|
||||
}).addTo(app);
|
||||
|
||||
app.route({
|
||||
path: 'noco-life',
|
||||
key: 'done',
|
||||
description: `完成某件事情,然后判断下一次运行时间。参数是id,数据类型是number。如果多个存在,则是ids的number数组`,
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const id = ctx.query.id;
|
||||
const ids = ctx.query.ids || [];
|
||||
if (!id && ids.length === 0) {
|
||||
ctx.throw(400, '缺少参数 id');
|
||||
}
|
||||
if (ids.length === 0 && id) {
|
||||
ids.push(Number(id));
|
||||
}
|
||||
console.log('id', id, ids);
|
||||
const token = ctx.query.token || '';
|
||||
const tableId = ctx.query.tableId || '';
|
||||
const nocoLifeService = new NocoLifeService({ token, tableId });
|
||||
await nocoLifeService.initConfig()
|
||||
const messages = [];
|
||||
const changeItem = async (id: number) => {
|
||||
const life = nocoLifeService.life;
|
||||
// 获取记录详情
|
||||
const recordRes = await life.getItem(id);
|
||||
if (recordRes.code !== 200) {
|
||||
// ctx.throw(500, '获取记录详情失败');
|
||||
messages.push({
|
||||
id,
|
||||
content: `获取记录 ${id} 详情失败`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const record = recordRes.data;
|
||||
|
||||
// 检查启动时间是否大于今天
|
||||
const startTime = record['启动时间'];
|
||||
const today = dayjs().startOf('day');
|
||||
const startDate = dayjs(startTime).startOf('day');
|
||||
|
||||
if (startDate.isAfter(today)) {
|
||||
// ctx.throw(400, '还没到今天呢,到时候再做吧');
|
||||
messages.push({
|
||||
id,
|
||||
content: `记录 ${id} 的启动时间是 ${dayjs(startTime).format('YYYY-MM-DD HH:mm:ss')},还没到今天呢,到时候再做吧`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// 计算下一次运行时间
|
||||
// 1. 知道当前时间
|
||||
// 2. 知道任务类型,如果是每日,则加一天;如果是每周,则加七天;如果是每月,则加一个月,如果是每年农历,需要转为新的,如果是其他,需要智能判断
|
||||
// 3. 更新记录
|
||||
const strTime = (time: string) => {
|
||||
return dayjs(time).format('YYYY-MM-DD HH:mm:ss');
|
||||
}
|
||||
const currentTime = strTime(new Date().toISOString());
|
||||
const isLuar = record['类型']?.includes?.('农历');
|
||||
let summay = record['总结'] || '无';
|
||||
if (summay.length > 200) {
|
||||
summay = summay.substring(0, 200) + '...';
|
||||
}
|
||||
const prompt = record['提示词'] || '';
|
||||
const type = record['类型'] || '';
|
||||
const content = `上一次执行的时间是${strTime(startTime)},当前时间是${currentTime},请帮我计算下一次的运行时间,如果时间不存在,默认在8点启动。
|
||||
${prompt ? `这是我给你的提示词,帮你更好地理解我的需求:${prompt}` : ''}
|
||||
|
||||
相关资料是
|
||||
任务:${record['标题']}
|
||||
总结:${summay}
|
||||
类型: ${type}
|
||||
`
|
||||
const ai = useContextKey('ai');
|
||||
await ai.chat([
|
||||
{ role: 'system', content: `你是一个时间计算专家,擅长根据任务类型和时间计算下一次运行时间。只返回我对应的日期的结果,格式是:YYYY-MM-DD HH:mm:ss。` },
|
||||
{ role: 'user', content }
|
||||
])
|
||||
let nextTime = ai.responseText?.trim();
|
||||
try {
|
||||
// 判断返回的时间是否可以格式化
|
||||
if (nextTime && dayjs(nextTime).isValid()) {
|
||||
const time = dayjs(nextTime);
|
||||
if (isLuar) {
|
||||
const festival = createLunarDate({ year: time.year(), month: time.month() + 1, day: time.date() });
|
||||
const { date } = toGregorian(festival);
|
||||
nextTime = dayjs(date).toISOString();
|
||||
} else {
|
||||
nextTime = time.toISOString();
|
||||
}
|
||||
} else {
|
||||
messages.push({
|
||||
id,
|
||||
content: `记录 ${id} 的任务 "${record['标题']}",AI 返回的时间格式无效,无法格式化,返回内容是:${ai.responseText}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
messages.push({
|
||||
id,
|
||||
content: `记录 ${id} 的任务 "${record['标题']}",AI 返回结果解析失败,返回内容是:${ai.responseText}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const update = await life.updateItem({ Id: id, '启动时间': nextTime });
|
||||
if (update.code !== 200) {
|
||||
messages.push({
|
||||
id,
|
||||
content: `记录 ${id} 的任务 "${record['标题']}",更新记录失败`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const msg = {
|
||||
id,
|
||||
nextTime,
|
||||
showCNTime: dayjs(nextTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
content: `任务 "${record['标题']}" 已标记为完成。下一次运行时间是 ${dayjs(nextTime).format('YYYY-MM-DD HH:mm:ss')}`
|
||||
};
|
||||
messages.push(msg);
|
||||
}
|
||||
|
||||
for (const _id of ids) {
|
||||
await changeItem(Number(_id));
|
||||
}
|
||||
ctx.body = {
|
||||
content: messages.map(m => m.content).join('\n'),
|
||||
list: messages
|
||||
};
|
||||
|
||||
}).addTo(app);
|
||||
|
||||
app.route({
|
||||
path: 'noco-life',
|
||||
key: 'record',
|
||||
description: `创建或者更新一条新的记录,参数是 question 和 id, 如果id存在,则更新记录,否则创建新的记录`,
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const { id, question } = ctx.query;
|
||||
let summary = '空'
|
||||
const token = ctx.query.token || '';
|
||||
const tableId = ctx.query.tableId || '';
|
||||
const nocoLifeService = new NocoLifeService({ token, tableId });
|
||||
await nocoLifeService.initConfig()
|
||||
const life = nocoLifeService.life;
|
||||
const ai = useContextKey('ai');
|
||||
let record = null;
|
||||
if (id) {
|
||||
record = await life.getItem(id);
|
||||
if (record.code !== 200) {
|
||||
// 获取记录失败
|
||||
} else {
|
||||
summary = record.data['总结'] || ''
|
||||
}
|
||||
}
|
||||
const prompt = `对当前的内容进行总结,要求简洁扼要,200字以内。如果内容已经很简洁,则不需要修改。当前内容是:${question}\n历史总结内容是:${summary}`;
|
||||
await ai.chat([
|
||||
{ role: 'system', content: `你是一个总结专家,擅长将冗长的信息进行提炼和总结。` },
|
||||
{ role: 'user', content: prompt }
|
||||
])
|
||||
const newSummary = ai.responseText?.trim() || '';
|
||||
if (record) {
|
||||
// 更新记录
|
||||
const updateRes = await life.updateItem({ Id: id, '总结': newSummary });
|
||||
if (updateRes.code !== 200) {
|
||||
ctx.throw(500, '更新记录失败');
|
||||
}
|
||||
ctx.body = {
|
||||
id: id,
|
||||
content: `已更新记录 ${id} 的总结内容为:${newSummary}`
|
||||
}
|
||||
} else {
|
||||
// 创建记录
|
||||
const createRes = await life.createItem({ '标题': question.slice(0, 50), '总结': newSummary, '任务': '运行中', '启动时间': new Date().toISOString() });
|
||||
if (createRes.code !== 200) {
|
||||
ctx.throw(500, '创建记录失败');
|
||||
}
|
||||
ctx.body = {
|
||||
id: createRes.data.Id,
|
||||
content: `已创建新的记录,ID 是 ${createRes.data.Id}\n内容是:${newSummary}`
|
||||
}
|
||||
}
|
||||
}).addTo(app);
|
||||
app.route({
|
||||
path: 'noco-life',
|
||||
key: 'how-to-use',
|
||||
description: `多维表格使用指南,如何配置和使用多维表格`,
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const message = `多维表格使用指南:
|
||||
1. 发送 "配置多维表格" 来设置和更新多维表格的配置。
|
||||
2. 配置包含的内容是 baseURL, baseId, token, tableId 其中 tableId是可选的,如果不配置会自动创建一个新的多维表格。
|
||||
`
|
||||
ctx.body = {
|
||||
content: message
|
||||
}
|
||||
}).addTo(app);
|
||||
108
backend/src/routes/noco/services/life.ts
Normal file
108
backend/src/routes/noco/services/life.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { Life, NocoApi } from '@kevisual/noco-auto';
|
||||
import { QueryConfig } from "@/query/query-config/query-config.ts";
|
||||
import { Query } from "@kevisual/query/query";
|
||||
import { CustomError } from '@kevisual/router'
|
||||
type NocoLifeServiceOpts = {
|
||||
token: string;
|
||||
/**
|
||||
* 不使用默认的视图配置,使用当前的表
|
||||
*/
|
||||
tableId?: string;
|
||||
}
|
||||
|
||||
export type NocoLifeConfig = {
|
||||
baseURL?: string;
|
||||
baseId: string;
|
||||
token: string;
|
||||
tableId?: string;
|
||||
}
|
||||
export class NocoLifeService {
|
||||
token: string;
|
||||
nocoApi: NocoApi;
|
||||
life: Life;
|
||||
queryConfig: QueryConfig;
|
||||
tableId: string;
|
||||
constructor(opts: NocoLifeServiceOpts) {
|
||||
this.token = opts.token;
|
||||
const tableId = opts.tableId;
|
||||
this.tableId = tableId || '';
|
||||
this.initEnv();
|
||||
}
|
||||
initEnv() {
|
||||
const config = new QueryConfig({
|
||||
query: new Query({ url: "https://kevisual.cn/api/router" })
|
||||
});
|
||||
this.queryConfig = config;
|
||||
}
|
||||
async getLifeConfig(): Promise<NocoLifeConfig> {
|
||||
const res = await this.queryConfig.getByKey('life.json', { token: this.token });
|
||||
if (res.code !== 200) {
|
||||
return { 'baseId': '', baseURL: '', token: '', tableId: '' } as NocoLifeConfig;
|
||||
}
|
||||
return res.data?.data as NocoLifeConfig;
|
||||
}
|
||||
async updateLifeConfig(data: NocoLifeConfig) {
|
||||
const res = await this.queryConfig.updateConfig({
|
||||
key: 'life.json',
|
||||
data,
|
||||
}, { token: this.token });
|
||||
return res;
|
||||
}
|
||||
createLife(data: NocoLifeConfig) {
|
||||
if (!data.tableId && this.tableId) {
|
||||
data.tableId = this.tableId;
|
||||
}
|
||||
const nocoApi = new NocoApi({
|
||||
baseURL: data.baseURL || '',
|
||||
token: data.token || '',
|
||||
table: data.tableId || '',
|
||||
});
|
||||
const life = new Life({ nocoApi, baseId: data.baseId });
|
||||
this.life = life;
|
||||
return life;
|
||||
}
|
||||
/**
|
||||
* 需要从服务端获取自己保存的配置,包括 nocodb 地址,apiKey 等
|
||||
*/
|
||||
async initConfig() {
|
||||
const res = await this.queryConfig.getByKey('life.json', { token: this.token });
|
||||
if (res.code !== 200) {
|
||||
console.error('获取配置失败', res);
|
||||
throw new CustomError(res.code, `获取配置失败: ${res.message}`);
|
||||
}
|
||||
const lifeConfig: NocoLifeConfig = res.data?.data as NocoLifeConfig;
|
||||
if (!lifeConfig || !lifeConfig.token || !lifeConfig.baseId || !lifeConfig.baseURL) {
|
||||
throw new CustomError(400, `配置不完整,请先设置正确的配置, baseURL, baseId, token 都是必须的.`);
|
||||
}
|
||||
const nocoApi = new NocoApi({
|
||||
baseURL: lifeConfig.baseURL || '',
|
||||
token: lifeConfig.token || '',
|
||||
});
|
||||
|
||||
this.nocoApi = nocoApi;
|
||||
const life = new Life({ nocoApi, baseId: lifeConfig.baseId });
|
||||
let tableId = this.tableId || lifeConfig.tableId || '';
|
||||
if (!tableId) {
|
||||
const newTable = await life.createTable()
|
||||
if (newTable.code !== 200) {
|
||||
throw new CustomError(500, `创建默认表失败: ${newTable.message}`);
|
||||
}
|
||||
tableId = newTable.data?.id;
|
||||
// 保存 tableId 到配置中
|
||||
const res = await this.queryConfig.updateConfig({
|
||||
key: 'life.json',
|
||||
data: { ...lifeConfig, tableId },
|
||||
}, { token: this.token });
|
||||
if (res.code === 200) {
|
||||
console.log('默认表创建成功,配置已更新');
|
||||
}
|
||||
}
|
||||
life.tableId = tableId || '';
|
||||
this.life = life;
|
||||
return lifeConfig;
|
||||
}
|
||||
initNocoApi() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
21
backend/test/chat.ts
Normal file
21
backend/test/chat.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { app, sleep, token } from './common.ts';
|
||||
|
||||
|
||||
const res = await app.call({
|
||||
path: 'noco-life',
|
||||
key: "chat",
|
||||
payload: {
|
||||
// question: '今天我需要做什么事情?',
|
||||
// question: '任务5 完成了,帮我判断下一次运行时间应该是什么时候?',
|
||||
// question: '任务59 完成了',
|
||||
// question: '我的多维表格配置'
|
||||
// question: '记录一下,今天洗了澡',
|
||||
// question: '编辑任务123,进行补充, 对opencode进行描述介绍。',
|
||||
// question: '编辑任务94,对内容注释',
|
||||
// question: '任务59和124完成了',
|
||||
question: '任务 126 完成',
|
||||
token: token,
|
||||
},
|
||||
})
|
||||
|
||||
console.log('res', res.code, res.body, res.message);
|
||||
20
backend/test/common.ts
Normal file
20
backend/test/common.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { app } from '../src/index.ts';
|
||||
export const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
// await sleep(1000); // 等待服务启动
|
||||
import { useContextKey } from '@kevisual/context';
|
||||
import { BailianProvider } from '@kevisual/ai';
|
||||
import dotenv from 'dotenv';
|
||||
dotenv.config();
|
||||
console.log('process.env.BAILIAN_API_KEY', process.env.BAILIAN_API_KEY);
|
||||
const token = process.env.KEVISUAL_API_TOKEN || '';
|
||||
const ai = useContextKey('ai', () => {
|
||||
return new BailianProvider({
|
||||
apiKey: process.env.BAILIAN_API_KEY || '',
|
||||
model: 'qwen-plus'
|
||||
});
|
||||
});
|
||||
export {
|
||||
app,
|
||||
ai,
|
||||
token,
|
||||
}
|
||||
27
backend/test/create-json.ts
Normal file
27
backend/test/create-json.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import fastGlob from 'fast-glob';
|
||||
|
||||
// 匹配所有 markdown 文件
|
||||
const mds = await fastGlob('../prompts/*.md', {
|
||||
cwd: __dirname,
|
||||
absolute: false,
|
||||
});
|
||||
|
||||
// 生成 JSON 数据
|
||||
const jsonData = mds.map((filePath) => {
|
||||
const fileName = path.basename(filePath, '.md');
|
||||
const content = fs.readFileSync(path.join(__dirname, filePath), 'utf-8');
|
||||
return {
|
||||
title: fileName,
|
||||
content: content,
|
||||
};
|
||||
});
|
||||
|
||||
// 输出 JSON
|
||||
const outputPath = path.join(__dirname, '../prompts/markdown-files.json');
|
||||
fs.writeFileSync(outputPath, JSON.stringify(jsonData, null, 2), 'utf-8');
|
||||
|
||||
console.log(`已生成 JSON 文件: ${outputPath}`);
|
||||
console.log(`共找到 ${jsonData.length} 个 markdown 文件`);
|
||||
|
||||
13
backend/test/done.ts
Normal file
13
backend/test/done.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { app, sleep } from './common';
|
||||
|
||||
|
||||
const res = await app.call({
|
||||
path: 'noco-life',
|
||||
key: "done",
|
||||
payload: {
|
||||
id: 59, // 洗漱
|
||||
// id:4, // 爸爸
|
||||
}
|
||||
})
|
||||
|
||||
console.log('res', res.code, res.message, res.body);
|
||||
11
backend/test/extract.ts
Normal file
11
backend/test/extract.ts
Normal file
@@ -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);
|
||||
9
backend/test/today.ts
Normal file
9
backend/test/today.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { app, sleep } from './common';
|
||||
|
||||
|
||||
const res = await app.call({
|
||||
path: 'noco-life',
|
||||
key: "today"
|
||||
})
|
||||
|
||||
console.log('res', res.body);
|
||||
22
demodule.sh
22
demodule.sh
@@ -1,22 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "🧹 正在移除所有 Git Submodule 配置(保留文件)..."
|
||||
|
||||
# 1. 取消注册所有子模块(关键!保留文件)
|
||||
git submodule foreach --quiet 'git submodule deinit -- "$name"'
|
||||
|
||||
# 2. 删除 .gitmodules 中的所有子模块配置段
|
||||
git config -f .gitmodules --remove-section submodule 2>/dev/null
|
||||
|
||||
# 3. 从 Git 索引中移除所有子模块(不删文件!)
|
||||
git submodule foreach --quiet 'git rm --cached "$name"'
|
||||
|
||||
# 4. 清理残留的 .git/modules/ 目录(保险)
|
||||
rm -rf .git/modules/* 2>/dev/null
|
||||
|
||||
# 5. 提交变更
|
||||
git add .gitmodules
|
||||
git add --all
|
||||
git commit -m "Remove all submodules, keep files" 2>/dev/null && echo "✅ 已提交变更" || echo "ℹ️ 无变更需提交(可能已清理)"
|
||||
|
||||
echo "🎉 所有子模块配置已移除,文件全部保留!"
|
||||
@@ -19,29 +19,29 @@
|
||||
"@astrojs/mdx": "^4.3.12",
|
||||
"@astrojs/react": "^4.4.2",
|
||||
"@astrojs/sitemap": "^3.6.0",
|
||||
"@kevisual/query": "^0.0.29",
|
||||
"@kevisual/query": "^0.0.31",
|
||||
"@kevisual/query-login": "^0.0.7",
|
||||
"@kevisual/registry": "^0.0.1",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"@uiw/react-md-editor": "^4.0.8",
|
||||
"antd": "^6.0.0",
|
||||
"astro": "^5.16.0",
|
||||
"@uiw/react-md-editor": "^4.0.11",
|
||||
"antd": "^6.0.1",
|
||||
"astro": "^5.16.4",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.19",
|
||||
"es-toolkit": "^1.42.0",
|
||||
"github-markdown-css": "^5.8.1",
|
||||
"highlight.js": "^11.11.1",
|
||||
"lucide-react": "^0.554.0",
|
||||
"lucide-react": "^0.556.0",
|
||||
"marked": "^17.0.1",
|
||||
"marked-highlight": "^2.2.3",
|
||||
"nanoid": "^5.1.6",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react": "^19.2.1",
|
||||
"react-dom": "^19.2.1",
|
||||
"react-toastify": "^11.0.5",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"zustand": "^5.0.8"
|
||||
"zustand": "^5.0.9"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -54,7 +54,7 @@
|
||||
"tailwindcss": "^4.1.17",
|
||||
"tw-animate-css": "^1.4.0"
|
||||
},
|
||||
"packageManager": "pnpm@10.23.0",
|
||||
"packageManager": "pnpm@10.24.0",
|
||||
"onlyBuiltDependencies": [
|
||||
"@tailwindcss/oxide",
|
||||
"esbuild",
|
||||
|
||||
30
package.json
30
package.json
@@ -1,22 +1,36 @@
|
||||
{
|
||||
"name": "@kevisual/apps-template",
|
||||
"version": "0.0.1",
|
||||
"name": "@kevisual/noco-auto",
|
||||
"version": "0.0.2",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"main": "mod.ts",
|
||||
"scripts": {
|
||||
"build": "pnpm build:web && pnpm build:backend",
|
||||
"build:backend": "cd backend && bun run src/main.ts",
|
||||
"build:web": "cd frontend && pnpm build",
|
||||
"serve": "cd backend && bun run src/main.ts",
|
||||
"init": "git submodule update --init --recursive",
|
||||
"clean:module": "rm .gitmodules -rf && sh demodules.sh"
|
||||
"serve": "cd backend && bun run src/main.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"files": [
|
||||
"agents/noco"
|
||||
],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@10.19.0",
|
||||
"packageManager": "pnpm@10.24.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@kevisual/router": "^0.0.33"
|
||||
"@kevisual/context": "^0.0.4",
|
||||
"@kevisual/noco": "^0.0.8",
|
||||
"@kevisual/query": "^0.0.31",
|
||||
"@kevisual/router": "^0.0.36",
|
||||
"@kevisual/use-config": "^1.0.21"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"exports": {
|
||||
".": "./mod.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.10.1"
|
||||
}
|
||||
}
|
||||
7060
pnpm-lock.yaml
generated
7060
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
24
scripts/create.ts
Normal file
24
scripts/create.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
// 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 = 'bMLb3rzj9Qz_nOuQ0dj9PRYQbPK_79C2Yfbq5Xae'
|
||||
const baseId = 'p7k66s6p7lss31j'
|
||||
const nocoApi = new NocoApi({
|
||||
baseURL: 'http://localhost:8080',
|
||||
token
|
||||
});
|
||||
|
||||
const life = new Life({ nocoApi, baseId });
|
||||
|
||||
const tableRes = await life.createTable();
|
||||
console.log('tableRes', tableRes);
|
||||
|
||||
const datas = generateMultipleRandomItems(100);
|
||||
for (const data of datas) {
|
||||
const createRes = await life.creatItem(data);
|
||||
console.log(createRes);
|
||||
}
|
||||
|
||||
21
scripts/get-list.ts
Normal file
21
scripts/get-list.ts
Normal file
@@ -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 }));
|
||||
105
scripts/random-data.ts
Normal file
105
scripts/random-data.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
type Item = {
|
||||
Id?: number;
|
||||
'标题': 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 = [
|
||||
'探索未知的宇宙',
|
||||
'人工智能的未来',
|
||||
'可持续发展的重要性',
|
||||
'历史上的伟大人物',
|
||||
'科技改变生活'
|
||||
];
|
||||
|
||||
const descriptions = [
|
||||
'这是一段关于宇宙探索的描述,涵盖了最新的发现和理论。',
|
||||
'本文探讨了人工智能的发展趋势及其对社会的影响。',
|
||||
'讨论了可持续发展的概念及其在现代社会中的应用。',
|
||||
'介绍了历史上几位对世界产生重大影响的人物。',
|
||||
'分析了科技进步如何改变了我们的日常生活。'
|
||||
];
|
||||
|
||||
const summaries = [
|
||||
'本文总结了宇宙探索的现状和未来方向。',
|
||||
'总结了人工智能技术的发展及其潜在挑战。',
|
||||
'概述了可持续发展的关键要素和实践方法。',
|
||||
'回顾了历史伟人对社会的贡献和影响。',
|
||||
'总结了科技进步对生活方式的改变。'
|
||||
];
|
||||
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],
|
||||
'类型': types[Math.floor(Math.random() * types.length)].title,
|
||||
'启动时间': randomDate.toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
export function generateMultipleRandomItems(count: number): Item[] {
|
||||
const items: Item[] = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
const item = generateRandomItem();
|
||||
items.push(item);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
Reference in New Issue
Block a user