From ce7cd03cb3f920ab63009d10e85e4eb07b614efa Mon Sep 17 00:00:00 2001 From: abearxiong Date: Sat, 21 Jun 2025 16:31:00 +0800 Subject: [PATCH] update --- .gitignore | 4 +- package.json | 7 +- packages/xhs/package.json | 2 +- packages/xhs/src/routes/mentions/mention.ts | 32 +++++-- pnpm-lock.yaml | 43 +++++++--- src/agent/agent.ts | 3 + src/agent/ai.ts | 16 ++++ src/agent/analyze/content.ts | 95 +++++++++++++++++++++ src/agent/fix/prompt.ts | 64 ++++++++++++++ src/agent/index.ts | 7 ++ src/agent/logger.ts | 3 + src/agent/test/analyze.ts | 19 +++++ src/agent/test/common.ts | 0 src/agent/test/prompt-fix.ts | 18 ++++ src/agent/xhs.ts | 53 ++++++++++++ src/modules/notify.ts | 3 + src/task/routes/mention.ts | 25 ++++-- turbo.json | 22 +++++ 18 files changed, 387 insertions(+), 29 deletions(-) create mode 100644 src/agent/agent.ts create mode 100644 src/agent/ai.ts create mode 100644 src/agent/analyze/content.ts create mode 100644 src/agent/fix/prompt.ts create mode 100644 src/agent/index.ts create mode 100644 src/agent/logger.ts create mode 100644 src/agent/test/analyze.ts create mode 100644 src/agent/test/common.ts create mode 100644 src/agent/test/prompt-fix.ts create mode 100644 src/agent/xhs.ts create mode 100644 src/modules/notify.ts create mode 100644 turbo.json diff --git a/.gitignore b/.gitignore index d4f23be..0c14f8d 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,6 @@ cache-file logs .env* -!.env.example \ No newline at end of file +!.env.example + +.turbo \ No newline at end of file diff --git a/package.json b/package.json index d1c33cc..138e07e 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "build": "rimraf dist && bun run bun.config.mjs", "test": "tsx test/**/*.ts", "clean": "rm -rf dist", + "turbo:build": "turbo run build", "pub": "npm run build && envision pack -p -u", "cmd": "tsx cmd/index.ts " }, @@ -34,7 +35,7 @@ "access": "public" }, "dependencies": { - "@kevisual/ai": "^0.0.6", + "@kevisual/ai": "^0.0.8", "@kevisual/code-center-module": "0.0.23", "@kevisual/context": "^0.0.3", "@kevisual/router": "0.0.23", @@ -47,6 +48,7 @@ }, "devDependencies": { "@kevisual/app-assistant": "workspace:*", + "@kevisual/logger": "^0.0.4", "@kevisual/social-prompts": "workspace:*", "@kevisual/types": "^0.0.10", "@kevisual/use-config": "^1.0.19", @@ -56,7 +58,7 @@ "@types/formidable": "^3.4.5", "@types/lodash-es": "^4.17.12", "@types/node": "^24.0.3", - "bullmq": "^5.54.3", + "bullmq": "^5.55.0", "commander": "^14.0.0", "concurrently": "^9.1.2", "cross-env": "^7.0.3", @@ -64,6 +66,7 @@ "inquire": "^0.4.8", "ioredis": "^5.6.1", "nodemon": "^3.1.10", + "openai": "^5.6.0", "pg": "^8.16.2", "rimraf": "^6.0.1", "sequelize": "^6.37.7", diff --git a/packages/xhs/package.json b/packages/xhs/package.json index 66616ce..a30ee14 100644 --- a/packages/xhs/package.json +++ b/packages/xhs/package.json @@ -6,7 +6,7 @@ "types": "app.d.ts", "scripts": { "build": "bun run bun.config.mjs", - "postbuild": "dts -i src/index.ts -o app.d.ts", + "postbuild2": "dts -i src/index.ts -o app.d.ts", "cmd": "tsx src/test/command.ts ", "dts": "dts -i src/index.js -o app.d.ts" }, diff --git a/packages/xhs/src/routes/mentions/mention.ts b/packages/xhs/src/routes/mentions/mention.ts index 93ecbfa..5166ed5 100644 --- a/packages/xhs/src/routes/mentions/mention.ts +++ b/packages/xhs/src/routes/mentions/mention.ts @@ -75,17 +75,31 @@ app .define(async (ctx) => { const { note_id, comment_id, content } = ctx.query; const client = xhsServices.getClient(); - const res = await client.postComment({ - note_id: note_id, - comment_id: comment_id, - content, - }); - if (res.code === 0) { - ctx.body = res.data; + // content 300个字内,超过cai fen + const textArr: string[] = []; + if (content.length > 300) { + const num = Math.ceil(content.length / 300); + for (let i = 0; i < num; i++) { + textArr.push(content.slice(i * 300, (i + 1) * 300)); + } } else { - console.log('添加评论失败', res.code); - ctx.throw(res.code, '添加评论失败'); + textArr.push(content); } + const resArr: any[] = []; + for (const text of textArr) { + const res = await client.postComment({ + note_id: note_id, + comment_id: comment_id, + content: text, + }); + if (res.code === 0) { + resArr.push(res.data); + } else { + console.log('添加评论失败', res.code); + ctx.throw(res.code, '添加评论失败'); + } + } + ctx.body = resArr; }) .addTo(app); app diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca27266..3128192 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@kevisual/ai': - specifier: ^0.0.6 - version: 0.0.6 + specifier: ^0.0.8 + version: 0.0.8 '@kevisual/code-center-module': specifier: 0.0.23 version: 0.0.23(dotenv@16.5.0) @@ -42,6 +42,9 @@ importers: '@kevisual/app-assistant': specifier: workspace:* version: link:packages/app-assistant + '@kevisual/logger': + specifier: ^0.0.4 + version: 0.0.4 '@kevisual/social-prompts': specifier: workspace:* version: link:packages/social-prompts @@ -67,8 +70,8 @@ importers: specifier: ^24.0.3 version: 24.0.3 bullmq: - specifier: ^5.54.3 - version: 5.54.3 + specifier: ^5.55.0 + version: 5.55.0 commander: specifier: ^14.0.0 version: 14.0.0 @@ -90,6 +93,9 @@ importers: nodemon: specifier: ^3.1.10 version: 3.1.10 + openai: + specifier: ^5.6.0 + version: 5.6.0(ws@8.18.1)(zod@3.25.67) pg: specifier: ^8.16.2 version: 8.16.2 @@ -215,8 +221,8 @@ packages: '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - '@kevisual/ai@0.0.6': - resolution: {integrity: sha512-khTCGsDMjGL9VN8ZB2HzEPUXcSTHu1fHXKBRmyojxy9gt+lMCx7JFgqLKiENkekOUwS2Xg6Fe5jwQDNJRwGwDA==} + '@kevisual/ai@0.0.8': + resolution: {integrity: sha512-MvK4U1iWf8hz7lj/+YBQV3qWRRDy42VH8fInKFVxjpEGPGaxfXOMP73C85T4Cf82OGU/fxOayiR0xLi2SyBTLw==} '@kevisual/auth@1.0.5': resolution: {integrity: sha512-GwsLj7unKXi7lmMiIIgdig4LwwLiDJnOy15HHZR5gMbyK6s5/uJiMY5RXPB2+onGzTNDqFo/hXjsD2wkerHPVg==} @@ -574,8 +580,8 @@ packages: bullmq@5.51.1: resolution: {integrity: sha512-JEZokH5Sb6p66HRjbfQjPNYuSilDRcB8UREmJzOBqTTaJFza8I92vsBF3J/zmtzd7KVv3dxhZyH9CYSLOJALRA==} - bullmq@5.54.3: - resolution: {integrity: sha512-MVK2pOkB3hvrIcubwI8dS4qWHJLNKakKPpgRBTw91sIpPZArmvZ4t2hvryyEaJXJbAS/JHd6pKYOUd+RGRkWQQ==} + bullmq@5.55.0: + resolution: {integrity: sha512-LKaQZroyXBYSQd/SNP9EcmCZgiZjIImtQHBlnupUvhX1GmmJfIXjn0bf8lek3bvajMUbvVf8FrYdFD0ajAuy0g==} bun-types@1.2.16: resolution: {integrity: sha512-ciXLrHV4PXax9vHvUrkvun9VPVGOVwbbbBF/Ev1cXz12lyEZMoJpIJABOfPcN9gDJRaiKF9MVbSygLg4NXu3/A==} @@ -1458,6 +1464,18 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + openai@5.6.0: + resolution: {integrity: sha512-jNH5z+hYAdOMZXyEt0yZ7246s+UZjg2AwFQqkAhZIPPjxNtHHO5mykOefau6FkOqj16aC94MOdJl/rZBcKj/cQ==} + hasBin: true + peerDependencies: + ws: ^8.18.0 + zod: ^3.23.8 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} @@ -2182,7 +2200,7 @@ snapshots: '@jridgewell/sourcemap-codec@1.5.0': {} - '@kevisual/ai@0.0.6': + '@kevisual/ai@0.0.8': dependencies: '@kevisual/logger': 0.0.4 @@ -2550,7 +2568,7 @@ snapshots: transitivePeerDependencies: - supports-color - bullmq@5.54.3: + bullmq@5.55.0: dependencies: cron-parser: 4.9.0 ioredis: 5.6.1 @@ -3629,6 +3647,11 @@ snapshots: dependencies: wrappy: 1.0.2 + openai@5.6.0(ws@8.18.1)(zod@3.25.67): + optionalDependencies: + ws: 8.18.1 + zod: 3.25.67 + own-keys@1.0.1: dependencies: get-intrinsic: 1.2.7 diff --git a/src/agent/agent.ts b/src/agent/agent.ts new file mode 100644 index 0000000..5e4fca7 --- /dev/null +++ b/src/agent/agent.ts @@ -0,0 +1,3 @@ +import { QueryRouterServer } from '@kevisual/router'; + +export const agent = new QueryRouterServer(); diff --git a/src/agent/ai.ts b/src/agent/ai.ts new file mode 100644 index 0000000..cb267c5 --- /dev/null +++ b/src/agent/ai.ts @@ -0,0 +1,16 @@ +import { SiliconFlowProvider } from '@kevisual/ai'; +import { config } from '../modules/config.ts'; + +export const ai = new SiliconFlowProvider({ + model: 'Qwen/Qwen3-32B', + // model: 'Pro/deepseek-ai/DeepSeek-R1',// 只有充值能用 + apiKey: config.SILICONFLOW_API_KEY, +}); + +ai.getUsageInfo() + .then((usage) => { + console.log('AI usage info:', usage); + }) + .catch((res) => { + console.error('Error fetching AI usage info:', res.status); + }); diff --git a/src/agent/analyze/content.ts b/src/agent/analyze/content.ts new file mode 100644 index 0000000..453888e --- /dev/null +++ b/src/agent/analyze/content.ts @@ -0,0 +1,95 @@ +import { agent } from '@/agent/agent.ts'; +import { ai } from '../ai.ts'; +import { logger } from '@/agent/logger.ts'; +const getJsonFromString = (str: string) => { + // 尝试从字符串中提取JSON对象 + try { + const jsonMatch = str.match(/```json\s*([\s\S]*?)\s*```/); + if (jsonMatch && jsonMatch[1]) { + return JSON.parse(jsonMatch[1]); + } + } catch (error) { + console.error('Error parsing JSON from string:', error); + } + return null; +}; +agent + .route({ + path: 'analyze', + key: 'content', + description: '分析文本内容,意图分析,判断是否需要获取上下文包函小红书的图片,视频,文本。', + }) + .define(async (ctx) => { + const text = ctx.query?.text || ''; + + let result = { + image: false, + video: false, + text: false, + comment: false, + }; + + const prompt = ` +请分析包函的内容,判断是否需要包含小红书的图片、视频或文本信息,返回一个JSON对象,包含三个布尔值:image(是否包含图片)、video(是否包含视频)、text(是否包含小红书文本),comment(是否是评论信息)。 +如果文本中提到图片或视频,请返回true,否则返回false。 + +文本示例: +1. "解答一下这个笔记。" , text为true +2. "分析一下这个图片。", image为true +3. "这个视频介绍的是什么。", video为true +4. "评价一下这个评论。", comment为true + +返回内容示例: +\`\`\`json +{ + "image": true, + "video": false, + "text": true, + "comment": false +} +\`\`\` +分析的文本的内容是: + +${text} + + `; + const now = Date.now(); + console.log('start'); + const res = await ai + .chat( + [ + { + role: 'user', + content: prompt, + }, + ], + { + // @ts-ignore + enable_thinking: false, + }, + ) + .catch((err) => { + console.log('AI service error:', err.status); + ctx.throw(500, 'AI service error: ' + err.status); + return err; + }); + + console.log('end', Date.now() - now, 'ms'); + const ans = res.choices[0]?.message?.content || ''; + if (!ans) { + logger.error('Empty response from AI:', res); + } + const json = getJsonFromString(ans); + if (!json) { + logger.error('Invalid JSON format in response:', ans); + ctx.throw(400, 'Invalid JSON format in response'); + } + result = { + image: json.image || false, + video: json.video || false, + text: json.text || false, + comment: json.comment || false, + }; + ctx.body = result; + }) + .addTo(agent); diff --git a/src/agent/fix/prompt.ts b/src/agent/fix/prompt.ts new file mode 100644 index 0000000..7603f24 --- /dev/null +++ b/src/agent/fix/prompt.ts @@ -0,0 +1,64 @@ +import { agent } from '@/agent/agent.ts'; +import { ai } from '../ai.ts'; +import { logger } from '@/agent/logger.ts'; + +const getTagContent = (text: string) => { + const match = text.match(/([\s\S]*?)<\/content>/); + return match ? match[1].trim() : text; +}; +agent + .route({ + path: 'fix', + key: 'xhs', + description: '对小红书的提示词进行修正和优化', + }) + .define(async (ctx) => { + const text = ctx.query?.text || ''; + const now = Date.now(); + console.log('start'); + const res = await ai + .chat( + [ + { + role: 'user', + content: ` +你是一个提示词优化的专家,请根据用户提供的提示词进行修正和优化,其中用户的提示词返回的要求如果没有或者不明确,请你都修正为要求返回的文本在500字以内,且内容是纯文本格式,不能是markdown模式,也不包含任何HTML标签或其他格式化内容。 + +只对提示词进行优化,并且不需要对内容进行分析或总结。 + +示例1. 用户提示词 +总结笔记 +优化后的提示词 +请总结一下这个笔记,要求返回的内容是纯文本格式,字数不超过500字。 +示例2. 用户提示词 +分析一下这个图片 +优化后的提示词 +请分析一下这个图片,要求返回的内容是纯文本格式,字数不超过500字。 + + +用户的提示词是 + +${text} + +`, + }, + ], + { + // @ts-ignore + enable_thinking: false, + }, + ) + .catch((err) => { + console.log('AI service error:', err.status); + ctx.throw(500, 'AI service error: ' + err.status); + return err; + }); + + console.log('end', Date.now() - now, 'ms'); + const ans = res.choices[0]?.message?.content || ''; + if (!ans) { + logger.error('Empty response from AI:', res); + } + ctx.body = getTagContent(ans) + }) + .addTo(agent); diff --git a/src/agent/index.ts b/src/agent/index.ts new file mode 100644 index 0000000..d03e659 --- /dev/null +++ b/src/agent/index.ts @@ -0,0 +1,7 @@ +import { agent } from './agent.ts'; +import './analyze/content.ts'; +import './fix/prompt.ts'; + +import './xhs.ts'; + +export { agent }; diff --git a/src/agent/logger.ts b/src/agent/logger.ts new file mode 100644 index 0000000..5dc6ede --- /dev/null +++ b/src/agent/logger.ts @@ -0,0 +1,3 @@ +import { Logger } from '@kevisual/logger'; + +export const logger = new Logger(); diff --git a/src/agent/test/analyze.ts b/src/agent/test/analyze.ts new file mode 100644 index 0000000..f8a3116 --- /dev/null +++ b/src/agent/test/analyze.ts @@ -0,0 +1,19 @@ +import { agent } from '../index.ts'; + +const main = async () => { + const text1 = '解答一下这个笔记。'; + const text2 = '分析一下这个图片'; + const text3 = '这个视频介绍的是什么'; + const text4 = '评价一下这个评论。'; + const text5 = '关于这个评论。'; + const text6 = '1+1='; + const res = await agent.call({ + path: 'analyze', + key: 'content', + payload: { + text: text6, + }, + }); + console.log('analyze content res', res.code, 'content', res.body); +}; +main(); diff --git a/src/agent/test/common.ts b/src/agent/test/common.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/agent/test/prompt-fix.ts b/src/agent/test/prompt-fix.ts new file mode 100644 index 0000000..99cccc4 --- /dev/null +++ b/src/agent/test/prompt-fix.ts @@ -0,0 +1,18 @@ +// 其中回复的要求是以纯文本,具体的内容在当中 +import { agent } from '../index.ts'; + +const main = async () => { + const text = '请总结一下这个笔记。'; + const text2 = '告诉我1+1的值'; + const text3 = 'html和css的大纲是什么?'; + const text4 = '1+1='; + const res = await agent.call({ + path: 'fix', + key: 'xhs', + payload: { + text: text4, + }, + }); + console.log('fix xhs res', res.code, 'content', res.body); +}; +main(); diff --git a/src/agent/xhs.ts b/src/agent/xhs.ts new file mode 100644 index 0000000..fdaa5e7 --- /dev/null +++ b/src/agent/xhs.ts @@ -0,0 +1,53 @@ +import { nanoid } from 'nanoid'; +import { agent } from './agent.ts'; +import { ai } from './ai.ts'; +/** + * 清除文本中的@信息 + * @param text + */ +const clearAtInfo = (text: string = '') => { + const newText = text.replace(/@[\u4e00-\u9fa5\w]+/g, '').replace(/#.*?#/g, ''); + return newText.trim(); +}; +agent + .route({ + path: 'xhs', + }) + .define(async (ctx) => { + const { text = '' } = ctx.query || {}; + const id = nanoid(); + const no_at_text = clearAtInfo(text); + const resFix = await agent.call({ + path: 'fix', + key: 'xhs', + payload: { + text: no_at_text, + }, + }); + if (resFix.code !== 200) { + ctx.throw(500, 'AI 小红书prompt优化错误: ' + resFix.message); + return; + } else { + console.log('小红书优化的文本', resFix.body); + } + const prompt_text = resFix.body || ''; + const res = await ai + .chat( + [ + { + role: 'user', + content: prompt_text, + }, + ], + { + // @ts-ignore + enable_thinking: false, + }, + ) + .catch((error) => { + ctx.throw(500, 'AI 服务错误: ' + error.status); + return error; + }); + ctx.body = res.choices?.[0]?.message?.content || ''; + }) + .addTo(agent); diff --git a/src/modules/notify.ts b/src/modules/notify.ts new file mode 100644 index 0000000..14d3aa2 --- /dev/null +++ b/src/modules/notify.ts @@ -0,0 +1,3 @@ +export const notify = () => { + // +}; diff --git a/src/task/routes/mention.ts b/src/task/routes/mention.ts index 86369cf..9d97bda 100644 --- a/src/task/routes/mention.ts +++ b/src/task/routes/mention.ts @@ -1,3 +1,4 @@ +import { agent } from '@/agent/index.ts'; import { taskApp, queue, xhsApp } from '../task.ts'; import { random, omit } from 'lodash-es'; import util from 'node:util'; @@ -10,6 +11,7 @@ taskApp .route({ path: 'task', key: 'getUnread', + description: '获取未读提及消息', }) .define(async (ctx) => { const res = await xhsApp.call({ @@ -50,6 +52,7 @@ taskApp .route({ path: 'task', key: 'getMention', + description: '获取提及消息', }) .define(async (ctx) => { const { unread_count } = ctx.query; @@ -61,11 +64,9 @@ taskApp num: unread_count, }, }); - console.log('mentionRes', mentionRes.body); if (mentionRes.code === 200) { let data = mentionRes.body || []; // data = data.map((item) => omit(item, 'mention')); - console.log('queryMention', util.inspect(data, { depth: 10 })); for (let i = 0; i < data.length; i++) { const item = data[i]; queue.add( @@ -86,7 +87,6 @@ taskApp }, }, ); - console.log('add mention task', item); await sleep(200); } } @@ -94,9 +94,7 @@ taskApp path: 'mention', key: 'postRead', }); - console.log('postRead', postRead.body); } - await sleep(1000); ctx.body = { job: unread_count, }; @@ -109,7 +107,7 @@ taskApp key: 'ai', }) .define(async (ctx) => { - const data = ctx.query.data; + const data = ctx.query.data; // 为提及的相关信息 const note_id = data.note_id; const xsec_token = data.xsec_token; const comment_id = data.comment.comment_id; @@ -119,6 +117,21 @@ taskApp content, comment_id, }; + const resAgent = await agent.call({ + path: 'xhs', + payload: { + text: content, + }, + }); + let responseText = ''; + let errorText = ''; + if (resAgent.code !== 200) { + errorText = '【调用错误】'; + responseText = `${resAgent.message}`; + } else { + responseText = resAgent.body; + } + postData.content = responseText; const res = await xhsApp.call({ path: 'mention', key: 'addComment', diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..2163d21 --- /dev/null +++ b/turbo.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://turbo.build/schema.json", + "tasks": { + "build": { + "dependsOn": [ + "^build" + ], + "outputs": [ + "dist/**" + ] + }, + "dev:lib": { + "persistent": true, + "cache": true + }, + "build:lib": { + "dependsOn": [ + "^build:lib" + ] + } + } +} \ No newline at end of file