feat: implement AI agent for flowme-life interactions
- Add agent-run module to handle AI interactions with tools and messages. - Create routes for proxying requests to OpenAI and Anthropic APIs. - Implement flowme-life chat route for user queries and task management. - Add services for retrieving and updating life records in the database. - Implement logic for fetching today's tasks and marking tasks as done with next execution time calculation. - Introduce tests for flowme-life functionalities.
This commit is contained in:
23
package.json
23
package.json
@@ -41,14 +41,14 @@
|
|||||||
],
|
],
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kevisual/ai": "^0.0.26",
|
"@kevisual/ai": "^0.0.27",
|
||||||
"@kevisual/auth": "^2.0.3",
|
"@kevisual/auth": "^2.0.3",
|
||||||
"@kevisual/js-filter": "^0.0.5",
|
"@kevisual/js-filter": "^0.0.6",
|
||||||
"@kevisual/query": "^0.0.52",
|
"@kevisual/query": "^0.0.53",
|
||||||
"@types/busboy": "^1.5.4",
|
"@types/busboy": "^1.5.4",
|
||||||
"@types/send": "^1.2.1",
|
"@types/send": "^1.2.1",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
"bullmq": "^5.70.2",
|
"bullmq": "^5.70.4",
|
||||||
"busboy": "^1.6.0",
|
"busboy": "^1.6.0",
|
||||||
"drizzle-kit": "^0.31.9",
|
"drizzle-kit": "^0.31.9",
|
||||||
"drizzle-orm": "^0.45.1",
|
"drizzle-orm": "^0.45.1",
|
||||||
@@ -58,26 +58,28 @@
|
|||||||
"xml2js": "^0.6.2"
|
"xml2js": "^0.6.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.1002.0",
|
"@ai-sdk/openai-compatible": "^2.0.35",
|
||||||
|
"@aws-sdk/client-s3": "^3.1005.0",
|
||||||
"@kevisual/api": "^0.0.62",
|
"@kevisual/api": "^0.0.62",
|
||||||
"@kevisual/cnb": "^0.0.33",
|
"@kevisual/cnb": "^0.0.42",
|
||||||
"@kevisual/context": "^0.0.8",
|
"@kevisual/context": "^0.0.8",
|
||||||
"@kevisual/convex": "^0.0.4",
|
"@kevisual/convex": "^0.0.6",
|
||||||
"@kevisual/local-app-manager": "0.1.32",
|
"@kevisual/local-app-manager": "0.1.32",
|
||||||
"@kevisual/logger": "^0.0.4",
|
"@kevisual/logger": "^0.0.4",
|
||||||
"@kevisual/oss": "0.0.20",
|
"@kevisual/oss": "0.0.20",
|
||||||
"@kevisual/permission": "^0.0.4",
|
"@kevisual/permission": "^0.0.4",
|
||||||
"@kevisual/router": "0.0.85",
|
"@kevisual/router": "0.1.0",
|
||||||
"@kevisual/types": "^0.0.12",
|
"@kevisual/types": "^0.0.12",
|
||||||
"@kevisual/use-config": "^1.0.30",
|
"@kevisual/use-config": "^1.0.30",
|
||||||
"@types/archiver": "^7.0.0",
|
"@types/archiver": "^7.0.0",
|
||||||
"@types/bun": "^1.3.10",
|
"@types/bun": "^1.3.10",
|
||||||
"@types/crypto-js": "^4.2.2",
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/jsonwebtoken": "^9.0.10",
|
"@types/jsonwebtoken": "^9.0.10",
|
||||||
"@types/node": "^25.3.3",
|
"@types/node": "^25.4.0",
|
||||||
"@types/pg": "^8.18.0",
|
"@types/pg": "^8.18.0",
|
||||||
"@types/semver": "^7.7.1",
|
"@types/semver": "^7.7.1",
|
||||||
"@types/xml2js": "^0.4.14",
|
"@types/xml2js": "^0.4.14",
|
||||||
|
"ai": "^6.0.116",
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
"convex": "^1.32.0",
|
"convex": "^1.32.0",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
@@ -87,6 +89,7 @@
|
|||||||
"es-toolkit": "^1.45.1",
|
"es-toolkit": "^1.45.1",
|
||||||
"ioredis": "^5.10.0",
|
"ioredis": "^5.10.0",
|
||||||
"jsonwebtoken": "^9.0.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
|
"lunar": "^2.0.0",
|
||||||
"nanoid": "^5.1.6",
|
"nanoid": "^5.1.6",
|
||||||
"p-queue": "^9.1.0",
|
"p-queue": "^9.1.0",
|
||||||
"pg": "^8.20.0",
|
"pg": "^8.20.0",
|
||||||
@@ -99,7 +102,7 @@
|
|||||||
"picomatch": "^4.0.2",
|
"picomatch": "^4.0.2",
|
||||||
"ioredis": "^5.9.3"
|
"ioredis": "^5.9.3"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.30.3",
|
"packageManager": "pnpm@10.32.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"wxmsg"
|
"wxmsg"
|
||||||
]
|
]
|
||||||
|
|||||||
1291
pnpm-lock.yaml
generated
1291
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
26
src/app.ts
26
src/app.ts
@@ -7,7 +7,9 @@ import { BailianProvider } from '@kevisual/ai';
|
|||||||
import * as schema from './db/schema.ts';
|
import * as schema from './db/schema.ts';
|
||||||
import { config } from './modules/config.ts'
|
import { config } from './modules/config.ts'
|
||||||
import { db } from './modules/db.ts'
|
import { db } from './modules/db.ts'
|
||||||
import { convexClient, convexApi } from './modules/convex.ts'
|
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
||||||
|
|
||||||
|
|
||||||
export const router = useContextKey('router', () => new SimpleRouter());
|
export const router = useContextKey('router', () => new SimpleRouter());
|
||||||
export const runtime = useContextKey('runtime', () => {
|
export const runtime = useContextKey('runtime', () => {
|
||||||
return {
|
return {
|
||||||
@@ -44,6 +46,24 @@ export const ai = useContextKey('ai', () => {
|
|||||||
|
|
||||||
export { schema };
|
export { schema };
|
||||||
|
|
||||||
|
export const bailian = createOpenAICompatible({
|
||||||
|
baseURL: 'https://coding.dashscope.aliyuncs.com/v1',
|
||||||
|
name: 'custom-bailian',
|
||||||
|
apiKey: process.env.BAILIAN_CODE_API_KEY!,
|
||||||
|
});
|
||||||
|
|
||||||
export const convex = useContextKey('convex', () => convexClient);
|
export const cnb = createOpenAICompatible({
|
||||||
export { convexApi };
|
baseURL: 'https://api.cnb.cool/kevisual/kevisual/-/ai/',
|
||||||
|
name: 'custom-cnb',
|
||||||
|
apiKey: process.env.CNB_API_KEY!,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const models = {
|
||||||
|
'doubao-ark-code-latest': 'doubao-ark-code-latest',
|
||||||
|
'GLM-4.7': 'GLM-4.7',
|
||||||
|
'MiniMax-M2.1': 'MiniMax-M2.1',
|
||||||
|
'qwen3-coder-plus': 'qwen3-coder-plus',
|
||||||
|
'hunyuan-a13b': 'hunyuan-a13b',
|
||||||
|
'qwen-plus': 'qwen-plus',
|
||||||
|
'auto': 'auto',
|
||||||
|
}
|
||||||
505
src/db/drizzle/0002_loving_lyja.sql
Normal file
505
src/db/drizzle/0002_loving_lyja.sql
Normal file
@@ -0,0 +1,505 @@
|
|||||||
|
CREATE TYPE "public"."enum_cf_router_code_type" AS ENUM('route', 'middleware');--> statement-breakpoint
|
||||||
|
CREATE TABLE "ai_agent" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"type" varchar(255) NOT NULL,
|
||||||
|
"baseUrl" varchar(255) NOT NULL,
|
||||||
|
"apiKey" varchar(255) NOT NULL,
|
||||||
|
"temperature" double precision,
|
||||||
|
"cache" varchar(255),
|
||||||
|
"cacheName" varchar(255),
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"model" varchar(255) NOT NULL,
|
||||||
|
"data" json DEFAULT '{}'::json,
|
||||||
|
"status" varchar(255) DEFAULT 'open',
|
||||||
|
"key" varchar(255) NOT NULL,
|
||||||
|
"description" text,
|
||||||
|
"deletedAt" timestamp with time zone,
|
||||||
|
CONSTRAINT "ai_agent_key_key" UNIQUE("key")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "apps_trades" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"out_trade_no" varchar(255) NOT NULL,
|
||||||
|
"money" integer NOT NULL,
|
||||||
|
"subject" text NOT NULL,
|
||||||
|
"status" varchar(255) DEFAULT 'WAIT_BUYER_PAY' NOT NULL,
|
||||||
|
"type" varchar(255) DEFAULT 'alipay' NOT NULL,
|
||||||
|
"data" jsonb DEFAULT '{"list":[]}'::jsonb,
|
||||||
|
"uid" uuid,
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"deletedAt" timestamp with time zone,
|
||||||
|
CONSTRAINT "apps_trades_out_trade_no_key" UNIQUE("out_trade_no")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "cf_orgs" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"username" varchar(255) NOT NULL,
|
||||||
|
"users" jsonb DEFAULT '[]'::jsonb,
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"deletedAt" timestamp with time zone,
|
||||||
|
"description" varchar(255),
|
||||||
|
CONSTRAINT "cf_orgs_username_key" UNIQUE("username")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "cf_router_code" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"path" varchar(255) NOT NULL,
|
||||||
|
"key" varchar(255) NOT NULL,
|
||||||
|
"active" boolean DEFAULT false,
|
||||||
|
"project" varchar(255) DEFAULT 'default',
|
||||||
|
"code" text DEFAULT '',
|
||||||
|
"type" "enum_cf_router_code_type" DEFAULT 'route',
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"middleware" varchar(255)[] DEFAULT '{"RRAY[]::character varying[])::character varying(25"}',
|
||||||
|
"next" varchar(255) DEFAULT '',
|
||||||
|
"exec" text DEFAULT '',
|
||||||
|
"data" json DEFAULT '{}'::json,
|
||||||
|
"validator" json DEFAULT '{}'::json,
|
||||||
|
"deletedAt" timestamp with time zone
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "cf_user" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"username" varchar(255) NOT NULL,
|
||||||
|
"password" varchar(255),
|
||||||
|
"salt" varchar(255),
|
||||||
|
"needChangePassword" boolean DEFAULT false,
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"description" text,
|
||||||
|
"data" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"deletedAt" timestamp with time zone,
|
||||||
|
"type" varchar(255) DEFAULT 'user',
|
||||||
|
"owner" uuid,
|
||||||
|
"orgId" uuid,
|
||||||
|
"email" varchar(255),
|
||||||
|
"avatar" text,
|
||||||
|
"nickname" text,
|
||||||
|
CONSTRAINT "cf_user_username_key" UNIQUE("username")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "cf_user_secrets" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"description" text,
|
||||||
|
"status" varchar(255) DEFAULT 'active',
|
||||||
|
"title" text,
|
||||||
|
"expiredTime" timestamp with time zone,
|
||||||
|
"token" varchar(255) DEFAULT '' NOT NULL,
|
||||||
|
"userId" uuid,
|
||||||
|
"data" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"orgId" uuid,
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "chat_histories" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"data" json,
|
||||||
|
"chatId" uuid,
|
||||||
|
"chatPromptId" uuid,
|
||||||
|
"root" boolean DEFAULT false,
|
||||||
|
"show" boolean DEFAULT true,
|
||||||
|
"uid" varchar(255),
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"role" varchar(255) DEFAULT 'user'
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "chat_prompts" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"title" varchar(255) NOT NULL,
|
||||||
|
"description" text,
|
||||||
|
"data" json,
|
||||||
|
"key" varchar(255) DEFAULT '' NOT NULL,
|
||||||
|
"uid" varchar(255),
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"deletedAt" timestamp with time zone
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "chat_sessions" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"data" json DEFAULT '{}'::json,
|
||||||
|
"chatPromptId" uuid,
|
||||||
|
"type" varchar(255) DEFAULT 'production',
|
||||||
|
"uid" varchar(255),
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"title" varchar(255) DEFAULT '',
|
||||||
|
"key" varchar(255)
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "file_sync" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"name" varchar(255),
|
||||||
|
"hash" varchar(255),
|
||||||
|
"stat" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"data" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"checkedAt" timestamp with time zone,
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "kv_ai_chat_history" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"username" varchar(255) DEFAULT '' NOT NULL,
|
||||||
|
"model" varchar(255) DEFAULT '' NOT NULL,
|
||||||
|
"group" varchar(255) DEFAULT '' NOT NULL,
|
||||||
|
"title" varchar(255) DEFAULT '' NOT NULL,
|
||||||
|
"messages" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
||||||
|
"prompt_tokens" integer DEFAULT 0,
|
||||||
|
"total_tokens" integer DEFAULT 0,
|
||||||
|
"completion_tokens" integer DEFAULT 0,
|
||||||
|
"data" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"uid" uuid,
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"version" integer DEFAULT 0,
|
||||||
|
"type" varchar(255) DEFAULT 'keep' NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "kv_app" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"data" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"version" varchar(255) DEFAULT '',
|
||||||
|
"key" varchar(255),
|
||||||
|
"uid" uuid,
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"deletedAt" timestamp with time zone,
|
||||||
|
"title" varchar(255) DEFAULT '',
|
||||||
|
"description" varchar(255) DEFAULT '',
|
||||||
|
"user" varchar(255),
|
||||||
|
"status" varchar(255) DEFAULT 'running',
|
||||||
|
"pid" uuid,
|
||||||
|
"proxy" boolean DEFAULT false,
|
||||||
|
CONSTRAINT "key_uid_unique" UNIQUE("key","uid")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "kv_app_domain" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"domain" varchar(255) NOT NULL,
|
||||||
|
"appId" varchar(255),
|
||||||
|
"uid" varchar(255),
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"deletedAt" timestamp with time zone,
|
||||||
|
"data" jsonb,
|
||||||
|
"status" varchar(255) DEFAULT 'running' NOT NULL,
|
||||||
|
CONSTRAINT "kv_app_domain_domain_key" UNIQUE("domain")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "kv_app_list" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"data" json DEFAULT '{}'::json,
|
||||||
|
"version" varchar(255) DEFAULT '',
|
||||||
|
"uid" uuid,
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"deletedAt" timestamp with time zone,
|
||||||
|
"key" varchar(255),
|
||||||
|
"status" varchar(255) DEFAULT 'running'
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "kv_config" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"title" text DEFAULT '',
|
||||||
|
"key" text DEFAULT '',
|
||||||
|
"description" text DEFAULT '',
|
||||||
|
"tags" jsonb DEFAULT '[]'::jsonb,
|
||||||
|
"data" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"uid" uuid,
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"deletedAt" timestamp with time zone,
|
||||||
|
"hash" text DEFAULT ''
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "kv_light_code" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"title" text DEFAULT '',
|
||||||
|
"description" text DEFAULT '',
|
||||||
|
"type" text DEFAULT 'render-js',
|
||||||
|
"code" text DEFAULT '',
|
||||||
|
"data" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"createdAt" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"uid" uuid,
|
||||||
|
"tags" jsonb DEFAULT '[]'::jsonb,
|
||||||
|
"hash" text DEFAULT ''
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "kv_github" (
|
||||||
|
"id" uuid PRIMARY KEY NOT NULL,
|
||||||
|
"title" varchar(255) DEFAULT '',
|
||||||
|
"githubToken" varchar(255) DEFAULT '',
|
||||||
|
"uid" uuid,
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"deletedAt" timestamp with time zone
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "kv_packages" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"title" text DEFAULT '',
|
||||||
|
"description" text DEFAULT '',
|
||||||
|
"tags" jsonb DEFAULT '[]'::jsonb,
|
||||||
|
"data" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"publish" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"expand" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"uid" uuid,
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"deletedAt" timestamp with time zone
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "kv_page" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"title" varchar(255) DEFAULT '',
|
||||||
|
"description" text DEFAULT '',
|
||||||
|
"type" varchar(255) DEFAULT '',
|
||||||
|
"data" json DEFAULT '{}'::json,
|
||||||
|
"uid" uuid,
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"deletedAt" timestamp with time zone,
|
||||||
|
"publish" json DEFAULT '{}'::json
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "kv_resource" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"name" varchar(255) DEFAULT '',
|
||||||
|
"description" text DEFAULT '',
|
||||||
|
"source" varchar(255) DEFAULT '',
|
||||||
|
"sourceId" varchar(255) DEFAULT '',
|
||||||
|
"version" varchar(255) DEFAULT '0.0.0',
|
||||||
|
"data" json DEFAULT '{}'::json,
|
||||||
|
"uid" uuid,
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"deletedAt" timestamp with time zone
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "kv_vip" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"userId" uuid NOT NULL,
|
||||||
|
"level" varchar(255) DEFAULT 'free',
|
||||||
|
"category" varchar(255) NOT NULL,
|
||||||
|
"startDate" timestamp with time zone,
|
||||||
|
"endDate" timestamp with time zone,
|
||||||
|
"data" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"deletedAt" timestamp with time zone,
|
||||||
|
"title" text DEFAULT '' NOT NULL,
|
||||||
|
"description" text DEFAULT '' NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "micro_apps_upload" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"title" varchar(255) DEFAULT '',
|
||||||
|
"description" varchar(255) DEFAULT '',
|
||||||
|
"tags" jsonb DEFAULT '[]'::jsonb,
|
||||||
|
"type" varchar(255) DEFAULT '',
|
||||||
|
"source" varchar(255) DEFAULT '',
|
||||||
|
"data" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"share" boolean DEFAULT false,
|
||||||
|
"uname" varchar(255) DEFAULT '',
|
||||||
|
"uid" uuid,
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "micro_mark" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"title" text DEFAULT '',
|
||||||
|
"description" text DEFAULT '',
|
||||||
|
"tags" jsonb DEFAULT '[]'::jsonb,
|
||||||
|
"data" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"uname" varchar(255) DEFAULT '',
|
||||||
|
"uid" uuid,
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"cover" text DEFAULT '',
|
||||||
|
"thumbnail" text DEFAULT '',
|
||||||
|
"link" text DEFAULT '',
|
||||||
|
"summary" text DEFAULT '',
|
||||||
|
"markType" text DEFAULT 'md',
|
||||||
|
"config" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"puid" uuid,
|
||||||
|
"deletedAt" timestamp with time zone,
|
||||||
|
"version" integer DEFAULT 1,
|
||||||
|
"fileList" jsonb DEFAULT '[]'::jsonb,
|
||||||
|
"key" text DEFAULT ''
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "query_views" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"uid" uuid,
|
||||||
|
"title" text DEFAULT '',
|
||||||
|
"summary" text DEFAULT '',
|
||||||
|
"description" text DEFAULT '',
|
||||||
|
"tags" jsonb DEFAULT '[]'::jsonb,
|
||||||
|
"link" text DEFAULT '',
|
||||||
|
"data" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"createdAt" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "router_views" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"uid" uuid,
|
||||||
|
"title" text DEFAULT '',
|
||||||
|
"summary" text DEFAULT '',
|
||||||
|
"description" text DEFAULT '',
|
||||||
|
"tags" jsonb DEFAULT '[]'::jsonb,
|
||||||
|
"link" text DEFAULT '',
|
||||||
|
"data" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"views" jsonb DEFAULT '[]'::jsonb,
|
||||||
|
"createdAt" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "work_share_mark" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"title" text DEFAULT '',
|
||||||
|
"key" text DEFAULT '',
|
||||||
|
"markType" text DEFAULT 'md',
|
||||||
|
"description" text DEFAULT '',
|
||||||
|
"cover" text DEFAULT '',
|
||||||
|
"link" text DEFAULT '',
|
||||||
|
"tags" jsonb DEFAULT '[]'::jsonb,
|
||||||
|
"summary" text DEFAULT '',
|
||||||
|
"config" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"data" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"fileList" jsonb DEFAULT '[]'::jsonb,
|
||||||
|
"uname" varchar(255) DEFAULT '',
|
||||||
|
"version" integer DEFAULT 1,
|
||||||
|
"markedAt" timestamp with time zone,
|
||||||
|
"uid" uuid,
|
||||||
|
"puid" uuid,
|
||||||
|
"createdAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp with time zone DEFAULT now() NOT NULL,
|
||||||
|
"deletedAt" timestamp with time zone
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "n_code_make" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"slug" text NOT NULL,
|
||||||
|
"resources" jsonb DEFAULT '[]'::jsonb,
|
||||||
|
"userId" uuid,
|
||||||
|
"createdAt" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "n_code_shop" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"slug" text NOT NULL,
|
||||||
|
"title" text NOT NULL,
|
||||||
|
"tags" jsonb,
|
||||||
|
"link" text,
|
||||||
|
"description" text DEFAULT '' NOT NULL,
|
||||||
|
"data" jsonb,
|
||||||
|
"platform" text NOT NULL,
|
||||||
|
"userinfo" text,
|
||||||
|
"orderLink" text NOT NULL,
|
||||||
|
"userId" uuid,
|
||||||
|
"createdAt" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "n_code_short_link" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"slug" text NOT NULL,
|
||||||
|
"code" text DEFAULT '' NOT NULL,
|
||||||
|
"type" text DEFAULT 'link' NOT NULL,
|
||||||
|
"version" text DEFAULT '1.0.0' NOT NULL,
|
||||||
|
"title" text DEFAULT '' NOT NULL,
|
||||||
|
"description" text DEFAULT '' NOT NULL,
|
||||||
|
"tags" jsonb DEFAULT '[]'::jsonb,
|
||||||
|
"data" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"userId" uuid,
|
||||||
|
"createdAt" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "flowme" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"uid" uuid,
|
||||||
|
"title" text DEFAULT '',
|
||||||
|
"tags" jsonb DEFAULT '[]'::jsonb,
|
||||||
|
"summary" text DEFAULT '',
|
||||||
|
"description" text DEFAULT '',
|
||||||
|
"link" text DEFAULT '',
|
||||||
|
"data" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"channelId" uuid,
|
||||||
|
"type" text DEFAULT '',
|
||||||
|
"source" text DEFAULT '',
|
||||||
|
"importance" integer DEFAULT 0,
|
||||||
|
"isArchived" boolean DEFAULT false,
|
||||||
|
"createdAt" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "flowme_channels" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"uid" uuid,
|
||||||
|
"title" text DEFAULT '',
|
||||||
|
"tags" jsonb DEFAULT '[]'::jsonb,
|
||||||
|
"summary" text DEFAULT '',
|
||||||
|
"description" text DEFAULT '',
|
||||||
|
"link" text DEFAULT '',
|
||||||
|
"data" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"key" text DEFAULT '',
|
||||||
|
"color" text DEFAULT '#007bff',
|
||||||
|
"createdAt" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "flowme_life" (
|
||||||
|
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||||
|
"uid" uuid,
|
||||||
|
"title" text DEFAULT '',
|
||||||
|
"tags" jsonb DEFAULT '[]'::jsonb,
|
||||||
|
"summary" text DEFAULT '',
|
||||||
|
"description" text DEFAULT '',
|
||||||
|
"link" text DEFAULT '',
|
||||||
|
"data" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"effectiveAt" timestamp with time zone,
|
||||||
|
"type" text DEFAULT '',
|
||||||
|
"prompt" text DEFAULT '',
|
||||||
|
"taskType" text DEFAULT '',
|
||||||
|
"taskResult" jsonb DEFAULT '{}'::jsonb,
|
||||||
|
"createdAt" timestamp DEFAULT now() NOT NULL,
|
||||||
|
"updatedAt" timestamp DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "cf_prompts" ALTER COLUMN "parents" SET DATA TYPE text[];--> statement-breakpoint
|
||||||
|
ALTER TABLE "cf_prompts" ALTER COLUMN "parents" SET DEFAULT '{}';--> statement-breakpoint
|
||||||
|
ALTER TABLE "flowme" ADD CONSTRAINT "flowme_channelId_flowme_channels_id_fk" FOREIGN KEY ("channelId") REFERENCES "public"."flowme_channels"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||||
|
CREATE INDEX "file_sync_name_idx" ON "file_sync" USING btree ("name");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "kv_app_key_uid" ON "kv_app" USING btree ("key","uid");--> statement-breakpoint
|
||||||
|
CREATE INDEX "query_views_uid_idx" ON "query_views" USING btree ("uid");--> statement-breakpoint
|
||||||
|
CREATE INDEX "query_title_idx" ON "query_views" USING btree ("title");--> statement-breakpoint
|
||||||
|
CREATE INDEX "router_views_uid_idx" ON "router_views" USING btree ("uid");--> statement-breakpoint
|
||||||
|
CREATE INDEX "router_title_idx" ON "router_views" USING btree ("title");--> statement-breakpoint
|
||||||
|
CREATE INDEX "router_views_views_idx" ON "router_views" USING gin ("views");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "n_code_make_idx_slug" ON "n_code_make" USING btree ("slug");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "n_code_shop_idx_slug" ON "n_code_shop" USING btree ("slug");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "n_code_short_idx_slug" ON "n_code_short_link" USING btree ("slug");--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX "n_code_short_idx_code" ON "n_code_short_link" USING btree ("code");--> statement-breakpoint
|
||||||
|
CREATE INDEX "flowme_uid_idx" ON "flowme" USING btree ("uid");--> statement-breakpoint
|
||||||
|
CREATE INDEX "flowme_title_idx" ON "flowme" USING btree ("title");--> statement-breakpoint
|
||||||
|
CREATE INDEX "flowme_channel_id_idx" ON "flowme" USING btree ("channelId");--> statement-breakpoint
|
||||||
|
CREATE INDEX "flowme_channels_uid_idx" ON "flowme_channels" USING btree ("uid");--> statement-breakpoint
|
||||||
|
CREATE INDEX "flowme_channels_key_idx" ON "flowme_channels" USING btree ("key");--> statement-breakpoint
|
||||||
|
CREATE INDEX "flowme_channels_title_idx" ON "flowme_channels" USING btree ("title");--> statement-breakpoint
|
||||||
|
CREATE INDEX "life_uid_idx" ON "flowme_life" USING btree ("uid");--> statement-breakpoint
|
||||||
|
CREATE INDEX "life_title_idx" ON "flowme_life" USING btree ("title");--> statement-breakpoint
|
||||||
|
CREATE INDEX "life_effective_at_idx" ON "flowme_life" USING btree ("effectiveAt");--> statement-breakpoint
|
||||||
|
CREATE INDEX "life_summary_idx" ON "flowme_life" USING btree ("summary");--> statement-breakpoint
|
||||||
|
CREATE INDEX "prompts_parents_idx" ON "cf_prompts" USING gin ("parents");
|
||||||
3479
src/db/drizzle/meta/0002_snapshot.json
Normal file
3479
src/db/drizzle/meta/0002_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,13 @@
|
|||||||
"when": 1767070768620,
|
"when": 1767070768620,
|
||||||
"tag": "0001_solid_nocturne",
|
"tag": "0001_solid_nocturne",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 2,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1773148571509,
|
||||||
|
"tag": "0002_loving_lyja",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,13 @@ export const life = pgTable("flowme_life", {
|
|||||||
link: text('link').default(''),
|
link: text('link').default(''),
|
||||||
data: jsonb().default({}),
|
data: jsonb().default({}),
|
||||||
|
|
||||||
effectiveAt: text('effectiveAt').default(''),
|
effectiveAt: timestamp('effectiveAt', { withTimezone: true }),
|
||||||
|
/**
|
||||||
|
* 智能,
|
||||||
|
* 每年农历
|
||||||
|
* 备忘
|
||||||
|
* 归档
|
||||||
|
*/
|
||||||
type: text('type').default(''),
|
type: text('type').default(''),
|
||||||
prompt: text('prompt').default(''),
|
prompt: text('prompt').default(''),
|
||||||
taskType: text('taskType').default(''),
|
taskType: text('taskType').default(''),
|
||||||
|
|||||||
65
src/modules/ai/agent-run.ts
Normal file
65
src/modules/ai/agent-run.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { type QueryRouterServer, type App, type RouteInfo } from '@kevisual/router'
|
||||||
|
import { generateText, tool, type ModelMessage, type LanguageModel, type GenerateTextResult } from 'ai';
|
||||||
|
import z from 'zod';
|
||||||
|
import { filter } from '@kevisual/js-filter'
|
||||||
|
export const createTool = async (app: QueryRouterServer | App, message: { path: string, key: string, token?: string }) => {
|
||||||
|
const route = app.findRoute({ path: message.path, key: message.key });
|
||||||
|
if (!route) {
|
||||||
|
console.error(`未找到路径 ${message.path} 和 key ${message.key} 的路由`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const _tool = tool({
|
||||||
|
description: route?.metadata?.summary || route?.description || '无描述',
|
||||||
|
inputSchema: z.object({
|
||||||
|
...route.metadata?.args
|
||||||
|
}), // 这里可以根据实际需要定义输入参数的 schema
|
||||||
|
execute: async (args: any) => {
|
||||||
|
const res = await app.run({ path: message.path, key: message.key, payload: args, token: message.token });
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return _tool;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createTools = async (opts: { app: QueryRouterServer | App, token?: string }) => {
|
||||||
|
const { app, token } = opts;
|
||||||
|
const tools: Record<string, any> = {};
|
||||||
|
for (const route of app.routes) {
|
||||||
|
const id = route.id!;
|
||||||
|
const _tool = await createTool(app, { path: route.path!, key: route.key!, token });
|
||||||
|
if (_tool && id) {
|
||||||
|
tools[id] = _tool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tools;
|
||||||
|
}
|
||||||
|
type Route = Partial<RouteInfo>
|
||||||
|
type AgentResult = {
|
||||||
|
result: GenerateTextResult<Record<string, any>, any>,
|
||||||
|
messages: ModelMessage[],
|
||||||
|
}
|
||||||
|
export const reCallAgent = async (opts: { messages?: ModelMessage[], tools?: Record<string, any>, languageModel: LanguageModel }): Promise<AgentResult> => {
|
||||||
|
const { messages = [], tools = {}, languageModel } = opts;
|
||||||
|
const result = await generateText({
|
||||||
|
model: languageModel,
|
||||||
|
messages,
|
||||||
|
tools,
|
||||||
|
});
|
||||||
|
const step = result.steps[0]!;
|
||||||
|
if (step.finishReason === 'tool-calls') {
|
||||||
|
messages.push(...result.response.messages);
|
||||||
|
return reCallAgent({ messages, tools, languageModel });
|
||||||
|
}
|
||||||
|
return { result, messages };
|
||||||
|
}
|
||||||
|
export const runAgent = async (opts: { app: QueryRouterServer | App, messages?: ModelMessage[], routes?: Route[], query?: string, languageModel: LanguageModel, token: string }) => {
|
||||||
|
const { app, languageModel } = opts;
|
||||||
|
let messages = opts.messages || [];
|
||||||
|
|
||||||
|
let routes = opts?.routes || app.routes;
|
||||||
|
if (opts.query) {
|
||||||
|
routes = filter(routes, opts.query);
|
||||||
|
};
|
||||||
|
const tools = await createTools({ app, token: opts.token });
|
||||||
|
return await reCallAgent({ messages, tools, languageModel });
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { api } from '@kevisual/convex';
|
|
||||||
import { ConvexClient, AuthTokenFetcher } from "convex/browser";
|
|
||||||
const url = process.env["CONVEX_URL"]
|
|
||||||
const convexClient = new ConvexClient(url!);
|
|
||||||
|
|
||||||
const token = process.env["KEVISUAL_CONVEX_TOKEN"]
|
|
||||||
const authTokenFetcher: AuthTokenFetcher = async ({ forceRefreshToken }: { forceRefreshToken: boolean }) => {
|
|
||||||
console.log("AuthTokenFetcher called, forceRefreshToken:", forceRefreshToken);
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
convexClient.setAuth(authTokenFetcher, (isAuthenticated) => {
|
|
||||||
console.log("Auth isAuthenticated:", isAuthenticated);
|
|
||||||
});
|
|
||||||
|
|
||||||
export { convexClient }
|
|
||||||
export const convexApi = api;
|
|
||||||
@@ -109,7 +109,7 @@ export const pipeProxyReq = async (req: http.IncomingMessage, proxyReq: http.Cli
|
|||||||
const bunRequest = req.bun.request;
|
const bunRequest = req.bun.request;
|
||||||
const contentType = req.headers['content-type'] || '';
|
const contentType = req.headers['content-type'] || '';
|
||||||
if (contentType.includes('multipart/form-data')) {
|
if (contentType.includes('multipart/form-data')) {
|
||||||
console.log('Processing multipart/form-data');
|
// console.log('Processing multipart/form-data');
|
||||||
const arrayBuffer = await bunRequest.arrayBuffer();
|
const arrayBuffer = await bunRequest.arrayBuffer();
|
||||||
|
|
||||||
// 设置请求头(在写入数据之前)
|
// 设置请求头(在写入数据之前)
|
||||||
@@ -123,7 +123,6 @@ export const pipeProxyReq = async (req: http.IncomingMessage, proxyReq: http.Cli
|
|||||||
proxyReq.end();
|
proxyReq.end();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('Bun pipeProxyReq content-type', contentType);
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const bodyString = req.body;
|
const bodyString = req.body;
|
||||||
bodyString && proxyReq.write(bodyString);
|
bodyString && proxyReq.write(bodyString);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { convex, convexApi } from '@/app.ts';
|
|
||||||
import { User } from '@/models/user.ts';
|
import { User } from '@/models/user.ts';
|
||||||
import { omit } from 'es-toolkit';
|
import { omit } from 'es-toolkit';
|
||||||
import { IncomingMessage, ServerResponse } from 'http';
|
import { IncomingMessage, ServerResponse } from 'http';
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ import { router } from './router.ts';
|
|||||||
import { handleRequest as PageProxy } from './page-proxy.ts';
|
import { handleRequest as PageProxy } from './page-proxy.ts';
|
||||||
|
|
||||||
import './routes/jwks.ts'
|
import './routes/jwks.ts'
|
||||||
|
import './routes/ai/openai.ts'
|
||||||
|
|
||||||
const simpleAppsPrefixs = [
|
const simpleAppsPrefixs = [
|
||||||
"/api/wxmsg",
|
"/api/wxmsg",
|
||||||
"/api/convex/",
|
"/api/convex/",
|
||||||
|
"/api/chat/completions"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
51
src/routes-simple/routes/ai/anthropic.ts
Normal file
51
src/routes-simple/routes/ai/anthropic.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import { router } from '@/app.ts';
|
||||||
|
import http from 'node:http';
|
||||||
|
import https from 'node:https';
|
||||||
|
import { pipeProxyReq, pipeProxyRes } from '@/modules/fm-manager/index.ts';
|
||||||
|
import { useKey } from '@kevisual/context';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: 由于目前没有找到合适的开源 Anthropic 兼容实现,暂时先把 /v1/messages 请求代理到配置的 OpenAI 兼容目标地址,等后续有了合适的 Anthropic 兼容实现再改回来
|
||||||
|
* 代理 /v1/messages 请求到配置的 OpenAI 兼容目标地址
|
||||||
|
* 配置项: config.OPENAI_BASE_URL,例如 http://localhost:11434/v1
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
router.all("/v1/messages", async (req, res) => {
|
||||||
|
const targetUrl = new URL('https://api.cnb.cool/kevisual/kevisual/-/ai/chat/messages');
|
||||||
|
const token = useKey('CNB_API_KEY');
|
||||||
|
// 收集并转发请求头(排除 host 和 authorization,用自己的 token 替换)
|
||||||
|
const headers: Record<string, any> = {};
|
||||||
|
for (const [key, value] of Object.entries(req.headers)) {
|
||||||
|
if (key.toLowerCase() !== 'host' && key.toLowerCase() !== 'authorization') {
|
||||||
|
headers[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (token) {
|
||||||
|
headers['authorization'] = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isHttps = targetUrl.protocol === 'https:';
|
||||||
|
const protocol = isHttps ? https : http;
|
||||||
|
|
||||||
|
const options: http.RequestOptions = {
|
||||||
|
hostname: targetUrl.hostname,
|
||||||
|
port: targetUrl.port || (isHttps ? 443 : 80),
|
||||||
|
path: targetUrl.pathname + (targetUrl.search || ''),
|
||||||
|
method: req.method,
|
||||||
|
headers,
|
||||||
|
...(isHttps ? { rejectUnauthorized: false } : {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const proxyReq = protocol.request(options, (proxyRes) => {
|
||||||
|
res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
|
||||||
|
pipeProxyRes(proxyRes, res);
|
||||||
|
});
|
||||||
|
|
||||||
|
proxyReq.on('error', (err) => {
|
||||||
|
console.error('[anthropic proxy] error:', err.message);
|
||||||
|
res.writeHead(502, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ error: err.message }));
|
||||||
|
});
|
||||||
|
|
||||||
|
pipeProxyReq(req, proxyReq, res);
|
||||||
|
});
|
||||||
50
src/routes-simple/routes/ai/openai.ts
Normal file
50
src/routes-simple/routes/ai/openai.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { router } from '@/app.ts';
|
||||||
|
import http from 'node:http';
|
||||||
|
import https from 'node:https';
|
||||||
|
import { pipeProxyReq, pipeProxyRes } from '@/modules/fm-manager/index.ts';
|
||||||
|
import { useKey } from '@kevisual/context';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 代理 /api/chat/completions 请求到配置的 OpenAI 兼容目标地址
|
||||||
|
* 配置项: config.OPENAI_BASE_URL,例如 http://localhost:11434/v1
|
||||||
|
*/
|
||||||
|
router.all("/api/chat/completions", async (req, res) => {
|
||||||
|
const targetUrl = new URL('https://api.cnb.cool/kevisual/kevisual/-/ai/chat/completions');
|
||||||
|
|
||||||
|
const token = useKey('CNB_API_KEY');
|
||||||
|
// 收集并转发请求头(排除 host 和 authorization,用自己的 token 替换)
|
||||||
|
const headers: Record<string, any> = {};
|
||||||
|
for (const [key, value] of Object.entries(req.headers)) {
|
||||||
|
if (key.toLowerCase() !== 'host' && key.toLowerCase() !== 'authorization') {
|
||||||
|
headers[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (token) {
|
||||||
|
headers['authorization'] = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isHttps = targetUrl.protocol === 'https:';
|
||||||
|
const protocol = isHttps ? https : http;
|
||||||
|
|
||||||
|
const options: http.RequestOptions = {
|
||||||
|
hostname: targetUrl.hostname,
|
||||||
|
port: targetUrl.port || (isHttps ? 443 : 80),
|
||||||
|
path: targetUrl.pathname + (targetUrl.search || ''),
|
||||||
|
method: req.method,
|
||||||
|
headers,
|
||||||
|
...(isHttps ? { rejectUnauthorized: false } : {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const proxyReq = protocol.request(options, (proxyRes) => {
|
||||||
|
res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
|
||||||
|
pipeProxyRes(proxyRes, res);
|
||||||
|
});
|
||||||
|
|
||||||
|
proxyReq.on('error', (err) => {
|
||||||
|
console.error('[openai proxy] error:', err.message);
|
||||||
|
res.writeHead(502, { 'Content-Type': 'application/json' });
|
||||||
|
res.end(JSON.stringify({ error: err.message }));
|
||||||
|
});
|
||||||
|
|
||||||
|
pipeProxyReq(req, proxyReq, res);
|
||||||
|
});
|
||||||
44
src/routes/flowme-life/chat.ts
Normal file
44
src/routes/flowme-life/chat.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
|
||||||
|
import { schema, app, cnb, models } from '@/app.ts'
|
||||||
|
import z from 'zod';
|
||||||
|
import { runAgent } from '@kevisual/ai/agent'
|
||||||
|
|
||||||
|
app.route({
|
||||||
|
path: 'flowme-life',
|
||||||
|
key: 'chat',
|
||||||
|
description: `聊天接口, 对自己的数据进行操作,参数是 question或messages,question是用户的提问,messages是对话消息列表,优先级高于 question`,
|
||||||
|
middleware: ['auth']
|
||||||
|
, metadata: {
|
||||||
|
args: {
|
||||||
|
question: z.string().describe('用户的提问'),
|
||||||
|
messages: z.any().optional().describe('对话消息列表,优先级高于 question'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
const question = ctx.query.question || '';
|
||||||
|
const _messages = ctx.query.messages;
|
||||||
|
const token = ctx.query.token || '';
|
||||||
|
if (!question && !_messages) {
|
||||||
|
ctx.throw(400, '缺少参数 question 或 messages');
|
||||||
|
}
|
||||||
|
const routes = ctx.app.getList().filter(r => r.path.startsWith('flowme-life') && r.key !== 'chat');
|
||||||
|
const messages = _messages || [
|
||||||
|
{
|
||||||
|
"role": "system" as const,
|
||||||
|
"content": `你是我的智能助手,协助我操作我的数据, 请根据我的提问选择合适的接口进行调用。`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user" as const,
|
||||||
|
"content": question
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const res = await runAgent({
|
||||||
|
app: app,
|
||||||
|
messages: messages,
|
||||||
|
languageModel: cnb(models['auto']),
|
||||||
|
// query: 'WHERE path LIKE 'flowme-life%',
|
||||||
|
routes,
|
||||||
|
token,
|
||||||
|
});
|
||||||
|
ctx.body = res
|
||||||
|
}).addTo(app);
|
||||||
@@ -1 +1,3 @@
|
|||||||
import './list.ts'
|
import './list.ts'
|
||||||
|
import './today.ts'
|
||||||
|
import './chat.ts'
|
||||||
38
src/routes/flowme-life/life.services.ts
Normal file
38
src/routes/flowme-life/life.services.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { eq } from 'drizzle-orm';
|
||||||
|
import { schema, db } from '@/app.ts';
|
||||||
|
|
||||||
|
export type LifeItem = typeof schema.life.$inferSelect;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 id 获取 life 记录
|
||||||
|
*/
|
||||||
|
export async function getLifeItem(id: string): Promise<{ code: number; data?: LifeItem; message?: string }> {
|
||||||
|
try {
|
||||||
|
const result = await db.select().from(schema.life).where(eq(schema.life.id, id)).limit(1);
|
||||||
|
if (result.length === 0) {
|
||||||
|
return { code: 404, message: `记录 ${id} 不存在` };
|
||||||
|
}
|
||||||
|
return { code: 200, data: result[0] };
|
||||||
|
} catch (e) {
|
||||||
|
return { code: 500, message: `获取记录 ${id} 失败: ${e?.message || e}` };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新 life 记录的 effectiveAt(下次执行时间)
|
||||||
|
*/
|
||||||
|
export async function updateLifeEffectiveAt(id: string, effectiveAt: string | Date): Promise<{ code: number; data?: LifeItem; message?: string }> {
|
||||||
|
try {
|
||||||
|
const result = await db
|
||||||
|
.update(schema.life)
|
||||||
|
.set({ effectiveAt: new Date(effectiveAt) })
|
||||||
|
.where(eq(schema.life.id, id))
|
||||||
|
.returning();
|
||||||
|
if (result.length === 0) {
|
||||||
|
return { code: 404, message: `记录 ${id} 不存在` };
|
||||||
|
}
|
||||||
|
return { code: 200, data: result[0] };
|
||||||
|
} catch (e) {
|
||||||
|
return { code: 500, message: `更新记录 ${id} 失败: ${e?.message || e}` };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { desc, eq, count, or, like, and } from 'drizzle-orm';
|
import { desc, eq, count, or, like, and } from 'drizzle-orm';
|
||||||
import { schema, app, db } from '@/app.ts'
|
import { schema, app, db } from '@/app.ts'
|
||||||
import z from 'zod';
|
import z from 'zod';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
app.route({
|
app.route({
|
||||||
path: 'flowme-life',
|
path: 'flowme-life',
|
||||||
key: 'list',
|
key: 'list',
|
||||||
@@ -72,7 +73,7 @@ app.route({
|
|||||||
link: z.string().describe('链接').optional(),
|
link: z.string().describe('链接').optional(),
|
||||||
data: z.record(z.string(), z.any()).describe('数据').optional(),
|
data: z.record(z.string(), z.any()).describe('数据').optional(),
|
||||||
effectiveAt: z.string().describe('生效日期').optional(),
|
effectiveAt: z.string().describe('生效日期').optional(),
|
||||||
type: z.string().describe('类型').optional(),
|
type: z.string().describe('类型: 智能, 每年农历, 备忘, 归档等.默认智能').optional(),
|
||||||
prompt: z.string().describe('提示词').optional(),
|
prompt: z.string().describe('提示词').optional(),
|
||||||
taskType: z.string().describe('任务类型').optional(),
|
taskType: z.string().describe('任务类型').optional(),
|
||||||
taskResult: z.record(z.string(), z.any()).describe('任务结果').optional(),
|
taskResult: z.record(z.string(), z.any()).describe('任务结果').optional(),
|
||||||
@@ -82,6 +83,11 @@ app.route({
|
|||||||
}).define(async (ctx) => {
|
}).define(async (ctx) => {
|
||||||
const { uid, updatedAt, createdAt, ...rest } = ctx.query.data || {};
|
const { uid, updatedAt, createdAt, ...rest } = ctx.query.data || {};
|
||||||
const tokenUser = ctx.state.tokenUser;
|
const tokenUser = ctx.state.tokenUser;
|
||||||
|
if (rest.effectiveAt && isNaN(Date.parse(rest.effectiveAt))) {
|
||||||
|
rest.effectiveAt = null;
|
||||||
|
} else if (rest.effectiveAt) {
|
||||||
|
rest.effectiveAt = dayjs(rest.effectiveAt).toISOString();
|
||||||
|
}
|
||||||
const lifeItem = await db.insert(schema.life).values({
|
const lifeItem = await db.insert(schema.life).values({
|
||||||
title: rest.title || '',
|
title: rest.title || '',
|
||||||
summary: rest.summary || '',
|
summary: rest.summary || '',
|
||||||
@@ -90,7 +96,7 @@ app.route({
|
|||||||
link: rest.link || '',
|
link: rest.link || '',
|
||||||
data: rest.data || {},
|
data: rest.data || {},
|
||||||
effectiveAt: rest.effectiveAt || '',
|
effectiveAt: rest.effectiveAt || '',
|
||||||
type: rest.type || '',
|
type: rest.type || '智能',
|
||||||
prompt: rest.prompt || '',
|
prompt: rest.prompt || '',
|
||||||
taskType: rest.taskType || '',
|
taskType: rest.taskType || '',
|
||||||
taskResult: rest.taskResult || {},
|
taskResult: rest.taskResult || {},
|
||||||
@@ -103,7 +109,7 @@ app.route({
|
|||||||
path: 'flowme-life',
|
path: 'flowme-life',
|
||||||
key: 'update',
|
key: 'update',
|
||||||
middleware: ['auth'],
|
middleware: ['auth'],
|
||||||
description: '更新一个 flowme-life',
|
description: '更新一个 flowme-life 的数据',
|
||||||
metadata: {
|
metadata: {
|
||||||
args: {
|
args: {
|
||||||
data: z.object({
|
data: z.object({
|
||||||
@@ -135,6 +141,11 @@ app.route({
|
|||||||
if (existing[0].uid !== tokenUser.id) {
|
if (existing[0].uid !== tokenUser.id) {
|
||||||
ctx.throw(403, '没有权限更新该 flowme-life');
|
ctx.throw(403, '没有权限更新该 flowme-life');
|
||||||
}
|
}
|
||||||
|
if (rest.effectiveAt && isNaN(Date.parse(rest.effectiveAt))) {
|
||||||
|
rest.effectiveAt = null;
|
||||||
|
} else if (rest.effectiveAt) {
|
||||||
|
rest.effectiveAt = dayjs(rest.effectiveAt).toISOString();
|
||||||
|
}
|
||||||
const lifeItem = await db.update(schema.life).set({
|
const lifeItem = await db.update(schema.life).set({
|
||||||
title: rest.title,
|
title: rest.title,
|
||||||
summary: rest.summary,
|
summary: rest.summary,
|
||||||
@@ -155,17 +166,15 @@ app.route({
|
|||||||
path: 'flowme-life',
|
path: 'flowme-life',
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
middleware: ['auth'],
|
middleware: ['auth'],
|
||||||
description: '删除 flowme-life',
|
description: '删除单个 flowme-life, 参数: id 必填',
|
||||||
metadata: {
|
metadata: {
|
||||||
args: {
|
args: {
|
||||||
data: z.object({
|
id: z.string().describe('ID'),
|
||||||
id: z.string().describe('ID'),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).define(async (ctx) => {
|
}).define(async (ctx) => {
|
||||||
const tokenUser = ctx.state.tokenUser;
|
const tokenUser = ctx.state.tokenUser;
|
||||||
const { id } = ctx.query.data || {};
|
const { id } = ctx.query || {};
|
||||||
if (!id) {
|
if (!id) {
|
||||||
ctx.throw(400, 'id 参数缺失');
|
ctx.throw(400, 'id 参数缺失');
|
||||||
}
|
}
|
||||||
@@ -184,10 +193,15 @@ app.route({
|
|||||||
path: 'flowme-life',
|
path: 'flowme-life',
|
||||||
key: 'get',
|
key: 'get',
|
||||||
middleware: ['auth'],
|
middleware: ['auth'],
|
||||||
description: '获取单个 flowme-life, 参数: data.id 必填',
|
description: '获取单个 flowme-life, 参数: id 必填',
|
||||||
|
metadata: {
|
||||||
|
args: {
|
||||||
|
id: z.string().describe('ID'),
|
||||||
|
}
|
||||||
|
}
|
||||||
}).define(async (ctx) => {
|
}).define(async (ctx) => {
|
||||||
const tokenUser = ctx.state.tokenUser;
|
const tokenUser = ctx.state.tokenUser;
|
||||||
const { id } = ctx.query.data || {};
|
const { id } = ctx.query || {};
|
||||||
if (!id) {
|
if (!id) {
|
||||||
ctx.throw(400, 'id 参数缺失');
|
ctx.throw(400, 'id 参数缺失');
|
||||||
}
|
}
|
||||||
|
|||||||
174
src/routes/flowme-life/today.ts
Normal file
174
src/routes/flowme-life/today.ts
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import { desc, eq, count, like, and, lt } from 'drizzle-orm';
|
||||||
|
import { schema, app, db } from '@/app.ts'
|
||||||
|
import z from 'zod';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { useContextKey } from '@kevisual/context';
|
||||||
|
import { createLunarDate, toGregorian } from 'lunar';
|
||||||
|
import { getLifeItem, updateLifeEffectiveAt } from './life.services.ts';
|
||||||
|
app.route({
|
||||||
|
path: 'flowme-life',
|
||||||
|
key: 'today',
|
||||||
|
description: `获取今天需要做的事情列表`,
|
||||||
|
middleware: ['auth'],
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
const tokenUser = ctx.state.tokenUser;
|
||||||
|
const uid = tokenUser.id;
|
||||||
|
|
||||||
|
const tomorrow = dayjs().add(1, 'day').startOf('day').toDate();
|
||||||
|
|
||||||
|
let whereCondition = eq(schema.life.uid, uid);
|
||||||
|
whereCondition = and(
|
||||||
|
eq(schema.life.uid, uid),
|
||||||
|
eq(schema.life.taskType, '运行中'),
|
||||||
|
lt(schema.life.effectiveAt, tomorrow)
|
||||||
|
);
|
||||||
|
|
||||||
|
const list = await db.select()
|
||||||
|
.from(schema.life)
|
||||||
|
.where(whereCondition)
|
||||||
|
.orderBy(desc(schema.life.effectiveAt));
|
||||||
|
|
||||||
|
console.log('today res', list.map(i => i['title']));
|
||||||
|
if (list.length > 0) {
|
||||||
|
ctx.body = {
|
||||||
|
list,
|
||||||
|
content: list.map(item => {
|
||||||
|
return `任务id:[${item['id']}]\n标题: ${item['title']}。\n启动时间: ${dayjs(item['effectiveAt']).format('YYYY-MM-DD HH:mm:ss')}。标签: ${item['tags'] || '无'} \n总结: ${item['summary'] || '无'}`;
|
||||||
|
}).join('\n')
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
ctx.body = {
|
||||||
|
list,
|
||||||
|
content: '今天没有需要做的事情了,休息一下吧'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).addTo(app);
|
||||||
|
|
||||||
|
|
||||||
|
app.route({
|
||||||
|
path: 'flowme-life',
|
||||||
|
key: 'done',
|
||||||
|
description: `完成某件事情,然后判断下一次运行时间。参数是id(string),数据类型是string。如果多个存在,则是ids的string数组`,
|
||||||
|
middleware: ['auth'],
|
||||||
|
metadata: {
|
||||||
|
args: {
|
||||||
|
id: z.string().optional().describe('记录id'),
|
||||||
|
ids: z.array(z.string()).optional().describe('记录id数组'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).define(async (ctx) => {
|
||||||
|
const id = ctx.query.id;
|
||||||
|
const ids: string[] = ctx.query.ids || [];
|
||||||
|
if (!id && ids.length === 0) {
|
||||||
|
ctx.throw(400, '缺少参数 id');
|
||||||
|
}
|
||||||
|
if (ids.length === 0 && id) {
|
||||||
|
ids.push(String(id));
|
||||||
|
}
|
||||||
|
console.log('id', id, ids);
|
||||||
|
const messages = [];
|
||||||
|
const changeItem = async (id: string) => {
|
||||||
|
// 获取记录详情
|
||||||
|
const recordRes = await getLifeItem(id);
|
||||||
|
if (recordRes.code !== 200) {
|
||||||
|
messages.push({
|
||||||
|
id,
|
||||||
|
content: `获取记录 ${id} 详情失败`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const record = recordRes.data;
|
||||||
|
|
||||||
|
// 检查启动时间是否大于今天
|
||||||
|
const startTime = record.effectiveAt;
|
||||||
|
const today = dayjs().startOf('day');
|
||||||
|
const startDate = dayjs(startTime).startOf('day');
|
||||||
|
|
||||||
|
if (startDate.isAfter(today)) {
|
||||||
|
messages.push({
|
||||||
|
id,
|
||||||
|
content: `记录 ${id} 的启动时间是 ${dayjs(startTime).format('YYYY-MM-DD HH:mm:ss')},还没到今天呢,到时候再做吧`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 计算下一次运行时间
|
||||||
|
// 1. 知道当前时间
|
||||||
|
// 2. 知道任务类型,如果是每日,则加一天;如果是每周,则加七天;如果是每月,则加一个月,如果是每年农历,需要转为新的,如果是其他,需要智能判断
|
||||||
|
// 3. 更新记录
|
||||||
|
const strTime = (time: string | Date) => {
|
||||||
|
return dayjs(time).format('YYYY-MM-DD HH:mm:ss');
|
||||||
|
}
|
||||||
|
const currentTime = strTime(new Date().toISOString());
|
||||||
|
const title = record.title || '无标题';
|
||||||
|
const isLuar = record.type?.includes?.('农历') || title.includes('农历');
|
||||||
|
let summay = record.summary || '无';
|
||||||
|
if (summay.length > 200) {
|
||||||
|
summay = summay.substring(0, 200) + '...';
|
||||||
|
}
|
||||||
|
const prompt = record.prompt || '';
|
||||||
|
const type = record.type || '';
|
||||||
|
const content = `上一次执行的时间是${strTime(startTime)},当前时间是${currentTime},请帮我计算下一次的运行时间,如果时间不存在,默认在8点启动。
|
||||||
|
${prompt ? `这是我给你的提示词,帮你更好地理解我的需求:${prompt}` : ''}
|
||||||
|
|
||||||
|
相关资料是
|
||||||
|
任务:${record.title}
|
||||||
|
总结:${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.title}",AI 返回的时间格式无效,无法格式化,返回内容是:${ai.responseText}`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
messages.push({
|
||||||
|
id,
|
||||||
|
content: `记录 ${id} 的任务 "${record.title}",AI 返回结果解析失败,返回内容是:${ai.responseText}`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const update = await updateLifeEffectiveAt(id, nextTime);
|
||||||
|
if (update.code !== 200) {
|
||||||
|
messages.push({
|
||||||
|
id,
|
||||||
|
content: `记录 ${id} 的任务 "${record.title}",更新记录失败`,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const msg = {
|
||||||
|
id,
|
||||||
|
nextTime,
|
||||||
|
showCNTime: dayjs(nextTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
content: `任务 "${record.title}" 已标记为完成。下一次运行时间是 ${dayjs(nextTime).format('YYYY-MM-DD HH:mm:ss')}`
|
||||||
|
};
|
||||||
|
messages.push(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const _id of ids) {
|
||||||
|
await changeItem(String(_id));
|
||||||
|
}
|
||||||
|
ctx.body = {
|
||||||
|
content: messages.map(m => m.content).join('\n'),
|
||||||
|
list: messages
|
||||||
|
};
|
||||||
|
|
||||||
|
}).addTo(app);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CNB } from '@kevisual/cnb'
|
import { CNB } from '@kevisual/cnb/src/index.ts'
|
||||||
import { UserModel } from '../../../auth/index.ts';
|
import { UserModel } from '../../../auth/index.ts';
|
||||||
import { CustomError } from '@kevisual/router';
|
import { CustomError } from '@kevisual/router';
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useConfig, useContextKey } from '@kevisual/context';
|
|||||||
import { Query } from '@kevisual/query';
|
import { Query } from '@kevisual/query';
|
||||||
import util from 'node:util';
|
import util from 'node:util';
|
||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
|
import { QueryLoginNode } from '@kevisual/api/login-node'
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
export {
|
export {
|
||||||
app,
|
app,
|
||||||
@@ -12,7 +12,6 @@ export {
|
|||||||
}
|
}
|
||||||
export const config = useConfig();
|
export const config = useConfig();
|
||||||
|
|
||||||
export const token = config.KEVISUAL_TOKEN || '';
|
|
||||||
export const cnbToken = config.CNB_TOKEN || '';
|
export const cnbToken = config.CNB_TOKEN || '';
|
||||||
|
|
||||||
export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
@@ -37,4 +36,10 @@ export const exit = (code = 0) => {
|
|||||||
|
|
||||||
export const query = new Query({
|
export const query = new Query({
|
||||||
url: 'https://kevisual.cn/api/router'
|
url: 'https://kevisual.cn/api/router'
|
||||||
|
// url: 'https://kevisual.xiongxiao.me/api/router'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const queryLogin = new QueryLoginNode({ query });
|
||||||
|
|
||||||
|
export const token = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZC1rZXktMSJ9.eyJzdWIiOiJ1c2VyOjBlNzAwZGM4LTkwZGQtNDFiNy05MWRkLTMzNmVhNTFkZTNkMiIsIm5hbWUiOiJyb290IiwiZXhwIjoxNzczMjM1NzM0LCJpc3MiOiJodHRwczovL2NvbnZleC5rZXZpc3VhbC5jbiIsImlhdCI6MTc3MzE0OTMzNCwiYXVkIjoiY29udmV4LWFwcCJ9.Zj3bepCCKnVGgXoOnmmdkM-2u0qiT2V-bLhI-0C1a-YX9-ZlcQP2W_1rYN_D2kaaL5BPduvKhoY1hJzM5UwxRYLc-tYr2oBU4fwEyHc3bn-M8p0spX2-Tbie7CN_WbBszZ9KGePNKCveWmx5rCc14YhfUiIvczviU7WP728yFsaHJ29sVu3FJqd3ezMSkdwwPtlwCBtOhuE3nyqPdWP6nRZHkSSbAZDu5jUb_-3TqGjI2cHVZwChfcIVNwdjTeQrj2KMMQ2NdXBim01PZcolr3wqNwpSsm4bN4IVyB5RmwCw7gzHyYSOSZ1bnE8kc53M0KANDSLBFynKUXzNQJ-Wmg'
|
||||||
|
// console.log('test config', token);
|
||||||
36
src/test/flowme.ts
Normal file
36
src/test/flowme.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { queryLogin, app, token, showMore } from './common.ts'
|
||||||
|
|
||||||
|
|
||||||
|
// const rest = await app.run({
|
||||||
|
// path: 'flowme-life',
|
||||||
|
// key: 'today',
|
||||||
|
// // @ts-ignore
|
||||||
|
// token: token,
|
||||||
|
// })
|
||||||
|
|
||||||
|
// console.log('flowme-life today', rest)
|
||||||
|
|
||||||
|
const updateId = '8c63cb7a-ff6d-463b-b210-6311ee12ed46'
|
||||||
|
|
||||||
|
// const updateRest = await app.run({
|
||||||
|
// path: 'flowme-life',
|
||||||
|
// key: 'done',
|
||||||
|
// // @ts-ignore
|
||||||
|
// token: token,
|
||||||
|
// payload: {
|
||||||
|
// id: updateId,
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// console.log('flowme-life done', updateRest)
|
||||||
|
|
||||||
|
const chatRes = await app.run({
|
||||||
|
path: 'flowme-life',
|
||||||
|
key: 'chat',
|
||||||
|
// @ts-ignore
|
||||||
|
token: token,
|
||||||
|
payload: {
|
||||||
|
// question: '帮我查询一下今天的待办事项'
|
||||||
|
// question: '帮我查询一下今天的待办事项, 然后帮我把键盘充电的待办标记为完成',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
console.log('flowme-life chat', showMore(chatRes))
|
||||||
Reference in New Issue
Block a user