update
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
.env*
|
||||
!.env*example
|
||||
17
package.json
Normal file
17
package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "batch-prompts",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@10.26.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@kevisual/ai": "^0.0.19"
|
||||
}
|
||||
}
|
||||
2195
pnpm-lock.yaml
generated
Normal file
2195
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
5
pnpm-workspace.yaml
Normal file
5
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
packages:
|
||||
- 'prompts'
|
||||
- 'web'
|
||||
- 'backend'
|
||||
- 'agent'
|
||||
13
prompts/.claude/skills/list/SKILL.md
Normal file
13
prompts/.claude/skills/list/SKILL.md
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
name: 列出前20个storage的文件名
|
||||
description: 读取storage存储的列表,并列出前20个文件名
|
||||
allowed-tools: Read, Bash
|
||||
---
|
||||
|
||||
执行技能时,在项目根目录运行以下命令:
|
||||
|
||||
```bash
|
||||
bun .claude/skills/list/scripts/list.ts
|
||||
```
|
||||
|
||||
脚本会读取 `storage` 目录下的文件,并输出前20个文件名。
|
||||
21
prompts/.claude/skills/list/scripts/list.ts
Normal file
21
prompts/.claude/skills/list/scripts/list.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
import { createStorage } from 'unstorage'
|
||||
import fsDriver from 'unstorage/drivers/fs'
|
||||
|
||||
const storage = createStorage({
|
||||
driver: fsDriver({ base: 'storage' }),
|
||||
})
|
||||
|
||||
async function listFiles() {
|
||||
const files = await storage.getKeys()
|
||||
const first20Files = files.slice(0, 20)
|
||||
console.log('前20个文件名:')
|
||||
first20Files.forEach((file) => {
|
||||
console.log(file)
|
||||
})
|
||||
}
|
||||
|
||||
listFiles().catch((err) => {
|
||||
console.error('Error listing files:', err)
|
||||
})
|
||||
27
prompts/.env.example
Normal file
27
prompts/.env.example
Normal file
@@ -0,0 +1,27 @@
|
||||
# AI Configuration
|
||||
KEVISUAL_NEW_API_KEY=your_api_key_here
|
||||
|
||||
# Redis
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
REDIS_DB=0
|
||||
|
||||
# POCKETBASE
|
||||
POCKETBASE_URL=https://root.pb.kevisual.cn
|
||||
POCKETBASE_TOKEN=your_pocketbase_token_here
|
||||
|
||||
# jimeng API
|
||||
JIMENG_API_KEY=your_jimeng_api_key
|
||||
JIMENG_API_URL=https://jimeng-api.kevisual.cn/v1
|
||||
JIMENG_TIMEOUT=300000
|
||||
|
||||
# S3
|
||||
S3_BUCKET=your_bucket_name
|
||||
S3_ACCESS_KEY_ID=your_access_key
|
||||
S3_ACCESS_KEY_SECRET=your_secret_key
|
||||
S3_REGION=cn-beijing
|
||||
S3_ENDPOINT=tos-cn-beijing.volces.com
|
||||
|
||||
## 飞书提醒
|
||||
FEISHU_NOTIFY_WEBHOOK_URL=your_feishu_webhook_url
|
||||
6
prompts/.gitignore
vendored
Normal file
6
prompts/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
|
||||
storage
|
||||
|
||||
.env
|
||||
!.env*example
|
||||
5002
prompts/data/prompts-01.json
Normal file
5002
prompts/data/prompts-01.json
Normal file
File diff suppressed because it is too large
Load Diff
16009
prompts/data/sentence-01-optimized.json
Normal file
16009
prompts/data/sentence-01-optimized.json
Normal file
File diff suppressed because it is too large
Load Diff
14010
prompts/data/sentence-01.json
Normal file
14010
prompts/data/sentence-01.json
Normal file
File diff suppressed because it is too large
Load Diff
11
prompts/docs/jimeng.md
Normal file
11
prompts/docs/jimeng.md
Normal file
@@ -0,0 +1,11 @@
|
||||
```sh
|
||||
curl -X POST https://jimeng-api.kevisual.cn/v1/images/generations \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer 4e962fc85078d5bfc02c9882bfe659eb" \
|
||||
-d '{
|
||||
"model": "jimeng-4.0",
|
||||
"prompt": "生成一个水墨山水画",
|
||||
"ratio": "1:1",
|
||||
"resolution": "2k"
|
||||
}'
|
||||
```
|
||||
19
prompts/ecosystem.config.js
Normal file
19
prompts/ecosystem.config.js
Normal file
@@ -0,0 +1,19 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'image-worker',
|
||||
script: './workers/image-worker.ts',
|
||||
interpreter: 'bun',
|
||||
instances: 1,
|
||||
autorestart: true,
|
||||
watch: false,
|
||||
max_memory_restart: '1G',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
},
|
||||
error_file: './logs/worker-error.log',
|
||||
out_file: './logs/worker-out.log',
|
||||
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
|
||||
},
|
||||
],
|
||||
};
|
||||
624
prompts/package-lock.json
generated
Normal file
624
prompts/package-lock.json
generated
Normal file
@@ -0,0 +1,624 @@
|
||||
{
|
||||
"name": "prompts",
|
||||
"version": "0.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "prompts",
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-toolkit": "^1.43.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kevisual/types": "^0.0.10",
|
||||
"tsx": "^4.21.0"
|
||||
}
|
||||
},
|
||||
"../node_modules/.pnpm/@kevisual+types@0.0.10/node_modules/@kevisual/types": {
|
||||
"version": "0.0.10",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"../node_modules/.pnpm/es-toolkit@1.43.0/node_modules/es-toolkit": {
|
||||
"version": "1.43.0",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"docs",
|
||||
"benchmarks"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@arethetypeswrong/cli": "^0.15.3",
|
||||
"@changesets/changelog-github": "^0.5.0",
|
||||
"@changesets/cli": "^2.27.1",
|
||||
"@eslint/js": "^9.9.0",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@rollup/plugin-typescript": "^12.1.0",
|
||||
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||
"@types/broken-link-checker": "^0",
|
||||
"@types/eslint": "^9",
|
||||
"@types/jscodeshift": "^0.12.0",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/node": "^22.7.4",
|
||||
"@types/tar": "^6.1.13",
|
||||
"@typescript-eslint/parser": "^8.26.1",
|
||||
"@vitest/coverage-istanbul": "^2.1.2",
|
||||
"@vitest/eslint-plugin": "^1.3.4",
|
||||
"@vue/compiler-sfc": "^3.5.10",
|
||||
"broken-link-checker": "^0.7.8",
|
||||
"eslint": "^9.22.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-no-for-of-array": "^0.0.1",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-vue": "^9.28.0",
|
||||
"execa": "^9.3.0",
|
||||
"globals": "^15.9.0",
|
||||
"happy-dom": "^16.7.3",
|
||||
"jscodeshift": "^17.0.0",
|
||||
"packlint": "^0.2.4",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-sort-re-exports": "^0.1.0",
|
||||
"rollup": "^4.19.0",
|
||||
"rollup-plugin-dts": "^6.1.1",
|
||||
"tar": "^6",
|
||||
"tslib": "^2.6.3",
|
||||
"tsx": "^4.19.0",
|
||||
"typescript": "^5.8.2",
|
||||
"typescript-eslint": "^8.6.0",
|
||||
"vercel": "^41.4.1",
|
||||
"vitest": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
|
||||
"integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
|
||||
"integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
|
||||
"integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
|
||||
"integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
|
||||
"integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
|
||||
"integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
|
||||
"integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
|
||||
"integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
|
||||
"integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
|
||||
"integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@kevisual/types": {
|
||||
"resolved": "../node_modules/.pnpm/@kevisual+types@0.0.10/node_modules/@kevisual/types",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/es-toolkit": {
|
||||
"resolved": "../node_modules/.pnpm/es-toolkit@1.43.0/node_modules/es-toolkit",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
|
||||
"integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.27.2",
|
||||
"@esbuild/android-arm": "0.27.2",
|
||||
"@esbuild/android-arm64": "0.27.2",
|
||||
"@esbuild/android-x64": "0.27.2",
|
||||
"@esbuild/darwin-arm64": "0.27.2",
|
||||
"@esbuild/darwin-x64": "0.27.2",
|
||||
"@esbuild/freebsd-arm64": "0.27.2",
|
||||
"@esbuild/freebsd-x64": "0.27.2",
|
||||
"@esbuild/linux-arm": "0.27.2",
|
||||
"@esbuild/linux-arm64": "0.27.2",
|
||||
"@esbuild/linux-ia32": "0.27.2",
|
||||
"@esbuild/linux-loong64": "0.27.2",
|
||||
"@esbuild/linux-mips64el": "0.27.2",
|
||||
"@esbuild/linux-ppc64": "0.27.2",
|
||||
"@esbuild/linux-riscv64": "0.27.2",
|
||||
"@esbuild/linux-s390x": "0.27.2",
|
||||
"@esbuild/linux-x64": "0.27.2",
|
||||
"@esbuild/netbsd-arm64": "0.27.2",
|
||||
"@esbuild/netbsd-x64": "0.27.2",
|
||||
"@esbuild/openbsd-arm64": "0.27.2",
|
||||
"@esbuild/openbsd-x64": "0.27.2",
|
||||
"@esbuild/openharmony-arm64": "0.27.2",
|
||||
"@esbuild/sunos-x64": "0.27.2",
|
||||
"@esbuild/win32-arm64": "0.27.2",
|
||||
"@esbuild/win32-ia32": "0.27.2",
|
||||
"@esbuild/win32-x64": "0.27.2"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-tsconfig": {
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz",
|
||||
"integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"resolve-pkg-maps": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-pkg-maps": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.21.0",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
|
||||
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "~0.27.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
},
|
||||
"bin": {
|
||||
"tsx": "dist/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
prompts/package.json
Normal file
39
prompts/package.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "prompts",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"perfect": "bun test/generate-perfect.ts",
|
||||
"pony": "bun test/generate-pony.ts",
|
||||
"worker": "bun src/workers/index.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
|
||||
"license": "MIT",
|
||||
"packageManager": "pnpm@10.27.0",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@kevisual/ai": "^0.0.19",
|
||||
"@kevisual/context": "^0.0.4",
|
||||
"@kevisual/oss": "^0.0.16",
|
||||
"@kevisual/query": "^0.0.35",
|
||||
"@kevisual/types": "^0.0.10",
|
||||
"@kevisual/use-config": "^1.0.21",
|
||||
"@types/bun": "^1.3.5",
|
||||
"@types/node": "^25.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.965.0",
|
||||
"@kevisual/logger": "^0.0.4",
|
||||
"@kevisual/notifier": "^0.0.2",
|
||||
"@kevisual/router": "^0.0.52",
|
||||
"bullmq": "^5.66.4",
|
||||
"es-toolkit": "^1.43.0",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"ioredis": "^5.9.0",
|
||||
"nanoid": "^5.1.6",
|
||||
"pocketbase": "^0.26.5",
|
||||
"unstorage": "^1.17.3"
|
||||
}
|
||||
}
|
||||
396
prompts/plan/哲理.md
Normal file
396
prompts/plan/哲理.md
Normal file
@@ -0,0 +1,396 @@
|
||||
# 1000条哲理话生成规划
|
||||
|
||||
## 一、项目概述
|
||||
|
||||
本规划旨在建立一个系统化的哲理话生成体系,通过关键词层级结构、主题分类、句式模板的组合,实现高效批量生成1000条高质量哲理话语。整体采用「主题+维度+意象+句式」的模块化设计,确保内容的深度与多样性。
|
||||
|
||||
---
|
||||
|
||||
## 二、关键词层级结构
|
||||
|
||||
### 2.1 第一层:主主题关键词(20个)
|
||||
|
||||
| 序号 | 主题 | 核心维度 | 产出权重 |
|
||||
|:---:|------|---------|:-------:|
|
||||
| 1 | 人生 | 起落、意义、旅程 | 8% |
|
||||
| 2 | 时间 | 流逝、珍惜、永恒 | 7% |
|
||||
| 3 | 成长 | 蜕变、代价、成熟 | 7% |
|
||||
| 4 | 孤独 | 独处、清醒、边缘 | 6% |
|
||||
| 5 | 选择 | 路口、承担、割舍 | 6% |
|
||||
| 6 | 自由 | 边界、主宰、释放 | 5% |
|
||||
| 7 | 痛苦 | 磨砺、觉醒、转化 | 5% |
|
||||
| 8 | 爱 | 付出、放手、成全 | 5% |
|
||||
| 9 | 死亡 | 终结、新生、虚无 | 4% |
|
||||
| 10 | 真相 | 本质、表象、谎言 | 4% |
|
||||
| 11 | 欲望 | 克制、追求、放下 | 4% |
|
||||
| 12 | 坚持 | 执着、韧劲、突破 | 4% |
|
||||
| 13 | 放下 | 释然、接纳、告别 | 4% |
|
||||
| 14 | 觉醒 | 开悟、看见、醒来 | 4% |
|
||||
| 15 | 平凡 | 普通、真实、踏实 | 4% |
|
||||
| 16 | 意义 | 价值、方向、目的 | 4% |
|
||||
| 17 | 沉默 | 安静、力量、倾听 | 4% |
|
||||
| 18 | 命运 | 注定、改变、巧合 | 4% |
|
||||
| 19 | 人性 | 善恶、复杂、真实 | 4% |
|
||||
| 20 | 世界 | 规则、真相、边界 | 3% |
|
||||
|
||||
### 2.2 第二层:子主题关键词(每个主主题5-10个)
|
||||
|
||||
#### 人生主题扩展
|
||||
```
|
||||
起点、终点、过程、意义、价值、角色、舞台、角色、剧本、观众、演员
|
||||
```
|
||||
|
||||
#### 时间主题扩展
|
||||
```
|
||||
过去、现在、未来、瞬间、永恒、季节、昼夜、钟表、沙漏、流水、光阴
|
||||
```
|
||||
|
||||
#### 成长主题扩展
|
||||
```
|
||||
伤痕、蜕变、破茧、跌倒、镜子、旧我、觉醒、代价、忍耐、成熟、枯荣
|
||||
```
|
||||
|
||||
#### 孤独主题扩展
|
||||
```
|
||||
独处、夜晚、人群疏离、无人理解、自我对话、清醒者、边缘人、静默、影子、星空
|
||||
```
|
||||
|
||||
#### 选择主题扩展
|
||||
```
|
||||
路口、岔路、方向、代价、后悔、坚定、承担、割舍、决定、权衡、放手
|
||||
```
|
||||
|
||||
#### 自由主题扩展
|
||||
```
|
||||
翅膀、天空、边界、束缚、解脱、随心所欲、主宰、释放、翱翔、远方
|
||||
```
|
||||
|
||||
#### 痛苦主题扩展
|
||||
```
|
||||
伤疤、火焰、锤炼、熔炉、泪水、黑夜、黎明、破碎、重建、结晶
|
||||
```
|
||||
|
||||
#### 爱的主题扩展
|
||||
```
|
||||
温暖、刺痛、放手、成全、燃烧、熄灭、靠近、远离、心跳、温柔
|
||||
```
|
||||
|
||||
### 2.3 第三层:意象/隐喻关键词(通用型,50个)
|
||||
|
||||
| 类别 | 意象词库 |
|
||||
|-----|---------|
|
||||
| 自然 | 光、暗、路、门、风、火、水、树、种子、根、山、海、雾、雨、雪、霜、雷、电、云、星、月、日 |
|
||||
| 生物 | 花、草、叶、果实、飞鸟、游鱼、蝴蝶、蜜蜂、蚂蚁、狮子、狼、鹰、蛇 |
|
||||
| 物品 | 钟、镜、书、信、灯、灯塔、钥匙、锁、刀、剑、帆、船、桥、窗、门、塔 |
|
||||
| 抽象 | 影、回声、脚步、呼吸、脉搏、血液、泪水、汗水、尘埃、烟雾、碎片、拼图 |
|
||||
| 空间 | 路口、渡口、山顶、谷底、十字路口、岔路口、隧道、迷宫、花园、荒原、森林 |
|
||||
|
||||
### 2.4 第四层:动词/状态词(30个)
|
||||
|
||||
| 类别 | 动词词库 |
|
||||
|-----|---------|
|
||||
| 动态 | 燃烧、沉淀、穿越、凝视、听见、醒来、破碎、重建、等待、放手、坠落、升起 |
|
||||
| 状态 | 沉默、挣扎、回归、追随、停留、遗忘、铭记、看清、承认、接受、抵抗、屈服 |
|
||||
| 变化 | 生长、凋零、绽放、枯萎、流动、凝固、溶解、凝结、扩散、收缩、上升、下降 |
|
||||
| 感知 | 感受、体会、领悟、觉醒、明白、理解、懂得、发现、寻找、追求、失去、获得 |
|
||||
|
||||
### 2.5 第五层:哲学句式模板(20种核心句式)
|
||||
|
||||
#### A类:对比型(强调对立统一)
|
||||
```
|
||||
1. 真正的____,不是____,而是____。
|
||||
2. 越____,越____。
|
||||
3. ____与____,从来不是对立的。
|
||||
4. 不是____太远,而是____太近。
|
||||
5. ____从不说谎,____却常常欺骗。
|
||||
```
|
||||
|
||||
#### B类:因果型(揭示内在逻辑)
|
||||
```
|
||||
6. 当你____,你才懂得____。
|
||||
7. 只有____过____的人,才明白____。
|
||||
8. ____教会我,____才是____。
|
||||
9. 经历过____,才知道____的可贵。
|
||||
10. ____的代价,是____。
|
||||
```
|
||||
|
||||
#### C类:隐喻型(借助意象传达)
|
||||
```
|
||||
11. ____像____,终将____。
|
||||
12. ____不会说话,却教会了我____。
|
||||
13. ____是____的镜子,照见____。
|
||||
14. 在____中,我看见了____。
|
||||
15. ____如同____,____才是____。
|
||||
```
|
||||
|
||||
#### D类:判断型(直接陈述真理)
|
||||
```
|
||||
16. 不怕____,只怕____。
|
||||
17. ____从不是____,而是____。
|
||||
18. 最高级的____,是____。
|
||||
19. 最深的____,往往____。
|
||||
20. ____的尽头,是____。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、主题分类体系
|
||||
|
||||
### 3.1 四大主题分区
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ 哲理话主题分类 │
|
||||
├─────────────┬─────────────┬─────────────┬───────────┤
|
||||
│ 人生智慧 │ 情感关系 │ 成长蜕变 │ 世界观 │
|
||||
├─────────────┼─────────────┼─────────────┼───────────┤
|
||||
│ 人生意义 │ 爱与被爱 │ 成长代价 │ 时间永恒 │
|
||||
│ 命运安排 │ 孤独本质 │ 痛苦转化 │ 真相假象 │
|
||||
│ 选择承担 │ 关系边界 │ 平凡真实 │ 自由束缚 │
|
||||
│ 放下执念 │ 付出回报 │ 自我认知 │ 人性善恶 │
|
||||
│ 活在当下 │ 放手成全 │ 坚持放弃 │ 世界规则 │
|
||||
└─────────────┴─────────────┴─────────────┴───────────┘
|
||||
```
|
||||
|
||||
### 3.2 每主题产出配额
|
||||
|
||||
| 主题分区 | 主主题数 | 每主题产出 | 小计 |
|
||||
|---------|:-------:|:---------:|-----:|
|
||||
| 人生智慧 | 5 | 70 | 350 |
|
||||
| 情感关系 | 4 | 70 | 280 |
|
||||
| 成长蜕变 | 5 | 70 | 350 |
|
||||
| 世界观 | 6 | 50 | 300 |
|
||||
| 通用意象 | - | - | 100(补足) |
|
||||
| 特殊句式 | - | - | 120(补足) |
|
||||
| **总计** | **20** | - | **1000** |
|
||||
|
||||
---
|
||||
|
||||
## 四、批量生成策略
|
||||
|
||||
### 4.1 组合爆炸法
|
||||
|
||||
```
|
||||
公式:主主题 × 子主题 × 意象 × 动词 × 句式模板 = 产出
|
||||
|
||||
示例计算:
|
||||
20个主主题 × 8个子主题 × 50个意象 × 30个动词 × 20个句式 = 480,000种可能
|
||||
|
||||
实际筛选:只需从中精选1000条高质量内容
|
||||
```
|
||||
|
||||
### 4.2 三大生成技法
|
||||
|
||||
#### 技法一:模块填充法
|
||||
```
|
||||
步骤:
|
||||
1. 选择主主题(如:孤独)
|
||||
2. 选择子主题(如:独处)
|
||||
3. 选择意象(如:灯塔)
|
||||
4. 选择动词(如:守望)
|
||||
5. 选择句式(如:真正的____,不是____,而是____)
|
||||
|
||||
产出:真正的孤独,不是独处,而是身处人群中却无人理解。
|
||||
```
|
||||
|
||||
#### 技法二:意象移植法
|
||||
```
|
||||
步骤:
|
||||
1. 固定一个意象(如:种子)
|
||||
2. 套用多个主主题
|
||||
|
||||
产出:
|
||||
- 关于成长:种子从不着急,它知道该发芽的时候自然会发芽。
|
||||
- 关于坚持:种子用几年的时间,只为换来几天的花开。
|
||||
- 关于孤独:种子独自在泥土里,完成自己的生长。
|
||||
- 关于时间:种子记得每一个春天的约定,从不迟到。
|
||||
```
|
||||
|
||||
#### 技法三:句式复用法
|
||||
```
|
||||
步骤:
|
||||
1. 固定一个句式(如:____不会说话,却教会了我____)
|
||||
2. 更换主主题和意象
|
||||
|
||||
产出:
|
||||
- 河流不会说话,却教会了我如何面对曲折。
|
||||
- 镜子不会说话,却教会了我认识真实的自己。
|
||||
- 黑夜不会说话,却教会了我珍惜光明。
|
||||
- 书本不会说话,却教会了我理解复杂的人生。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、执行计划
|
||||
|
||||
### 阶段一:基础建设(第1-2天)
|
||||
|
||||
| 任务 | 内容 | 产出 |
|
||||
|-----|------|-----|
|
||||
| 关键词整理 | 将本规划的关键词表结构化 | 关键词库CSV文件 |
|
||||
| 句式模板确认 | 精选20个核心句式 | 句式模板库 |
|
||||
| 组合脚本编写 | 用Python实现自动组合 | 批量生成脚本 |
|
||||
|
||||
### 阶段二:批量生成(第3-7天)
|
||||
|
||||
| 天数 | 目标 | 质检标准 |
|
||||
|-----|------|---------|
|
||||
| 第3天 | 生成200条初稿 | 句式正确、无语法错误 |
|
||||
| 第4天 | 生成200条初稿 | 句式正确、无语法错误 |
|
||||
| 第5天 | 生成200条初稿 | 句式正确、无语法错误 |
|
||||
| 第6天 | 生成200条初稿 | 句式正确、无语法错误 |
|
||||
| 第7天 | 生成200条初稿 | 句式正确、无语法错误 |
|
||||
|
||||
### 阶段三:人工筛选优化(第8-10天)
|
||||
|
||||
| 天数 | 任务 | 标准 |
|
||||
|-----|------|-----|
|
||||
| 第8天 | 初筛1000条精品 | 去除重复、逻辑不通 |
|
||||
| 第9天 | 精修200条重点 | 语言润色、深度强化 |
|
||||
| 第10天 | 最终校对排版 | 格式统一、风格一致 |
|
||||
|
||||
### 阶段四:分类整理输出(第11-12天)
|
||||
|
||||
| 任务 | 内容 |
|
||||
|-----|------|
|
||||
| 按主题分类 | 按四大主题分区归类 |
|
||||
| 打标签 | 标记主题、句式、意象 |
|
||||
| 输出格式 | JSON、CSV、Markdown多格式 |
|
||||
|
||||
---
|
||||
|
||||
## 六、质量评估标准
|
||||
|
||||
### 6.1 五维评分模型
|
||||
|
||||
| 维度 | 权重 | 评估要点 |
|
||||
|-----|:---:|---------|
|
||||
| 深度 | 25% | 是否有哲理深度,能否引发思考 |
|
||||
| 共鸣 | 25% | 是否有情感共鸣,让人停留 |
|
||||
| 简洁 | 20% | 是否言简意赅,无冗余表达 |
|
||||
| 新颖 | 15% | 是否有独特视角,避免陈词滥调 |
|
||||
| 完整 | 15% | 是否有头有尾,逻辑自洽 |
|
||||
|
||||
### 6.2 淘汰标准
|
||||
|
||||
以下情况直接淘汰:
|
||||
- 语法错误或逻辑不通
|
||||
- 与已有内容高度重复(相似度>70%)
|
||||
- 过于直白缺乏哲理深度
|
||||
- 陈词滥调、无新意
|
||||
- 消极负面、传播负能量
|
||||
|
||||
---
|
||||
|
||||
## 七、关键词速查表
|
||||
|
||||
### 7.1 主主题快速索引
|
||||
|
||||
```
|
||||
A类:人生意义系
|
||||
1 人生 → 旅程、剧本、角色、舞台
|
||||
2 命运 → 注定、巧合、安排、转折
|
||||
3 意义 → 价值、方向、目的、追求
|
||||
4 平凡 → 普通、真实、踏实、日常
|
||||
5 存在 → 活着、感知、体验、证明
|
||||
|
||||
B类:情感关系系
|
||||
6 孤独 → 独处、边缘、清醒、静默
|
||||
7 爱 → 温暖、刺痛、放手、成全
|
||||
8 选择 → 路口、承担、割舍、决定
|
||||
9 关系 → 边界、距离、连接、疏离
|
||||
10 自由 → 翅膀、天空、解脱、释放
|
||||
|
||||
C类:成长蜕变系
|
||||
11 成长 → 蜕变、破茧、代价、成熟
|
||||
12 痛苦 → 磨砺、锤炼、转化、结晶
|
||||
13 坚持 → 执着、韧劲、突破、恒心
|
||||
14 放下 → 释然、接纳、告别、轻松
|
||||
15 觉醒 → 开悟、看见、醒来、明白
|
||||
|
||||
D类:世界观系
|
||||
16 时间 → 流逝、珍惜、永恒、瞬间
|
||||
17 真相 → 本质、表象、谎言、虚假
|
||||
18 欲望 → 克制、追求、放下、贪念
|
||||
19 人性 → 善恶、复杂、真实、面具
|
||||
20 世界 → 规则、真相、边界、运转
|
||||
```
|
||||
|
||||
### 7.2 句式模板速查
|
||||
|
||||
```
|
||||
对比类:
|
||||
[1] 真正的____,不是____,而是____。
|
||||
[2] 越____,越____。
|
||||
[3] ____从不是____,而是____。
|
||||
|
||||
因果类:
|
||||
[4] 当你____,你才懂得____。
|
||||
[5] 只有____过____的人,才明白____。
|
||||
[6] ____的代价,是____。
|
||||
|
||||
隐喻类:
|
||||
[7] ____像____,终将____。
|
||||
[8] ____不会说话,却教会了我____。
|
||||
[9] ____是____的镜子,照见____。
|
||||
|
||||
判断类:
|
||||
[10] 不怕____,只怕____。
|
||||
[11] ____的尽头,是____。
|
||||
[12] 最深的____,往往____。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、附录:100条示例预览
|
||||
|
||||
### 人生智慧区(示例)
|
||||
|
||||
1. 真正的成熟,不是年龄的增长,而是学会与世界和平相处。
|
||||
2. 人生没有白走的路,每一步都算数,包括那些弯路。
|
||||
3. 命运从不亏欠谁,它只是给你想要的,或给你需要的。
|
||||
4. 平凡不是平庸,而是在平凡中活出真实的自己。
|
||||
5. 存在不需要证明,活着本身就是最好的证明。
|
||||
|
||||
### 情感关系区(示例)
|
||||
|
||||
6. 真正的爱,不是占有,而是让对方成为更好的自己。
|
||||
7. 孤独不是没人爱你,而是你不想爱任何人。
|
||||
8. 选择一个人,就是选择一种生活方式。
|
||||
9. 最好的关系,是相处不累,久处不厌。
|
||||
10. 自由不是想做什么就做什么,而是不想做什么就可以不做什么。
|
||||
|
||||
### 成长蜕变区(示例)
|
||||
|
||||
11. 成长就是把哭声调成静音的过程。
|
||||
12. 痛苦是化了妆的祝福,只是当时没有看出来。
|
||||
13. 坚持不是永不疲倦,而是累了依然选择继续。
|
||||
14. 放下不是忘记,而是记得但不再疼痛。
|
||||
15. 觉醒的那一刻,世界还是那个世界,但你已经不一样了。
|
||||
|
||||
### 世界观区(示例)
|
||||
|
||||
16. 时间从不说话,却回答了所有问题。
|
||||
17. 真相只有一个,但真相的背面还是真相。
|
||||
18. 欲望本身没有错,错的是被欲望控制。
|
||||
19. 人性经不起考验,但值得被理解。
|
||||
20. 世界从不温柔,但也从不残忍,它只是按规则运转。
|
||||
|
||||
---
|
||||
|
||||
## 九、总结
|
||||
|
||||
通过本规划的实施,可以系统化地完成1000条哲理话的生成任务。核心要点:
|
||||
|
||||
1. **模块化设计**:主主题×子主题×意象×动词×句式的自由组合
|
||||
2. **量化可控**:明确的配额分配和质量评估标准
|
||||
3. **高效产出**:自动化组合+人工筛选的混合模式
|
||||
4. **质量保证**:五维评分模型和淘汰机制
|
||||
|
||||
执行过程中可根据实际产出情况动态调整各环节的权重和数量,确保最终产出的1000条内容既丰富又深刻。
|
||||
|
||||
---
|
||||
|
||||
*规划版本:v1.0*
|
||||
*创建时间:2026-01-09*
|
||||
*目标产出:1000条哲理话*
|
||||
31
prompts/scripts/clear-all-jobs.ts
Normal file
31
prompts/scripts/clear-all-jobs.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { getRedisConnection } from '../src/index.ts';
|
||||
import { Queue } from 'bullmq'
|
||||
|
||||
export async function clearAllJobs() {
|
||||
const connection = getRedisConnection();
|
||||
const queueNames = [
|
||||
'image-download',
|
||||
'image-generate',
|
||||
'perfect-prompt',
|
||||
'perfect-sentence-prompt'
|
||||
];
|
||||
|
||||
for (const queueName of queueNames) {
|
||||
const queue = new Queue(queueName, { connection });
|
||||
await queue.drain();
|
||||
await queue.clean(0, 1000, 'completed');
|
||||
await queue.clean(0, 1000, 'failed');
|
||||
console.log(`Cleared all jobs in queue: ${queueName}`);
|
||||
await queue.close();
|
||||
}
|
||||
|
||||
await connection.quit();
|
||||
}
|
||||
|
||||
clearAllJobs().then(() => {
|
||||
console.log('All jobs cleared.');
|
||||
process.exit(0);
|
||||
}).catch((error) => {
|
||||
console.error('Error clearing jobs:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
28
prompts/scripts/show-download-error.ts
Normal file
28
prompts/scripts/show-download-error.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { IMAGE_DOWNLOAD_JOB } from '../src/task/image-creator.job'
|
||||
import { Worker, Queue, Job } from 'bullmq';
|
||||
import { getRedisConnection } from '../src/module/redis.ts';
|
||||
import { pbService, jimengService, ossService } from '../src/index.ts';
|
||||
|
||||
const connection = getRedisConnection();
|
||||
const queue = new Queue(IMAGE_DOWNLOAD_JOB, { connection });
|
||||
|
||||
// 显示错误的尝试任务, queue列出来
|
||||
async function showFailedJobs() {
|
||||
const failedJobs = await queue.getFailed();
|
||||
if (failedJobs.length === 0) {
|
||||
console.log('No failed jobs found.');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Found ${failedJobs.length} failed jobs:`);
|
||||
for (const job of failedJobs) {
|
||||
console.log(`- Job ID: ${job.id}, Attempts Made: ${job.attemptsMade}, Data: ${JSON.stringify(job.data)}`);
|
||||
}
|
||||
}
|
||||
|
||||
showFailedJobs().then(() => {
|
||||
process.exit(0);
|
||||
}).catch((error) => {
|
||||
console.error('Error showing failed jobs:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
40
prompts/src/app.ts
Normal file
40
prompts/src/app.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { JimengService } from './services/jimeng.service.ts';
|
||||
import { OSSService } from './services/oss.service.ts';
|
||||
import { PBService } from './services/pb.service.ts';
|
||||
import { useConfig } from '@kevisual/use-config';
|
||||
|
||||
import { App } from '@kevisual/router'
|
||||
import { useContextKey } from '@kevisual/context';
|
||||
import { getRedisConnection } from './module/redis.ts';
|
||||
import { Kevisual } from '@kevisual/ai';
|
||||
export const config = useConfig();
|
||||
export const redis = useContextKey('redis', () => getRedisConnection());
|
||||
export const jimengService = useContextKey('jimeng', new JimengService({
|
||||
apiKey: config.JIMENG_API_KEY,
|
||||
baseUrl: config.JIMENG_API_URL,
|
||||
timeout: parseInt(config.JIMENG_TIMEOUT || '300000'),
|
||||
}));
|
||||
export {
|
||||
getRedisConnection
|
||||
}
|
||||
export const ossService = useContextKey('oss', new OSSService({
|
||||
accessKeyId: config.S3_ACCESS_KEY_ID,
|
||||
accessKeySecret: config.S3_ACCESS_KEY_SECRET,
|
||||
bucketName: config.S3_BUCKET_NAME,
|
||||
region: config.S3_REGION,
|
||||
endpoint: config.S3_ENDPOINT,
|
||||
prefix: 'projects/horse/',
|
||||
}));
|
||||
export const pbService = useContextKey('pb', new PBService({
|
||||
url: config.POCKETBASE_URL,
|
||||
token: config.POCKETBASE_TOKEN,
|
||||
}));
|
||||
|
||||
export const app = useContextKey('app', new App());
|
||||
|
||||
export const ai = useContextKey('ai', new Kevisual({
|
||||
apiKey: config.KEVISUAL_NEW_API_KEY,
|
||||
model: 'qwen-plus',
|
||||
}));
|
||||
|
||||
export const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
20
prompts/src/index.ts
Normal file
20
prompts/src/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { PromptGenerator, type PromptGeneratorOptions } from "./module/prompt-geneator.ts";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { Prompt } from "./module/prompt-perfect.ts";
|
||||
import { customAlphabet } from "nanoid";
|
||||
|
||||
const letter = 'abcdefghijklmnopqrstuvwxyz'
|
||||
const randomString = customAlphabet(letter, 16);
|
||||
async function saveToFile(data: Map<string, string>, outputPath: string): Promise<void> {
|
||||
const arrayData = Array.from(data.entries()).map(([key, value]) => ({ key, value, id: randomString() }));
|
||||
await writeFile(outputPath, JSON.stringify(arrayData, null, 2), "utf-8");
|
||||
console.log(`Generated ${arrayData.length} prompts and saved to ${outputPath}`);
|
||||
}
|
||||
|
||||
import './routes/index.ts';
|
||||
|
||||
export * from './app.ts';
|
||||
// list all routes and import
|
||||
|
||||
|
||||
export { PromptGenerator, PromptGeneratorOptions, saveToFile, Prompt };
|
||||
9
prompts/src/module/config.ts
Normal file
9
prompts/src/module/config.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { useConfig } from '@kevisual/use-config';
|
||||
export const config = useConfig();
|
||||
|
||||
export const redisConfig = {
|
||||
host: config.REDIS_HOST || 'localhost',
|
||||
port: parseInt(config.REDIS_PORT || '6379'),
|
||||
password: config.REDIS_PASSWORD || undefined,
|
||||
db: parseInt(config.REDIS_DB || '0'),
|
||||
};
|
||||
12
prompts/src/module/logger.ts
Normal file
12
prompts/src/module/logger.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Logger } from "@kevisual/logger";
|
||||
import { config } from '@/app.ts'
|
||||
import { FeishuNotifier } from "@kevisual/notifier";
|
||||
export const logger = new Logger({
|
||||
level: config.LOG_LEVEL || 'info',
|
||||
});
|
||||
|
||||
export const feishuNotifier = new FeishuNotifier({
|
||||
webhook: config.FEISHU_NOTIFY_WEBHOOK_URL || '',
|
||||
});
|
||||
|
||||
export const notify = feishuNotifier;
|
||||
91
prompts/src/module/prompt-geneator.ts
Normal file
91
prompts/src/module/prompt-geneator.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { randomInt as random } from "es-toolkit";
|
||||
import { max } from "es-toolkit/compat";
|
||||
|
||||
export interface PromptGeneratorOptions {
|
||||
count?: number;
|
||||
outputPath?: string;
|
||||
sourceKeys?: string[];
|
||||
additionalValues?: string[];
|
||||
source?: { [key: string]: string[] };
|
||||
endString?: string;
|
||||
}
|
||||
|
||||
export class PromptGenerator {
|
||||
sourceKeys: string[];
|
||||
additionalValues: string[];
|
||||
count: number;
|
||||
source: { [key: string]: string[] } = {};
|
||||
endString: string = "";
|
||||
map: Map<string, string> = new Map();
|
||||
constructor(options: PromptGeneratorOptions = {}) {
|
||||
this.additionalValues = options.additionalValues || [];
|
||||
this.count = options.count ?? 1000;
|
||||
this.source = options.source || {};
|
||||
if (options?.sourceKeys) {
|
||||
this.sourceKeys = options.sourceKeys || [];
|
||||
} else {
|
||||
this.sourceKeys = Object.keys(this.source);
|
||||
}
|
||||
this.endString = options.endString || "";
|
||||
}
|
||||
|
||||
generateMap(opts?: { max?: number }): Map<string, string> {
|
||||
const map = new Map<string, string>();
|
||||
const source = this.source
|
||||
let _max = max([opts?.max || 1200, this.count + 1000])!;
|
||||
|
||||
let k = 0;
|
||||
while (map.size < this.count) {
|
||||
const selectedValues: string[] = [];
|
||||
this.sourceKeys.forEach((key: string) => {
|
||||
const values = source[key];
|
||||
if (!values) {
|
||||
return;
|
||||
}
|
||||
const randomIndex = random(0, values.length - 1);
|
||||
const randomValue = values[randomIndex];
|
||||
selectedValues.push(randomValue);
|
||||
});
|
||||
|
||||
const randomSlect = random(1, 3);
|
||||
const additionalRandomValue = this.pickRandomValue(randomSlect, this.additionalValues);
|
||||
selectedValues.push(...additionalRandomValue);
|
||||
if (this.endString) {
|
||||
selectedValues.push(this.endString);
|
||||
}
|
||||
const key = selectedValues.join(",");
|
||||
|
||||
k++;
|
||||
if (k > _max) {
|
||||
console.warn(`Reached maximum attempts (${_max}) to generate unique prompts. Generated ${map.size} unique prompts.`);
|
||||
break;
|
||||
}
|
||||
if (!map.has(key)) {
|
||||
map.set(key, selectedValues.join(" "));
|
||||
}
|
||||
}
|
||||
this.map = map;
|
||||
return map;
|
||||
}
|
||||
/**
|
||||
* 获取随机附加值
|
||||
* @param num
|
||||
* @param arr
|
||||
* @returns
|
||||
*/
|
||||
pickRandomValue(num: number, arr: string[]): string[] {
|
||||
const randomAdditionalValues: string[] = [];
|
||||
const usedIndices: Set<number> = new Set();
|
||||
if (num > arr.length) {
|
||||
num = Math.min(num, arr.length - 1);
|
||||
}
|
||||
while (randomAdditionalValues.length < num && usedIndices.size < arr.length) {
|
||||
const randomIndex = random(0, arr.length - 1);
|
||||
if (!usedIndices.has(randomIndex)) {
|
||||
usedIndices.add(randomIndex);
|
||||
randomAdditionalValues.push(arr[randomIndex]);
|
||||
}
|
||||
}
|
||||
return randomAdditionalValues;
|
||||
}
|
||||
}
|
||||
18
prompts/src/module/prompt-perfect.ts
Normal file
18
prompts/src/module/prompt-perfect.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export interface PromptOptions {
|
||||
perfectPrompt?: string;
|
||||
}
|
||||
|
||||
const DefaultPerfectPrompt = "我需要你帮我完善提示词,使其更加详细和具体。\n";
|
||||
|
||||
export class Prompt {
|
||||
perfectPrompt: string = "";
|
||||
constructor(options: PromptOptions = {}) {
|
||||
this.perfectPrompt = options.perfectPrompt || DefaultPerfectPrompt;
|
||||
}
|
||||
perfect(info: string): string {
|
||||
return `${info}\n当前的提示词是,在<perfect/>标签中:\n<perfect>${this.perfectPrompt}</perfect>`;
|
||||
}
|
||||
clearPerfectTags(text: string): string {
|
||||
return text.replace('<perfect>', '').replace('</perfect>', '').trim();
|
||||
}
|
||||
}
|
||||
29
prompts/src/module/redis.ts
Normal file
29
prompts/src/module/redis.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Redis } from 'ioredis';
|
||||
import { redisConfig } from './config.ts'
|
||||
export interface RedisConfig {
|
||||
host: string;
|
||||
port: number;
|
||||
password?: string;
|
||||
db: number;
|
||||
}
|
||||
|
||||
let redisConnection: Redis | null = null;
|
||||
|
||||
export const getRedisConnection = () => {
|
||||
if (!redisConnection) {
|
||||
redisConnection = new Redis({
|
||||
...redisConfig,
|
||||
maxRetriesPerRequest: null
|
||||
});
|
||||
|
||||
redisConnection.on('connect', () => {
|
||||
console.log('Redis connected');
|
||||
});
|
||||
|
||||
redisConnection.on('error', (err) => {
|
||||
console.error('Redis connection error:', err);
|
||||
});
|
||||
}
|
||||
|
||||
return redisConnection as any;
|
||||
};
|
||||
343
prompts/src/module/sentence-generator.ts
Normal file
343
prompts/src/module/sentence-generator.ts
Normal file
@@ -0,0 +1,343 @@
|
||||
import { randomInt as random } from 'es-toolkit';
|
||||
|
||||
// ============================================
|
||||
// 关键词库定义
|
||||
// ============================================
|
||||
|
||||
// 主主题关键词(带子主题)
|
||||
export const MAIN_THEMES: Record<string, string[]> = {
|
||||
人生: ['旅程', '剧本', '角色', '舞台', '起点', '终点', '过程', '意义', '价值', '方向'],
|
||||
时间: ['过去', '现在', '未来', '瞬间', '永恒', '季节', '昼夜', '钟表', '沙漏', '流水'],
|
||||
成长: ['伤痕', '蜕变', '破茧', '跌倒', '镜子', '旧我', '觉醒', '代价', '忍耐', '成熟'],
|
||||
孤独: ['独处', '夜晚', '人群疏离', '无人理解', '自我对话', '清醒者', '边缘人', '静默', '影子'],
|
||||
选择: ['路口', '岔路', '方向', '代价', '后悔', '坚定', '承担', '割舍', '决定', '权衡'],
|
||||
自由: ['翅膀', '天空', '边界', '束缚', '解脱', '随心所欲', '主宰', '释放', '翱翔', '远方'],
|
||||
痛苦: ['伤疤', '火焰', '锤炼', '熔炉', '泪水', '黑夜', '黎明', '破碎', '重建', '结晶'],
|
||||
爱: ['温暖', '刺痛', '放手', '成全', '燃烧', '熄灭', '靠近', '远离', '心跳', '温柔'],
|
||||
死亡: ['终结', '新生', '虚无', '告别', '离开', '遗忘', '铭记', '永恒', '轮回', '起点'],
|
||||
真相: ['本质', '表象', '谎言', '虚假', '真实', '假象', '核心', '事实', '表面', '深层'],
|
||||
欲望: ['克制', '追求', '放下', '贪念', '满足', '渴望', '执念', '放手', '贪婪', '节制'],
|
||||
坚持: ['执着', '韧劲', '突破', '恒心', '毅力', '不放弃', '继续', '前行', '攀登', '坚守'],
|
||||
放下: ['释然', '接纳', '告别', '轻松', '释怀', '松手', '转身', '遗忘', '宽恕', '和解'],
|
||||
觉醒: ['开悟', '看见', '醒来', '明白', '领悟', '理解', '懂得', '发现', '觉悟', '洞悉'],
|
||||
平凡: ['普通', '真实', '踏实', '日常', '简单', '朴素', '寻常', '平淡', '自然', '安宁'],
|
||||
意义: ['价值', '方向', '目的', '追求', '使命', '理由', '答案', '方向感', '存在感', '理由'],
|
||||
沉默: ['安静', '力量', '倾听', '静默', '无言', '无声', '寂静', '宁静', '平和', '镇定'],
|
||||
命运: ['注定', '改变', '巧合', '安排', '转折', '际遇', '天意', '偶然', '必然', '定数'],
|
||||
人性: ['善恶', '复杂', '真实', '面具', '伪装', '本质', '脆弱', '坚强', '自私', '无私'],
|
||||
世界: ['规则', '真相', '边界', '运转', '秩序', '法则', '规律', '现实', '规则', '边界']
|
||||
};
|
||||
|
||||
// 通用意象关键词
|
||||
export const IMAGES = [
|
||||
'光', '暗', '路', '门', '风', '火', '水', '树', '种子', '根', '山', '海', '雾', '雨',
|
||||
'雪', '霜', '雷', '电', '云', '星', '月', '日', '花', '草', '叶', '果实', '飞鸟',
|
||||
'游鱼', '蝴蝶', '灯塔', '镜子', '书本', '信', '钟', '钥匙', '锁', '刀', '剑', '帆',
|
||||
'船', '桥', '窗', '塔', '影', '回声', '脚步', '呼吸', '尘埃', '烟雾', '碎片'
|
||||
];
|
||||
|
||||
// 动词关键词
|
||||
export const VERBS = [
|
||||
'燃烧', '沉淀', '穿越', '凝视', '听见', '醒来', '破碎', '重建', '等待', '放手',
|
||||
'坠落', '升起', '沉默', '挣扎', '回归', '追随', '停留', '遗忘', '铭记', '看清',
|
||||
'承认', '接受', '抵抗', '屈服', '生长', '凋零', '绽放', '枯萎', '流动', '凝固',
|
||||
'感受', '体会', '领悟', '明白', '理解', '懂得', '发现', '寻找', '追求', '失去',
|
||||
'获得', '前行', '停留', '奔跑', '行走', '停留', '守望', '等待', '出发', '到达'
|
||||
];
|
||||
|
||||
// 句式模板(带占位符)
|
||||
export const TEMPLATES = [
|
||||
// 对比型
|
||||
{ type: '对比', text: '真正的{主},不是{子},而是{子}。' },
|
||||
{ type: '对比', text: '越{子},越{子}。' },
|
||||
{ type: '对比', text: '{主}与{子},从来不是对立的。' },
|
||||
{ type: '对比', text: '不是{主}太远,而是{子}太近。' },
|
||||
{ type: '对比', text: '{主}从不说谎,{子}却常常欺骗。' },
|
||||
{ type: '对比', text: '不怕{主},只怕{动}。' },
|
||||
{ type: '对比', text: '{主}从不是{子},而是{意义}。' },
|
||||
{ type: '对比', text: '最高级的{主},是{动}{意义}。' },
|
||||
{ type: '对比', text: '最深的{主},往往{动}。' },
|
||||
{ type: '对比', text: '{主}的尽头,是{动}{意义}。' },
|
||||
|
||||
// 因果型
|
||||
{ type: '因果', text: '当你{动}{主},你才懂得{意义}。' },
|
||||
{ type: '因果', text: '只有{动}过{主}的人,才明白{意义}。' },
|
||||
{ type: '因果', text: '{主}教会我,{动}才是{意义}。' },
|
||||
{ type: '因果', text: '经历过{主},才知道{意义}的可贵。' },
|
||||
{ type: '因果', text: '{主}的代价,是{动}{意义}。' },
|
||||
{ type: '因果', text: '因为{动}{主},所以{动}{意义}。' },
|
||||
{ type: '因果', text: '{主}让我明白,{动}才是真正的{意义}。' },
|
||||
|
||||
// 隐喻型
|
||||
{ type: '隐喻', text: '{主}像{意象},终将{动}。' },
|
||||
{ type: '隐喻', text: '{意象}不会说话,却教会了我{动}{意义}。' },
|
||||
{ type: '隐喻', text: '{主}是{意象}的镜子,照见{意义}。' },
|
||||
{ type: '隐喻', text: '在{主}中,我看见了{意义}。' },
|
||||
{ type: '隐喻', text: '{主}如同{意象},{动}才是{意义}。' },
|
||||
{ type: '隐喻', text: '{意象}见证了{主}的{子}。' },
|
||||
{ type: '隐喻', text: '{主}是{意象},需要{动}才能明白{意义}。' },
|
||||
{ type: '隐喻', text: '当{意象}遇见{主},{动}才是唯一的{意义}。' },
|
||||
];
|
||||
|
||||
// ============================================
|
||||
// 生成器类
|
||||
// ============================================
|
||||
|
||||
export interface SentenceGeneratorOptions {
|
||||
/** 生成数量 */
|
||||
count?: number;
|
||||
/** 使用的模板类型 */
|
||||
templateTypes?: ('对比' | '因果' | '隐喻' | '判断')[];
|
||||
/** 使用的主主题 */
|
||||
mainThemes?: string[];
|
||||
/** 输出格式 */
|
||||
outputFormat?: 'array' | 'text' | 'json';
|
||||
/** 是否包含标签 */
|
||||
withTags?: boolean;
|
||||
}
|
||||
|
||||
export interface GeneratedSentence {
|
||||
text: string;
|
||||
theme: string;
|
||||
template: string;
|
||||
templateType: string;
|
||||
tags: string[];
|
||||
index: number;
|
||||
}
|
||||
|
||||
export class SentenceGenerator {
|
||||
private options: Required<SentenceGeneratorOptions>;
|
||||
private themeUsage: Map<string, number> = new Map();
|
||||
private generatedCount: number = 0;
|
||||
|
||||
constructor(options: SentenceGeneratorOptions = {}) {
|
||||
this.options = {
|
||||
count: options.count ?? 100,
|
||||
templateTypes: options.templateTypes ?? ['对比', '因果', '隐喻'],
|
||||
mainThemes: options.mainThemes ?? Object.keys(MAIN_THEMES),
|
||||
outputFormat: options.outputFormat ?? 'array',
|
||||
withTags: options.withTags ?? true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全获取随机数组元素
|
||||
*/
|
||||
private pickRandom<T>(arr: T[]): T | undefined {
|
||||
if (arr.length === 0) return undefined;
|
||||
return arr[random(0, arr.length - 1)];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取可用主题(考虑均衡使用)
|
||||
*/
|
||||
private pickTheme(): string {
|
||||
const { mainThemes, count } = this.options;
|
||||
const threshold = count / mainThemes.length * 1.5;
|
||||
|
||||
const availableThemes = mainThemes.filter(theme => {
|
||||
const usage = this.themeUsage.get(theme) || 0;
|
||||
return usage < threshold;
|
||||
});
|
||||
|
||||
const themes = availableThemes.length > 0 ? availableThemes : mainThemes;
|
||||
const theme = this.pickRandom(themes) || mainThemes[0];
|
||||
|
||||
this.themeUsage.set(theme, (this.themeUsage.get(theme) || 0) + 1);
|
||||
return theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充模板
|
||||
*/
|
||||
private fillTemplate(template: string, theme: string, subTheme: string, image: string, verb: string, meaning: string): string {
|
||||
return template
|
||||
.replace(/\{主\}/g, theme)
|
||||
.replace(/\{子\}/g, subTheme)
|
||||
.replace(/\{意象\}/g, image)
|
||||
.replace(/\{动\}/g, verb)
|
||||
.replace(/\{意义\}/g, meaning);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成单条句子
|
||||
*/
|
||||
private generateOne(): GeneratedSentence {
|
||||
// 选择主主题
|
||||
const theme = this.pickTheme();
|
||||
|
||||
// 选择子主题
|
||||
const subTheme = this.pickRandom(MAIN_THEMES[theme]) || theme;
|
||||
|
||||
// 选择其他关键词
|
||||
const image = this.pickRandom(IMAGES) || '';
|
||||
const verb = this.pickRandom(VERBS) || '';
|
||||
const meaning = this.pickRandom(MAIN_THEMES['意义']) || '意义';
|
||||
|
||||
// 选择模板
|
||||
const availableTemplates = TEMPLATES.filter(t =>
|
||||
this.options.templateTypes.includes(t.type as any)
|
||||
);
|
||||
const templateObj = this.pickRandom(availableTemplates);
|
||||
if (!templateObj) {
|
||||
throw new Error('没有可用的模板');
|
||||
}
|
||||
|
||||
// 填充模板
|
||||
let text = this.fillTemplate(templateObj.text, theme, subTheme, image, verb, meaning);
|
||||
|
||||
// 清理多余空格
|
||||
text = text.replace(/\s+/g, ' ').trim();
|
||||
|
||||
this.generatedCount++;
|
||||
|
||||
return {
|
||||
text,
|
||||
theme,
|
||||
template: templateObj.text,
|
||||
templateType: templateObj.type,
|
||||
tags: [theme, templateObj.type, subTheme, image, verb],
|
||||
index: this.generatedCount
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成句子
|
||||
*/
|
||||
generate(): GeneratedSentence[] {
|
||||
const results: GeneratedSentence[] = [];
|
||||
const maxAttempts = this.options.count * 10;
|
||||
let attempts = 0;
|
||||
|
||||
while (results.length < this.options.count && attempts < maxAttempts) {
|
||||
attempts++;
|
||||
const sentence = this.generateOne();
|
||||
|
||||
// 去重
|
||||
const isDuplicate = results.some(s => s.text === sentence.text);
|
||||
if (!isDuplicate) {
|
||||
results.push(sentence);
|
||||
}
|
||||
}
|
||||
|
||||
if (results.length < this.options.count) {
|
||||
console.warn(`只生成了 ${results.length} 条句子,达到最大尝试次数`);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成并输出
|
||||
*/
|
||||
generateAndOutput(): string | GeneratedSentence[] {
|
||||
return this.generate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出为CSV
|
||||
*/
|
||||
exportToCSV(): string {
|
||||
const results = this.generate();
|
||||
const headers = ['序号', '句子', '主题', '模板类型', '标签'];
|
||||
const rows = results.map(s => [
|
||||
s.index.toString(),
|
||||
`"${s.text}"`,
|
||||
s.theme,
|
||||
s.templateType,
|
||||
s.tags.join(';')
|
||||
]);
|
||||
return [headers.join(','), ...rows.map(r => r.join(','))].join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出为JSON
|
||||
*/
|
||||
exportToJSON(): string {
|
||||
const results = this.generate();
|
||||
return JSON.stringify({
|
||||
meta: {
|
||||
total: results.length,
|
||||
generatedAt: new Date().toISOString(),
|
||||
generator: 'sentence-generator.ts',
|
||||
version: '1.0'
|
||||
},
|
||||
sentences: results
|
||||
}, null, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
*/
|
||||
getStats(): Record<string, number> {
|
||||
return Object.fromEntries(this.themeUsage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置
|
||||
*/
|
||||
reset(): void {
|
||||
this.generatedCount = 0;
|
||||
this.themeUsage.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 便捷函数
|
||||
// ============================================
|
||||
|
||||
export function generatePhilosophicalSentences(
|
||||
count: number = 100,
|
||||
options: Partial<SentenceGeneratorOptions> = {}
|
||||
): GeneratedSentence[] {
|
||||
const generator = new SentenceGenerator({ count, ...options });
|
||||
return generator.generate();
|
||||
}
|
||||
|
||||
export function generateByTheme(theme: string, count: number = 20): GeneratedSentence[] {
|
||||
const generator = new SentenceGenerator({ count, mainThemes: [theme] });
|
||||
return generator.generate();
|
||||
}
|
||||
|
||||
export function generateByTemplate(
|
||||
templateType: '对比' | '因果' | '隐喻' | '判断',
|
||||
count: number = 50
|
||||
): GeneratedSentence[] {
|
||||
const generator = new SentenceGenerator({ count, templateTypes: [templateType] });
|
||||
return generator.generate();
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// CLI入口
|
||||
// ============================================
|
||||
|
||||
const isMainModule = process.argv[1]?.includes('sentence-generator');
|
||||
|
||||
if (isMainModule) {
|
||||
const args = process.argv.slice(2);
|
||||
const count = parseInt(args[0]) || 100;
|
||||
const format = (args[1] as 'text' | 'json' | 'csv') || 'text';
|
||||
|
||||
console.log(`生成 ${count} 条哲理句...\n`);
|
||||
|
||||
const generator = new SentenceGenerator({
|
||||
count,
|
||||
templateTypes: ['对比', '因果', '隐喻']
|
||||
});
|
||||
|
||||
if (format === 'csv') {
|
||||
console.log(generator.exportToCSV());
|
||||
} else if (format === 'json') {
|
||||
console.log(generator.exportToJSON());
|
||||
} else {
|
||||
const sentences = generator.generate();
|
||||
sentences.forEach(s => {
|
||||
console.log(`${s.index}. ${s.text} [${s.theme}]`);
|
||||
});
|
||||
}
|
||||
|
||||
console.log('\n统计信息:');
|
||||
console.log(generator.getStats());
|
||||
}
|
||||
|
||||
export default SentenceGenerator;
|
||||
56
prompts/src/module/sentence-image.ts
Normal file
56
prompts/src/module/sentence-image.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
import { Prompt } from "./prompt-perfect.ts"
|
||||
const exampleData = {
|
||||
"text": "选择像锁,终将奔跑。",
|
||||
"theme": "选择",
|
||||
"template": "{主}像{意象},终将{动}。",
|
||||
"templateType": "隐喻",
|
||||
"tags": [
|
||||
"选择",
|
||||
"隐喻",
|
||||
"后悔",
|
||||
"锁",
|
||||
"奔跑"
|
||||
],
|
||||
"index": 7,
|
||||
"optimized": "选择如秤,终需掂量。", // 要展示的句子
|
||||
}
|
||||
const perfect2 = `你是一个专业的海报设计师和文案策划。请根据以下句子数据,设计一张富有启发性的海报:
|
||||
|
||||
**句子信息**:
|
||||
- 主题:{theme}
|
||||
- 模板:{template}
|
||||
- 模板类型:{templateType}
|
||||
- 标签:{tags}
|
||||
- 待优化句子:{text}
|
||||
- 推荐展示文案:{optimized}
|
||||
|
||||
**设计要求**:
|
||||
|
||||
1. **文案优化**
|
||||
- 以 {optimized} 为基础,进一步精炼成适合海报展示的短句(8-15字)
|
||||
- 语言要有力量感,能引起共鸣
|
||||
- 可运用比喻、排比等修辞手法增强表现力
|
||||
|
||||
2. **意象选择**
|
||||
- 根据 {tags} 和主题,选择1-2个视觉意象
|
||||
- 意象要能准确传达句子的情感和哲理
|
||||
- 推荐风格:温暖励志/清新自然/深沉思考/极简有力
|
||||
|
||||
3. **视觉风格**
|
||||
- 现代简约,有质感
|
||||
- 文字与画面和谐统一
|
||||
- 适合社交媒体传播
|
||||
|
||||
**输出格式**:
|
||||
海报短句:[精炼后的短句]
|
||||
视觉意象:[1-2个核心意象]
|
||||
设计风格:[推荐风格]
|
||||
设计说明:[50字以内的设计思路]
|
||||
|
||||
请直接输出以上内容,不要有其他解释。`
|
||||
export class SentenceImage extends Prompt {
|
||||
constructor() {
|
||||
super({ perfectPrompt: perfect2 });
|
||||
}
|
||||
}
|
||||
396
prompts/src/module/sentence-perfect.ts
Normal file
396
prompts/src/module/sentence-perfect.ts
Normal file
@@ -0,0 +1,396 @@
|
||||
import { config } from './config.ts';
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
import { Kevisual } from '@kevisual/ai';
|
||||
|
||||
// ============================================
|
||||
// AI优化提示词配置
|
||||
// ============================================
|
||||
|
||||
// 通用哲理句优化提示词
|
||||
export const PERFECT_PROMPT = `你是一个深谙人生哲理的作家。请将以下不完整的哲理句补充完整,使其成为一句发人深省的哲理名言。
|
||||
|
||||
要求:
|
||||
1. 保持原句的核心主题和结构
|
||||
2. 语言要简洁有力,富有哲理深度
|
||||
3. 让人读后有所思考和共鸣
|
||||
4. 符合中文表达习惯
|
||||
5. 字数控制在20-50字之间
|
||||
|
||||
原句:{{sentence}}
|
||||
|
||||
请直接输出优化后的句子,不需要任何解释。`;
|
||||
|
||||
// 不同主题的专项优化提示词
|
||||
export const THEME_PROMPTS: Record<string, string> = {
|
||||
人生: `你是一个智慧的导师。请将以下关于人生的句子优化成一句深刻的人生感悟:
|
||||
|
||||
要求:
|
||||
- 语言平实却蕴含智慧
|
||||
- 让人读后有所触动
|
||||
- 适合作为人生格言
|
||||
|
||||
原句:{{sentence}}
|
||||
|
||||
直接输出优化后的句子。`,
|
||||
|
||||
时间: `你是一个洞察时光的智者。请将以下关于时间的句子优化:
|
||||
|
||||
要求:
|
||||
- 体现时间的珍贵与无情
|
||||
- 让人珍惜当下
|
||||
- 富有诗意但不晦涩
|
||||
|
||||
原句:{{sentence}}
|
||||
|
||||
直接输出优化后的句子。`,
|
||||
|
||||
成长: `你是一个经历过蜕变的智者。请将以下关于成长的句子优化:
|
||||
|
||||
要求:
|
||||
- 体现成长的代价与收获
|
||||
- 让人对成长有新的理解
|
||||
- 有力量感但不鸡汤
|
||||
|
||||
原句:{{sentence}}
|
||||
|
||||
直接输出优化后的句子。`,
|
||||
|
||||
孤独: `你是一个深刻理解孤独的人。请将以下关于孤独的句子优化:
|
||||
|
||||
要求:
|
||||
- 准确描述孤独的本质
|
||||
- 不是消极,而是清醒
|
||||
- 让人学会与孤独和解
|
||||
|
||||
原句:{{sentence}}
|
||||
|
||||
直接输出优化后的句子。`,
|
||||
|
||||
爱: `你是一个懂得爱的人。请将以下关于爱的句子优化:
|
||||
|
||||
要求:
|
||||
- 体现爱的真谛
|
||||
- 不是空洞的情话
|
||||
- 让人对爱有新的认识
|
||||
|
||||
原句:{{sentence}}
|
||||
|
||||
直接输出优化后的句子。`,
|
||||
|
||||
选择: `你是一个善于抉择的人。请将以下关于选择的句子优化:
|
||||
|
||||
要求:
|
||||
- 体现选择的重量
|
||||
- 不是教条,而是智慧
|
||||
- 让人更慎重地面对选择
|
||||
|
||||
原句:{{sentence}}
|
||||
|
||||
直接输出优化后的句子。`,
|
||||
|
||||
自由: `你是一个追求自由的人。请将以下关于自由的句子优化:
|
||||
|
||||
要求:
|
||||
- 准确描述自由的真谛
|
||||
- 让人理解自由的代价
|
||||
- 有深度但不晦涩
|
||||
|
||||
原句:{{sentence}}
|
||||
|
||||
直接输出优化后的句子。`,
|
||||
|
||||
痛苦: `你是一个从痛苦中走过来的人。请将以下关于痛苦的句子优化:
|
||||
|
||||
要求:
|
||||
- 不是宣泄负面情绪
|
||||
- 而是让人从痛苦中获得力量
|
||||
- 转化痛苦为成长
|
||||
|
||||
原句:{{sentence}}
|
||||
|
||||
直接输出优化后的句子。`
|
||||
};
|
||||
|
||||
// ============================================
|
||||
// AI客户端接口
|
||||
// ============================================
|
||||
|
||||
export interface PerfectOptions {
|
||||
/** Kevisual AI 实例 */
|
||||
ai?: Kevisual;
|
||||
/** 使用的提示词模板 */
|
||||
promptTemplate?: string;
|
||||
/** 并发请求数 */
|
||||
concurrency?: number;
|
||||
/** 重试次数 */
|
||||
retryTimes?: number;
|
||||
/** 成功后回调 */
|
||||
onSuccess?: (item: PerfectItem) => void;
|
||||
/** 失败后回调 */
|
||||
onError?: (item: PerfectError) => void;
|
||||
/** 进度回调 */
|
||||
onProgress?: (progress: ProgressInfo) => void;
|
||||
}
|
||||
|
||||
export interface PerfectItem {
|
||||
index: number;
|
||||
text: string;
|
||||
template: string;
|
||||
templateType: string;
|
||||
optimized: string;
|
||||
prompt?: string;
|
||||
theme: string;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export interface PerfectError {
|
||||
index: number;
|
||||
original: string;
|
||||
error: string;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
export interface ProgressInfo {
|
||||
current: number;
|
||||
total: number;
|
||||
percentage: number;
|
||||
successCount: number;
|
||||
errorCount: number;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 默认AI实例
|
||||
// ============================================
|
||||
|
||||
function createDefaultAI(): Kevisual {
|
||||
return new Kevisual({
|
||||
apiKey: config.KEVISUAL_NEW_API_KEY || '',
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 句子优化器类
|
||||
// ============================================
|
||||
|
||||
export class SentencePerfect {
|
||||
private options: Required<PerfectOptions>;
|
||||
private ai: Kevisual;
|
||||
|
||||
constructor(options: PerfectOptions = {}) {
|
||||
this.ai = options.ai || createDefaultAI();
|
||||
|
||||
this.options = {
|
||||
ai: this.ai,
|
||||
promptTemplate: options.promptTemplate || PERFECT_PROMPT,
|
||||
concurrency: options.concurrency ?? 5,
|
||||
retryTimes: options.retryTimes ?? 3,
|
||||
onSuccess: options.onSuccess || (() => { }),
|
||||
onError: options.onError || (() => { }),
|
||||
onProgress: options.onProgress || (() => { })
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取主题对应的提示词
|
||||
*/
|
||||
private getThemePrompt(theme: string, sentence: string): string {
|
||||
const themePrompt = THEME_PROMPTS[theme];
|
||||
if (themePrompt) {
|
||||
return themePrompt.replace('{{sentence}}', sentence);
|
||||
}
|
||||
return this.options.promptTemplate.replace('{{sentence}}', sentence);
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化单条句子(带重试)
|
||||
*/
|
||||
async perfectOne(item: PerfectItem): Promise<PerfectItem> {
|
||||
const prompt = this.getThemePrompt(item.theme, item.text);
|
||||
let lastError: Error | null = null;
|
||||
|
||||
for (let i = 0; i < this.options.retryTimes; i++) {
|
||||
try {
|
||||
await this.ai.chat([{ role: 'user', content: prompt }]);
|
||||
let optimized = this.ai.responseText || '';
|
||||
optimized = optimized.trim().replace(/^["']|["']$/g, '');
|
||||
|
||||
const result: PerfectItem = {
|
||||
...item,
|
||||
prompt: prompt,
|
||||
optimized: optimized || item.text
|
||||
};
|
||||
|
||||
this.options.onSuccess(result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
lastError = error as Error;
|
||||
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
const error: PerfectError = {
|
||||
index: item.index,
|
||||
original: item.text,
|
||||
error: lastError?.message || '未知错误',
|
||||
theme: item.theme
|
||||
};
|
||||
|
||||
this.options.onError(error);
|
||||
return {
|
||||
...item,
|
||||
optimized: item.text
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量优化句子
|
||||
*/
|
||||
async perfectBatch(
|
||||
items: PerfectItem[]
|
||||
): Promise<PerfectItem[]> {
|
||||
const total = items.length;
|
||||
let successCount = 0;
|
||||
let errorCount = 0;
|
||||
const results: PerfectItem[] = [];
|
||||
|
||||
const batchSize = this.options.concurrency;
|
||||
|
||||
for (let i = 0; i < items.length; i += batchSize) {
|
||||
const batch = items.slice(i, i + batchSize);
|
||||
|
||||
const promises = batch.map(async (item) => {
|
||||
try {
|
||||
const result = await this.perfectOne(item);
|
||||
results.push(result);
|
||||
successCount++;
|
||||
} catch {
|
||||
errorCount++;
|
||||
}
|
||||
|
||||
const current = Math.min(i + batchSize, total);
|
||||
const percentage = Math.round((current / total) * 100);
|
||||
|
||||
this.options.onProgress({
|
||||
current,
|
||||
total,
|
||||
percentage,
|
||||
successCount,
|
||||
errorCount
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
if (i + batchSize < items.length) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件加载句子并优化
|
||||
*/
|
||||
async perfectFromFile(
|
||||
inputPath: string,
|
||||
outputPath: string
|
||||
): Promise<{ success: number; failed: number; results: any[] }> {
|
||||
const data = JSON.parse(readFileSync(inputPath, 'utf-8'));
|
||||
const sentences = data.sentences || data;
|
||||
|
||||
const items: PerfectItem[] = sentences.map((s: any, i: number) => ({
|
||||
index: s.index || i + 1,
|
||||
text: s.text || s,
|
||||
template: s.template || '',
|
||||
templateType: s.templateType || '',
|
||||
theme: s.theme || '人生',
|
||||
tags: s.tags || [],
|
||||
optimized: ''
|
||||
}));
|
||||
|
||||
const results = await this.perfectBatch(items);
|
||||
|
||||
// 合并原始数据与优化结果
|
||||
const mergedResults = results.map((r, i) => {
|
||||
return {
|
||||
index: r.index,
|
||||
text: r.text,
|
||||
template: r.template,
|
||||
templateType: r.templateType,
|
||||
theme: r.theme,
|
||||
tags: r.tags,
|
||||
optimized: r.optimized
|
||||
};
|
||||
});
|
||||
|
||||
const successResults = results.filter(r => r.optimized !== r.text);
|
||||
const failedResults = results.filter(r => r.optimized === r.text);
|
||||
|
||||
const output = {
|
||||
meta: {
|
||||
total: results.length,
|
||||
success: successResults.length,
|
||||
failed: failedResults.length,
|
||||
optimizedAt: new Date().toISOString()
|
||||
},
|
||||
sentences: mergedResults
|
||||
};
|
||||
|
||||
writeFileSync(outputPath, JSON.stringify(output, null, 2), 'utf-8');
|
||||
|
||||
return {
|
||||
success: successResults.length,
|
||||
failed: failedResults.length,
|
||||
results: mergedResults
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置AI实例
|
||||
*/
|
||||
setAI(ai: Kevisual): void {
|
||||
this.ai = ai;
|
||||
this.options.ai = ai;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 便捷函数
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* 快速优化单条句子
|
||||
*/
|
||||
export async function quickPerfect(
|
||||
sentence: string,
|
||||
theme: string = '人生'
|
||||
): Promise<string> {
|
||||
const perfect = new SentencePerfect();
|
||||
const item: PerfectItem = {
|
||||
index: 0,
|
||||
text: sentence,
|
||||
template: '',
|
||||
templateType: '',
|
||||
theme,
|
||||
tags: [],
|
||||
optimized: ''
|
||||
};
|
||||
const result = await perfect.perfectOne(item);
|
||||
return result.optimized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JSON文件优化句子
|
||||
*/
|
||||
export async function perfectFromJSON(
|
||||
inputPath: string,
|
||||
outputPath: string,
|
||||
options: Partial<PerfectOptions> = {}
|
||||
): Promise<{ success: number; failed: number }> {
|
||||
const perfect = new SentencePerfect(options);
|
||||
const result = await perfect.perfectFromFile(inputPath, outputPath);
|
||||
return { success: result.success, failed: result.failed };
|
||||
}
|
||||
|
||||
export default SentencePerfect;
|
||||
74
prompts/src/routes/create-sentence.ts
Normal file
74
prompts/src/routes/create-sentence.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { app, ossService, pbService, redis } from '@/app.ts'
|
||||
import { addImageGenerateJob } from '@/task/image-creator.job.ts';
|
||||
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
type SentenceItem = {
|
||||
text: string;
|
||||
theme: string;
|
||||
template: string;
|
||||
templateType: string;
|
||||
tags: string[];
|
||||
index: number;
|
||||
prompt?: string;
|
||||
optimized?: string;
|
||||
}
|
||||
app.route({
|
||||
path: 'image-creator',
|
||||
key: 'create-sentence-list',
|
||||
description: '导入句子列表',
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const data: SentenceItem[] = ctx.query?.data || [];
|
||||
if (!Array.isArray(data) || data.length === 0) {
|
||||
ctx.throw(400, 'Invalid input data');
|
||||
}
|
||||
const BATCH_SIZE = 50;
|
||||
for (let i = 0; i < data.length; i += BATCH_SIZE) {
|
||||
const chunk = data.slice(i, i + BATCH_SIZE);
|
||||
const batch = pbService.client.createBatch();
|
||||
for (const item of chunk) {
|
||||
batch.collection(pbService.collectionName).create({
|
||||
text: '句子生成海报',
|
||||
summary: item.optimized,
|
||||
description: '',
|
||||
status: '创建',
|
||||
tags: ['sentence', ...item.tags],
|
||||
data: {
|
||||
sentence: item
|
||||
}
|
||||
});
|
||||
}
|
||||
await batch.send();
|
||||
console.log(`Processed batch ${Math.floor(i / BATCH_SIZE) + 1}: ${chunk.length} items.`);
|
||||
await sleep(200); // 避免短时间内添加过多请求
|
||||
}
|
||||
ctx.body = { message: `Imported ${data.length} sentences.` };
|
||||
}).addTo(app);
|
||||
|
||||
|
||||
app.route({
|
||||
path: 'image-creator',
|
||||
key: 'fix-sentences',
|
||||
middleware: ['auth'],
|
||||
description: '修正句子条目的 summary 字段'
|
||||
}).define(async () => {
|
||||
const list = await pbService.collection.getFullList({
|
||||
filter: `tags~"sentence"`,
|
||||
})
|
||||
console.log(`Fetched ${list.length} sentence items from PocketBase.`);
|
||||
const BATCH_SIZE = 50;
|
||||
|
||||
for (let i = 0; i < list.length; i += BATCH_SIZE) {
|
||||
const chunk = list.slice(i, i + BATCH_SIZE);
|
||||
const batch = pbService.client.createBatch();
|
||||
for (const item of chunk) {
|
||||
const sentenceData = item.data?.sentence || {};
|
||||
if (!sentenceData.optimized) continue;
|
||||
batch.collection(pbService.collectionName).update(item.id, {
|
||||
summary: sentenceData.optimized,
|
||||
});
|
||||
}
|
||||
await batch.send();
|
||||
console.log(`Processed batch ${Math.floor(i / BATCH_SIZE) + 1}: ${chunk.length} items.`);
|
||||
}
|
||||
}).addTo(app);
|
||||
43
prompts/src/routes/create-task.ts
Normal file
43
prompts/src/routes/create-task.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { app, ossService, pbService, redis } from '@/app.ts'
|
||||
import { addImageGenerateJob } from '@/task/image-creator.job.ts';
|
||||
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
app.route({
|
||||
path: 'image-creator',
|
||||
key: 'create-task',
|
||||
description: '创建图片生成任务,将所有计划中的任务加入图片生成队列',
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const status = ctx.status ?? '计划中';
|
||||
const list = await pbService.collection.getFullList({
|
||||
filter: `status="${status}"`,
|
||||
})
|
||||
for (const item of list) {
|
||||
await addImageGenerateJob(item);
|
||||
await sleep(100); // 避免短时间内添加过多任务
|
||||
}
|
||||
console.log(`Added ${list.length} image generate jobs to the queue.`);
|
||||
}).addTo(app);
|
||||
|
||||
|
||||
app.route({
|
||||
path: 'image-creator',
|
||||
key: 'batch-update-tags',
|
||||
description: '测试 Redis 连接',
|
||||
isDebug: true
|
||||
}).define(async (ctx) => {
|
||||
const list = await pbService.collection.getFullList({
|
||||
filter: `tags="[]"`,
|
||||
})
|
||||
console.log(`Fetched ${list.length} items from PocketBase.`, list[0]);
|
||||
const BATCH_SIZE = 50;
|
||||
for (let i = 0; i < list.length; i += BATCH_SIZE) {
|
||||
const chunk = list.slice(i, i + BATCH_SIZE);
|
||||
const batch = pbService.client.createBatch();
|
||||
for (const item of chunk) {
|
||||
batch.collection(pbService.collectionName).update(item.id, { tags: ["horse"] });
|
||||
}
|
||||
await batch.send();
|
||||
console.log(`Processed batch ${Math.floor(i / BATCH_SIZE) + 1}: ${chunk.length} items.`);
|
||||
}
|
||||
ctx.body = { message: `Processed ${list.length} items.` };
|
||||
}).addTo(app);
|
||||
69
prompts/src/routes/image-update.ts
Normal file
69
prompts/src/routes/image-update.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { pbService, jimengService, ossService, app } from '../index.ts';
|
||||
import type { ImageCollection } from '../services/pb.service.ts';
|
||||
|
||||
// 更新 PB 状态
|
||||
export async function updateItemStatus(
|
||||
itemId: string,
|
||||
status: string,
|
||||
extraData?: Partial<ImageCollection>
|
||||
): Promise<void> {
|
||||
const collection = pbService.getCollection<ImageCollection>(pbService.collectionName);
|
||||
if (extraData) {
|
||||
const existingItem = await pbService.collection.getOne(itemId);
|
||||
const data = existingItem.data;
|
||||
const existingImages = data?.images || [];
|
||||
const newImages = extraData.data?.images || [];
|
||||
await collection.update(itemId, {
|
||||
status,
|
||||
...extraData,
|
||||
data: {
|
||||
...extraData?.data,
|
||||
...data,
|
||||
images: [...existingImages, ...newImages],
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await collection.update(itemId, {
|
||||
status,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
app.route({
|
||||
path: 'image-creator',
|
||||
key: 'image-update',
|
||||
description: '更新所有图片生成任务的状态',
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const { itemId, status, extraData } = ctx.query;
|
||||
if (!itemId || !status) {
|
||||
ctx.throw(400, 'Missing itemId or status');
|
||||
}
|
||||
await updateItemStatus(itemId as string, status as string, extraData as Partial<ImageCollection> | undefined);
|
||||
ctx.body = { message: 'Item status updated successfully' };
|
||||
}).addTo(app);
|
||||
|
||||
app.route({
|
||||
path: 'image-creator',
|
||||
key: 'image-download',
|
||||
description: '下载所有图片生成任务的图片,并上传到 OSS, 返回图片链接。参数: itemId, imageUrl, index',
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const { itemId, imageUrl, index = 0 } = ctx.query;
|
||||
if (!itemId || !imageUrl || index === undefined) {
|
||||
ctx.throw(400, 'Missing itemId, imageUrl or index');
|
||||
}
|
||||
const imageBuffer = await jimengService.downloadImage(imageUrl);
|
||||
const filename = `generated_${itemId}_${index}_${Date.now()}.png`;
|
||||
await ossService.putObject(filename, imageBuffer);
|
||||
const ossUrl = ossService.getLink(filename)
|
||||
console.log(`[ImageDownload] Image uploaded to OSS: ${ossUrl}`);
|
||||
|
||||
const imageData = { type: 'tos' as const, url: ossUrl };
|
||||
await updateItemStatus(itemId as string, 'completed', {
|
||||
data: {
|
||||
images: [imageData],
|
||||
},
|
||||
});
|
||||
ctx.body = { message: 'Image downloaded and uploaded to OSS successfully', ossUrl };
|
||||
}).addTo(app);
|
||||
15
prompts/src/routes/index.ts
Normal file
15
prompts/src/routes/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { app } from '../index.ts';
|
||||
|
||||
import './create-task.ts'
|
||||
|
||||
import './image-update.ts'
|
||||
|
||||
import './create-sentence.ts'
|
||||
|
||||
import './perfect.ts'
|
||||
app.route({
|
||||
path: 'auth',
|
||||
id: 'auth'
|
||||
}).define(async (ctx) => {
|
||||
// ctx.body = { message: 'Auth route' };
|
||||
}).addTo(app);
|
||||
46
prompts/src/routes/perfect.ts
Normal file
46
prompts/src/routes/perfect.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { app, ossService, pbService, redis } from '@/app.ts'
|
||||
import { addPerfectPromptJob } from '@/task/perfect-prompt.job.ts';
|
||||
import { addPerfectSentencePromptJob } from '@/task/perfect-sentence.job.ts';
|
||||
|
||||
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
app.route({
|
||||
path: 'image-creator',
|
||||
key: 'perfect-horse',
|
||||
description: '优化关于像素马的关键字',
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const id = ctx.query.id as string;
|
||||
if (!id) {
|
||||
ctx.throw(400, '缺少 id 参数');
|
||||
}
|
||||
const item = await pbService.collection.getOne(id);
|
||||
if (!item) {
|
||||
ctx.throw(404, '未找到对应的条目');
|
||||
}
|
||||
addPerfectPromptJob(item);
|
||||
ctx.body = { message: '已添加优化任务到队列' };
|
||||
}).addTo(app);
|
||||
|
||||
|
||||
app.route({
|
||||
path: 'image-creator',
|
||||
key: 'perfect-sentence',
|
||||
description: '优化关于句子的关键字',
|
||||
middleware: ['auth']
|
||||
}).define(async (ctx) => {
|
||||
const list = await pbService.collection.getFullList({
|
||||
filter: `status="创建"`,
|
||||
limit: 1
|
||||
});
|
||||
console.log(`Fetched ${list.length} items for sentence perfection.`);
|
||||
const hasSentences = list.filter(item => {
|
||||
return item.data && item.data.sentence;
|
||||
});
|
||||
for (const item of hasSentences) {
|
||||
addPerfectSentencePromptJob(item);
|
||||
await sleep(100); // 避免短时间内添加过多任务
|
||||
}
|
||||
console.log(`Added ${list.length} perfect prompt jobs to the queue.`);
|
||||
ctx.body = { message: '已添加优化任务到队列' };
|
||||
}).addTo(app);
|
||||
121
prompts/src/services/jimeng.service.ts
Normal file
121
prompts/src/services/jimeng.service.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import { Result } from '@kevisual/query'
|
||||
export interface JimengOptions {
|
||||
/** API密钥,用于认证请求 */
|
||||
apiKey: string;
|
||||
/** API基础URL */
|
||||
baseUrl: string;
|
||||
/** 请求超时时间(毫秒) */
|
||||
timeout: number;
|
||||
}
|
||||
|
||||
export interface JimengGenerateOptions {
|
||||
/** 图片生成提示词 */
|
||||
prompt: string;
|
||||
/** 使用的模型版本,默认 jimeng-4.0 */
|
||||
model?: string;
|
||||
/** 图片比例,默认 1:1 */
|
||||
ratio?: string;
|
||||
/** 图片分辨率,默认 2k */
|
||||
resolution?: string;
|
||||
}
|
||||
|
||||
interface JimengResponse {
|
||||
/** 请求创建时间戳 */
|
||||
created: number;
|
||||
/** 生成的图片列表 */
|
||||
data: Array<{
|
||||
/** 图片URL */
|
||||
url: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export class JimengService {
|
||||
private apiKey: string;
|
||||
private baseUrl: string;
|
||||
private timeout: number;
|
||||
|
||||
constructor(options: JimengOptions) {
|
||||
this.apiKey = options.apiKey;
|
||||
this.baseUrl = options.baseUrl || 'https://jimeng-api.kevisual.cn/v1';
|
||||
this.timeout = options.timeout;
|
||||
}
|
||||
|
||||
async generateImage(options: JimengGenerateOptions): Promise<Result<JimengResponse>> {
|
||||
const {
|
||||
prompt,
|
||||
model = 'jimeng-4.6',
|
||||
ratio = '1:1',
|
||||
resolution = '2k'
|
||||
} = options;
|
||||
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
||||
|
||||
const response = await fetch(`${this.baseUrl}/images/generations`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
prompt,
|
||||
ratio,
|
||||
resolution,
|
||||
}),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`jimeng API error: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const result = await response.json() as JimengResponse;
|
||||
return { code: 200, data: result };
|
||||
} catch (error: any) {
|
||||
return { code: 500, message: error.message || 'Unknown error' };
|
||||
}
|
||||
}
|
||||
|
||||
async downloadImage(url: string): Promise<Uint8Array> {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download image: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
return new Uint8Array(arrayBuffer);
|
||||
} catch (error: any) {
|
||||
clearTimeout(timeoutId);
|
||||
if (error.name === 'AbortError') {
|
||||
throw new Error('Image download timeout');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/** 获取图片过期时间 */
|
||||
async getExpiredTime(url: string): Promise<{ expiredAt: number, expired: boolean }> {
|
||||
// https://p3-dreamina-sign.byteimg.com/tos-cn-i-tb4s082cfz/c018e06ee6654dd78ccacb29eff4744e~tplv-tb4s082cfz-aigc_resize:0:0.png?lk3s=43402efa&x-expires=1767852000&x-signature=34yf37N955BP37eLaYEzKeLQn0Q%3D&format=.png
|
||||
const urlObj = new URL(url);
|
||||
let expires = urlObj.searchParams.get('x-expires');
|
||||
if (!expires) {
|
||||
expires = '0';
|
||||
}
|
||||
const expiredAt = parseInt(expires) * 1000;
|
||||
const expired = Date.now() > expiredAt;
|
||||
return { expiredAt, expired };
|
||||
}
|
||||
}
|
||||
|
||||
36
prompts/src/services/oss.service.ts
Normal file
36
prompts/src/services/oss.service.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { OssBase } from '@kevisual/oss/s3.ts';
|
||||
import { S3Client } from '@aws-sdk/client-s3'
|
||||
export type OSSOptions = {
|
||||
accessKeyId: string;
|
||||
accessKeySecret: string;
|
||||
region: string;
|
||||
bucketName: string;
|
||||
endpoint: string;
|
||||
prefix?: string;
|
||||
}
|
||||
export class OSSService extends OssBase {
|
||||
declare client: S3Client;
|
||||
endpoint: string;
|
||||
constructor(options: OSSOptions) {
|
||||
const client = new S3Client({
|
||||
region: options.region,
|
||||
endpoint: `${options.endpoint}`,
|
||||
credentials: {
|
||||
accessKeyId: options.accessKeyId,
|
||||
secretAccessKey: options.accessKeySecret,
|
||||
},
|
||||
});
|
||||
super({
|
||||
client,
|
||||
bucketName: options.bucketName,
|
||||
prefix: options.prefix || '',
|
||||
});
|
||||
this.endpoint = options.endpoint;
|
||||
}
|
||||
|
||||
getLink(objectName: string): string {
|
||||
const endpoint = this.endpoint;
|
||||
return `${endpoint}/${this.bucketName}/${this.prefix}${objectName}`;
|
||||
}
|
||||
}
|
||||
|
||||
174
prompts/src/services/pb.service.ts
Normal file
174
prompts/src/services/pb.service.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import PocketBase from 'pocketbase';
|
||||
import { EventEmitter } from 'eventemitter3'
|
||||
|
||||
type PBOptions = {
|
||||
url: string;
|
||||
token?: string;
|
||||
}
|
||||
export class PBCore {
|
||||
declare client: PocketBase;
|
||||
emitter = new EventEmitter();
|
||||
token?: string;
|
||||
constructor(options: PBOptions) {
|
||||
this.client = new PocketBase(options.url);
|
||||
this.token = options.token || '';
|
||||
if (this.token) {
|
||||
this.client.authStore.save(this.token);
|
||||
}
|
||||
}
|
||||
|
||||
async loginByPassword(email: string, password: string, admin: boolean = true) {
|
||||
const collectionName = admin ? '_superusers' : 'users';
|
||||
const authData = await this.client.collection(collectionName).authWithPassword(email, password);
|
||||
this.emitter.emit('login', authData);
|
||||
return authData;
|
||||
}
|
||||
async login({ username, password, admin = true }: { username: string; password: string, admin?: boolean }) {
|
||||
if (this.client.authStore.isValid) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
await this.loginByPassword(username, password, admin);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('PocketBase login error:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
*
|
||||
* https://pocketbase.io/docs/api-collections/#create-collection
|
||||
*
|
||||
* */
|
||||
async createCollection({ name, type, fields }: CreateCollection) {
|
||||
const collections = await this.client.collections.getFullList();
|
||||
const existing = collections.find(c => c.name === name);
|
||||
if (existing) {
|
||||
const collection = this.client.collection(name);
|
||||
const schema = await this.client.collections.getOne(name);
|
||||
return {
|
||||
schema: schema,
|
||||
existing: true,
|
||||
collection
|
||||
};
|
||||
}
|
||||
const schema = await this.client.collections.create({
|
||||
name,
|
||||
type: type ?? 'base',
|
||||
fields: fields ?? defaultFields,
|
||||
});
|
||||
const collection = this.client.collection(name);
|
||||
return {
|
||||
collection,
|
||||
schema
|
||||
}
|
||||
}
|
||||
}
|
||||
export const defaultFields: CollectionFields[] = [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'summary',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
type: 'json',
|
||||
},
|
||||
{
|
||||
name: 'data',
|
||||
type: 'json',
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
name: 'created',
|
||||
type: 'autodate',
|
||||
onCreate: true,
|
||||
},
|
||||
{
|
||||
name: 'updated',
|
||||
type: 'autodate',
|
||||
onCreate: true,
|
||||
onUpdate: true,
|
||||
}
|
||||
]
|
||||
export type CollectionFields = {
|
||||
name: string;
|
||||
type: string;
|
||||
required?: boolean;
|
||||
options?: any;
|
||||
onCreate?: boolean;
|
||||
onUpdate?: boolean;
|
||||
}
|
||||
export type CreateCollectioRule = {
|
||||
listRule?: string;
|
||||
viewRule?: string;
|
||||
/**
|
||||
* createRule: 'id = @request.auth.id',
|
||||
*/
|
||||
createRule?: string;
|
||||
updateRule?: string;
|
||||
deleteRule?: string;
|
||||
}
|
||||
export type CreateCollection = {
|
||||
name: string;
|
||||
type?: 'base' | 'viwer' | 'auth';
|
||||
fields?: CollectionFields[];
|
||||
|
||||
/**
|
||||
* viewer:
|
||||
* viewQuery: 'SELECT id, name from posts',
|
||||
*/
|
||||
viewQuery?: string;
|
||||
} & CreateCollectioRule;
|
||||
|
||||
export class PBService extends PBCore {
|
||||
collectionName = 'images_generation_tasks';
|
||||
constructor(options: PBOptions) {
|
||||
super(options);
|
||||
this.client.autoCancellation(false)
|
||||
}
|
||||
getCollection<T>(name: string) {
|
||||
return this.client.collection<T>(name);
|
||||
}
|
||||
|
||||
async initPbService() {
|
||||
const isLogin = this.client.authStore.isValid;
|
||||
console.log('PocketBase is logged in:', isLogin);
|
||||
}
|
||||
async importData(data: any[]) {
|
||||
const collection = this.getCollection(this.collectionName);
|
||||
for (const item of data) {
|
||||
await collection.create(item);
|
||||
}
|
||||
}
|
||||
get collection() {
|
||||
return this.client.collection<ImageCollection>(this.collectionName);
|
||||
}
|
||||
}
|
||||
|
||||
const ImageTaskStatus = ['提示词优化中', '计划中', '生成图片中', '图片下载中', '暂停中', '已完成', '失败'] as const;
|
||||
|
||||
type Data<T = {}> = {
|
||||
images: { type: 'jimeng' | 'tos', url: string }[];
|
||||
} & T
|
||||
export type ImageCollection<T = any> = {
|
||||
id: string;
|
||||
created: string;
|
||||
updated: string;
|
||||
title: string;
|
||||
tags: any;
|
||||
summary: string;
|
||||
description: string;
|
||||
data: Data<T>;
|
||||
status: typeof ImageTaskStatus[number];
|
||||
}
|
||||
58
prompts/src/services/storage.service.ts
Normal file
58
prompts/src/services/storage.service.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { createStorage } from 'unstorage';
|
||||
import fsDriver from 'unstorage/drivers/fs';
|
||||
|
||||
export interface PromptData {
|
||||
value: string;
|
||||
id: string;
|
||||
perfect: string;
|
||||
imageUrl?: string;
|
||||
generatedAt?: number;
|
||||
}
|
||||
|
||||
export class StorageService {
|
||||
private storage: ReturnType<typeof createStorage>;
|
||||
|
||||
constructor() {
|
||||
this.storage = createStorage({
|
||||
driver: fsDriver({ base: 'storage' }),
|
||||
});
|
||||
}
|
||||
|
||||
async get(id: string): Promise<PromptData | null> {
|
||||
const filename = id.endsWith('.json') ? id : `${id}.json`;
|
||||
const data = await this.storage.getItem<PromptData>(filename);
|
||||
return data || null;
|
||||
}
|
||||
|
||||
async getPendingPrompts(): Promise<PromptData[]> {
|
||||
const keys = await this.storage.getKeys();
|
||||
const pending: PromptData[] = [];
|
||||
|
||||
for (const key of keys) {
|
||||
if (key === 'usage.json') continue;
|
||||
|
||||
const data = await this.storage.getItem<PromptData>(key);
|
||||
if (data && !data.imageUrl) {
|
||||
pending.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
return pending;
|
||||
}
|
||||
|
||||
async update(id: string, data: Partial<PromptData>): Promise<void> {
|
||||
const filename = id.endsWith('.json') ? id : `${id}.json`;
|
||||
const existing = await this.storage.getItem<PromptData>(filename);
|
||||
|
||||
if (existing) {
|
||||
await this.storage.setItem(filename, { ...existing, ...data });
|
||||
}
|
||||
}
|
||||
|
||||
async hasImage(id: string): Promise<boolean> {
|
||||
const data = await this.get(id);
|
||||
return !!data?.imageUrl;
|
||||
}
|
||||
}
|
||||
|
||||
export const storageService = new StorageService();
|
||||
232
prompts/src/task/image-creator.job.ts
Normal file
232
prompts/src/task/image-creator.job.ts
Normal file
@@ -0,0 +1,232 @@
|
||||
import { Worker, Queue, Job } from 'bullmq';
|
||||
import { getRedisConnection } from '../module/redis.ts';
|
||||
import { pbService, jimengService, ossService, app } from '../index.ts';
|
||||
import type { ImageCollection } from '../services/pb.service.ts';
|
||||
import { updateItemStatus } from '../routes/image-update.ts';
|
||||
import { notify } from '@/module/logger.ts';
|
||||
|
||||
export const IMAGE_GENERATE_JOB = 'image-generate';
|
||||
export const IMAGE_DOWNLOAD_JOB = 'image-download';
|
||||
|
||||
// 状态常量
|
||||
export const ImageTaskStatus = {
|
||||
PENDING: '提示词优化中' as const,
|
||||
PLANNING: '计划中' as const,
|
||||
GENERATING: '生成图片中' as const,
|
||||
DOWNLOADING: '图片下载中' as const,
|
||||
PAUSED: '暂停中' as const,
|
||||
COMPLETED: '已完成' as const,
|
||||
FAILED: '失败' as const,
|
||||
};
|
||||
|
||||
// 生成图片任务的节流时间(毫秒)
|
||||
const JIMENG_THROTTLE_DELAY = 60 * 1000;
|
||||
// 下载任务最大重试次数
|
||||
const DOWNLOAD_MAX_RETRIES = 3;
|
||||
// 图片生成任务最大重试次数
|
||||
const GENERATE_MAX_RETRIES = 3;
|
||||
|
||||
export interface ImageCreatorJobData {
|
||||
itemId: string;
|
||||
prompt: string;
|
||||
collectionName?: string;
|
||||
}
|
||||
|
||||
export interface ImageGenerateJobData {
|
||||
itemId: string;
|
||||
prompt: string;
|
||||
ratio?: string;
|
||||
collectionName?: string;
|
||||
}
|
||||
|
||||
export interface ImageDownloadJobData {
|
||||
itemId: string;
|
||||
imageUrl: string;
|
||||
collectionName?: string;
|
||||
index: number;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 单独添加生成图片任务
|
||||
*/
|
||||
export async function addImageGenerateJob(item: ImageCollection): Promise<void> {
|
||||
const connection = getRedisConnection();
|
||||
const queue = new Queue(IMAGE_GENERATE_JOB, { connection });
|
||||
|
||||
const jobData: ImageGenerateJobData = {
|
||||
itemId: item.id,
|
||||
prompt: item.description || item.summary || item.title,
|
||||
collectionName: pbService.collectionName,
|
||||
};
|
||||
|
||||
await queue.add(IMAGE_GENERATE_JOB, jobData, {
|
||||
removeOnComplete: 100,
|
||||
removeOnFail: 100,
|
||||
delay: JIMENG_THROTTLE_DELAY, // 任务间隔 30 秒
|
||||
});
|
||||
|
||||
await updateItemStatus(item.id, ImageTaskStatus.GENERATING);
|
||||
// console.log(`[ImageGenerate] Job created for item: ${item.id}`);
|
||||
await queue.close();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 单独添加下载图片任务
|
||||
*/
|
||||
export async function addImageDownloadJob(
|
||||
itemId: string,
|
||||
imageUrl: string,
|
||||
index?: number
|
||||
): Promise<void> {
|
||||
const connection = getRedisConnection();
|
||||
const queue = new Queue(IMAGE_DOWNLOAD_JOB, { connection });
|
||||
const jobData: ImageDownloadJobData = {
|
||||
itemId,
|
||||
imageUrl,
|
||||
collectionName: pbService.collectionName,
|
||||
index: index ?? 0
|
||||
};
|
||||
|
||||
// 使用 bullmq 内置重试,指数退避
|
||||
await queue.add(IMAGE_DOWNLOAD_JOB, jobData, {
|
||||
attempts: DOWNLOAD_MAX_RETRIES,
|
||||
backoff: {
|
||||
type: 'exponential',
|
||||
delay: 200, // 初始 200 毫秒
|
||||
},
|
||||
removeOnComplete: 100,
|
||||
removeOnFail: 100,
|
||||
});
|
||||
|
||||
await updateItemStatus(itemId, ImageTaskStatus.DOWNLOADING);
|
||||
// console.log(`[ImageDownload] Job created for item: ${itemId}`);
|
||||
await queue.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行独立的下载 worker
|
||||
*/
|
||||
export async function runImageDownloadWorker(): Promise<void> {
|
||||
const connection = getRedisConnection();
|
||||
|
||||
const worker = new Worker(
|
||||
IMAGE_DOWNLOAD_JOB,
|
||||
async (job: Job<ImageDownloadJobData>) => {
|
||||
const { itemId, imageUrl, index } = job.data;
|
||||
const attemptsMade = job.attemptsMade;
|
||||
console.log(`[ImageDownload] Processing item: ${itemId}, attempt: ${attemptsMade + 1}/${DOWNLOAD_MAX_RETRIES}`);
|
||||
|
||||
try {
|
||||
const imageBuffer = await jimengService.downloadImage(imageUrl);
|
||||
const filename = `generated_${itemId}_${index}_${Date.now()}.png`;
|
||||
await ossService.putObject(filename, imageBuffer);
|
||||
const ossUrl = ossService.getLink(filename)
|
||||
console.log(`[ImageDownload] Image uploaded to OSS: ${ossUrl}`);
|
||||
|
||||
const imageData = { type: 'tos' as const, url: ossUrl };
|
||||
await updateItemStatus(itemId, ImageTaskStatus.COMPLETED, {
|
||||
data: {
|
||||
images: [imageData],
|
||||
},
|
||||
});
|
||||
|
||||
return { success: true, ossUrl };
|
||||
} catch (error: any) {
|
||||
console.error(`[ImageDownload] Error: ${error.message}`);
|
||||
|
||||
// 重试次数用尽,暂停任务
|
||||
if (job.attemptsMade >= DOWNLOAD_MAX_RETRIES - 1) {
|
||||
await updateItemStatus(itemId, ImageTaskStatus.PAUSED);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
{
|
||||
connection,
|
||||
concurrency: 1, // 避免更新冲突
|
||||
lockDuration: 60000 * 5, // 锁持续时间 5分钟
|
||||
stalledInterval: 30000, // 每30秒检查一次 stalled
|
||||
} as any
|
||||
);
|
||||
|
||||
worker.on('completed', (job) => {
|
||||
console.log(`[ImageDownload] Job completed: ${job.id}`);
|
||||
});
|
||||
|
||||
worker.on('failed', (job, err) => {
|
||||
console.error(`[ImageDownload] Job failed: ${job?.id}, error: ${err.message}`);
|
||||
notify.notify(`[ImageDownload] \nJob failed: ${job?.id}, error: ${err.message}\n Job data: ${JSON.stringify(job?.data)}`);
|
||||
});
|
||||
|
||||
console.log('[ImageDownload] Worker 开始运行');
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行图片生成 worker(使用 jimeng API)
|
||||
*/
|
||||
export async function runImageGenerateWorker(): Promise<void> {
|
||||
const connection = getRedisConnection();
|
||||
|
||||
const worker = new Worker(
|
||||
IMAGE_GENERATE_JOB,
|
||||
async (job: Job<ImageGenerateJobData>) => {
|
||||
const { itemId, prompt, ratio = '9:16' } = job.data;
|
||||
const attemptsMade = job.attemptsMade;
|
||||
console.log(`[ImageGenerate] Processing item: ${itemId}, attempt: ${attemptsMade + 1}/${GENERATE_MAX_RETRIES}`);
|
||||
|
||||
try {
|
||||
// 调用 jimeng API 生成图片
|
||||
const result = await jimengService.generateImage({
|
||||
prompt,
|
||||
ratio: ratio,
|
||||
});
|
||||
|
||||
if (result.code !== 200 || !result.data?.data?.length) {
|
||||
throw new Error(result.message || 'Failed to generate image');
|
||||
}
|
||||
const images = result.data.data;
|
||||
for (const [index, img] of images.entries()) {
|
||||
console.log(`[ImageGenerate] Image generated: ${img.url}`);
|
||||
// 生成成功后,添加下载任务
|
||||
await addImageDownloadJob(itemId, img.url, index);
|
||||
}
|
||||
// 更新状态为下载中
|
||||
await updateItemStatus(itemId, ImageTaskStatus.DOWNLOADING, {
|
||||
data: { images: images.map(img => ({ type: 'jimeng' as const, url: img.url })) },
|
||||
});
|
||||
|
||||
return { success: true, images };
|
||||
} catch (error: any) {
|
||||
console.error(`[ImageGenerate] Error: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
{
|
||||
connection,
|
||||
concurrency: 1, // jimeng API 有节流限制,设置为 1
|
||||
lockDuration: 60000 * 5, // 锁持续时间 5分钟
|
||||
stalledInterval: 30000, // 每30秒检查一次 stalled
|
||||
}
|
||||
);
|
||||
|
||||
worker.on('completed', (job) => {
|
||||
console.log(`[ImageGenerate] Job completed: ${job.id}`);
|
||||
});
|
||||
|
||||
worker.on('failed', (job, err) => {
|
||||
console.error(`[ImageGenerate] Job failed: ${job?.id}, error: ${err.message}`);
|
||||
notify.notify(`[ImageGenerate] \nJob failed: ${job?.id}, error: ${err.message}\n Job data: ${JSON.stringify(job?.data)}`);
|
||||
if (job && job.attemptsMade >= GENERATE_MAX_RETRIES - 1) {
|
||||
worker.close();
|
||||
notify.notify(`[ImageGenerate] Worker stopped after reaching max retries for item ${job.data.itemId}`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('[ImageGenerate] Worker 开始运行');
|
||||
}
|
||||
|
||||
export { updateItemStatus };
|
||||
143
prompts/src/task/perfect-prompt.job.ts
Normal file
143
prompts/src/task/perfect-prompt.job.ts
Normal file
@@ -0,0 +1,143 @@
|
||||
import { Worker, Job } from 'bullmq';
|
||||
import { getRedisConnection } from '../module/redis.ts';
|
||||
import { Prompt, pbService, ai } from '../index.ts';
|
||||
import type { ImageCollection } from '../services/pb.service.ts';
|
||||
import { updateItemStatus } from '../routes/image-update.ts';
|
||||
import { Queue } from 'bullmq';
|
||||
import { notify } from '@/module/logger.ts';
|
||||
import { addImageGenerateJob } from './image-creator.job.ts';
|
||||
|
||||
export const PERFECT_PROMPT_JOB = 'perfect-prompt';
|
||||
|
||||
// 状态常量
|
||||
export const PerfectPromptStatus = {
|
||||
PENDING: '提示词优化中' as const,
|
||||
PLANNING: '计划中' as const,
|
||||
FAILED: '失败' as const,
|
||||
};
|
||||
|
||||
// 最大重试次数
|
||||
const MAX_RETRIES = 3;
|
||||
|
||||
export interface PerfectPromptJobData {
|
||||
itemId: string;
|
||||
prompt: string;
|
||||
collectionName?: string;
|
||||
data?: Record<string, any>;
|
||||
}
|
||||
|
||||
// 优化提示词的模板
|
||||
const DEFAULT_PERFECT_PROMPT = `请你将以下提示词进行完善,使其更加详细和具体,适合用于生成高质量的像素艺术图像。要求如下:
|
||||
1. 只返回完善后的提示词,不要包含任何多余的内容或解释。
|
||||
2. 确保提示词专注于像素艺术风格,包括但不限于像素化角色、场景和物体的描述。
|
||||
3. 使用具体的细节来增强提示词的表现力,例如颜色、构图、光影效果等。
|
||||
4. 避免使用与像素艺术无关的术语或描述。
|
||||
5. 保持提示词的简洁性,避免过于冗长,但要确保信息量充足。
|
||||
6. 如果需要颜色,需要整个图像的颜色更少的描述,而不是复杂的颜色细节, 背景默认纯蓝色。
|
||||
7. 使用中文进行描述。
|
||||
`;
|
||||
|
||||
/**
|
||||
* 单独添加优化提示词任务
|
||||
*/
|
||||
export async function addPerfectPromptJob(item: ImageCollection): Promise<void> {
|
||||
const connection = getRedisConnection();
|
||||
const queue = new Queue(PERFECT_PROMPT_JOB, { connection });
|
||||
|
||||
const jobData: PerfectPromptJobData = {
|
||||
itemId: item.id,
|
||||
prompt: item.description || item.summary || item.title || '',
|
||||
collectionName: pbService.collectionName,
|
||||
};
|
||||
|
||||
await queue.add(PERFECT_PROMPT_JOB, jobData, {
|
||||
attempts: MAX_RETRIES,
|
||||
backoff: {
|
||||
type: 'exponential',
|
||||
delay: 2000,
|
||||
},
|
||||
removeOnComplete: 100,
|
||||
removeOnFail: 100,
|
||||
});
|
||||
|
||||
await updateItemStatus(item.id, PerfectPromptStatus.PENDING);
|
||||
await queue.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 运行优化提示词 worker
|
||||
*/
|
||||
export async function runPerfectPromptWorker(): Promise<void> {
|
||||
const connection = getRedisConnection();
|
||||
// 获取环境变量中的 API key
|
||||
const worker = new Worker(
|
||||
PERFECT_PROMPT_JOB,
|
||||
async (job: Job<PerfectPromptJobData>) => {
|
||||
const { itemId, prompt } = job.data;
|
||||
const attemptsMade = job.attemptsMade;
|
||||
console.log(`[PerfectPrompt] Processing item: ${itemId}, attempt: ${attemptsMade + 1}/${MAX_RETRIES}`);
|
||||
try {
|
||||
if (!prompt) {
|
||||
throw new Error('Prompt is empty');
|
||||
}
|
||||
const promptTool = new Prompt({ perfectPrompt: DEFAULT_PERFECT_PROMPT });
|
||||
await ai.chat([
|
||||
{
|
||||
role: 'user',
|
||||
content: promptTool.perfect(prompt),
|
||||
},
|
||||
]);
|
||||
const perfectText = promptTool.clearPerfectTags(ai.responseText);
|
||||
|
||||
if (!perfectText) {
|
||||
throw new Error('Generated perfect prompt is empty');
|
||||
}
|
||||
|
||||
console.log(`[PerfectPrompt] Perfect prompt generated for item: ${itemId}`);
|
||||
|
||||
// 更新状态为已完成,并保存优化后的提示词
|
||||
await updateItemStatus(itemId, PerfectPromptStatus.PLANNING, {
|
||||
description: perfectText,
|
||||
});
|
||||
// 任务完成,把任务抛给下一个图片生成队列
|
||||
const item = await pbService.collection.getOne(itemId);
|
||||
if (item) {
|
||||
addImageGenerateJob(item)
|
||||
}
|
||||
|
||||
return { success: true, perfectPrompt: perfectText };
|
||||
} catch (error: any) {
|
||||
console.error(`[PerfectPrompt] Error: ${error.message}`);
|
||||
|
||||
// 重试次数用尽,标记为失败
|
||||
if (job.attemptsMade >= MAX_RETRIES - 1) {
|
||||
await updateItemStatus(itemId, PerfectPromptStatus.FAILED);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
{
|
||||
connection,
|
||||
concurrency: 2,
|
||||
lockDuration: 60000 * 5, // 锁持续时间 5分钟
|
||||
stalledInterval: 30000, // 每30秒检查一次 stalled
|
||||
}
|
||||
);
|
||||
|
||||
worker.on('completed', (job) => {
|
||||
console.log(`[PerfectPrompt] Job completed: ${job.id}`);
|
||||
});
|
||||
|
||||
worker.on('failed', (job, err) => {
|
||||
console.error(`[PerfectPrompt] Job failed: ${job?.id}, error: ${err.message}`);
|
||||
notify.notify(`[PerfectPrompt] \nJob failed: ${job?.id}, error: ${err.message}\n Job data: ${JSON.stringify(job?.data)}`);
|
||||
if (job && job.attemptsMade >= MAX_RETRIES - 1) {
|
||||
worker.close();
|
||||
notify.notify(`[PerfectPrompt] Worker stopped after reaching max retries for item ${job.data.itemId}`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('[PerfectPrompt] Worker started');
|
||||
}
|
||||
|
||||
121
prompts/src/task/perfect-sentence.job.ts
Normal file
121
prompts/src/task/perfect-sentence.job.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import { Worker, Job } from 'bullmq';
|
||||
import { getRedisConnection } from '../module/redis.ts';
|
||||
import { Prompt, pbService, ai, app } from '../index.ts';
|
||||
import type { ImageCollection } from '../services/pb.service.ts';
|
||||
import { updateItemStatus } from '../routes/image-update.ts';
|
||||
import { Queue } from 'bullmq';
|
||||
import { notify } from '@/module/logger.ts';
|
||||
import { addImageGenerateJob } from './image-creator.job.ts';
|
||||
import { PerfectPromptJobData } from './perfect-prompt.job.ts';
|
||||
import { SentenceImage } from '@/module/sentence-image.ts';
|
||||
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
export const PERFECT_SENTENCE_PROMPT_JOB = 'perfect-sentence-prompt';
|
||||
|
||||
// 最大重试次数
|
||||
const MAX_RETRIES = 3;
|
||||
|
||||
// 状态常量
|
||||
export const PerfectPromptStatus = {
|
||||
PENDING: '提示词优化中' as const,
|
||||
PLANNING: '计划中' as const,
|
||||
FAILED: '失败' as const,
|
||||
};
|
||||
|
||||
export async function addPerfectSentencePromptJob(item: ImageCollection): Promise<void> {
|
||||
const connection = getRedisConnection();
|
||||
const queue = new Queue(PERFECT_SENTENCE_PROMPT_JOB, { connection });
|
||||
const jobData: PerfectPromptJobData = {
|
||||
itemId: item.id,
|
||||
prompt: item.description || item.summary || item.title || '',
|
||||
collectionName: pbService.collectionName,
|
||||
data: item.data
|
||||
};
|
||||
|
||||
await queue.add(PERFECT_SENTENCE_PROMPT_JOB, jobData, {
|
||||
attempts: MAX_RETRIES,
|
||||
backoff: {
|
||||
type: 'exponential',
|
||||
delay: 2000,
|
||||
},
|
||||
removeOnComplete: 100,
|
||||
removeOnFail: 100,
|
||||
});
|
||||
|
||||
await updateItemStatus(item.id, PerfectPromptStatus.PENDING);
|
||||
await queue.close();
|
||||
}
|
||||
|
||||
export const runPerfectSentencePromptWorker = () => {
|
||||
const connection = getRedisConnection();
|
||||
const worker = new Worker<PerfectPromptJobData>(
|
||||
PERFECT_SENTENCE_PROMPT_JOB,
|
||||
async (job: Job<PerfectPromptJobData>) => {
|
||||
const { itemId, prompt, data } = job.data;
|
||||
const perfect = new SentenceImage();
|
||||
try {
|
||||
let content = ''
|
||||
if (data && data.sentence) {
|
||||
const { prompt, ...rest } = data.sentence || {};
|
||||
if (rest) {
|
||||
content = JSON.stringify(rest);
|
||||
}
|
||||
}
|
||||
if (!content) {
|
||||
content = prompt;
|
||||
}
|
||||
if (!content) {
|
||||
notify.notify(`[Sentence] 提示词优化任务跳过,条目 ${itemId}: 无内容可优化。`);
|
||||
await updateItemStatus(itemId, PerfectPromptStatus.FAILED);
|
||||
return;
|
||||
}
|
||||
|
||||
const perfectText = perfect.perfect(content);
|
||||
const result = await ai.chat([], {
|
||||
messages: [{ role: 'user', content: perfectText }],
|
||||
// model: 'qwen-plus',
|
||||
// enable_thinking: true
|
||||
model: 'qwen-turbo',
|
||||
});
|
||||
const perfectPrompt = perfect.clearPerfectTags(ai.responseText!) || '';
|
||||
console.log(`[Sentence] 提示词优化 ${itemId}:\n`, perfectPrompt);
|
||||
|
||||
await updateItemStatus(itemId, '提示词优化完成', {
|
||||
description: perfectPrompt
|
||||
});
|
||||
await sleep(500); // 确保数据已保存
|
||||
// 任务完成,把任务抛给下一个图片生成队列
|
||||
const item = await pbService.collection.getOne(itemId);
|
||||
if (item) {
|
||||
addImageGenerateJob(item)
|
||||
}
|
||||
|
||||
return { success: true, perfectPrompt: perfectText };
|
||||
} catch (error: any) {
|
||||
notify.notify(`[Sentence] 提示词优化任务失败,条目 ${itemId}: ${error.message}`);
|
||||
await updateItemStatus(itemId, PerfectPromptStatus.FAILED);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
{
|
||||
connection,
|
||||
lockDuration: 60000 * 5, // 锁持续时间 5分钟
|
||||
stalledInterval: 30000, // 每30秒检查一次 stalled
|
||||
}
|
||||
);
|
||||
|
||||
worker.on('completed', (job) => {
|
||||
// notify.notify(`[Sentence] Perfect Prompt Job Completed for item ${job.data.itemId}`);
|
||||
console.log(`[Sentence] Perfect Prompt Job Completed for item ${job.data.itemId}`);
|
||||
});
|
||||
|
||||
worker.on('failed', (job, err) => {
|
||||
notify.notify(`[Sentence] 提示词优化任务失败,条目${job?.data.itemId}: ${err.message}`);
|
||||
// 如果是第三次失败,停止worker;
|
||||
if (job && job.attemptsMade >= MAX_RETRIES - 1) {
|
||||
worker.close();
|
||||
notify.notify(`[Sentence] 提示词优化工作者在条目 ${job.data.itemId} 达到最大重试次数后停止`);
|
||||
}
|
||||
});
|
||||
console.log('[Sentence] Worker 开始运行');
|
||||
return worker;
|
||||
};
|
||||
12
prompts/src/workers/index.ts
Normal file
12
prompts/src/workers/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { runImageDownloadWorker, runImageGenerateWorker } from '../task/image-creator.job.ts';
|
||||
import { runPerfectSentencePromptWorker } from '../task/perfect-sentence.job.ts';
|
||||
|
||||
runImageDownloadWorker();
|
||||
runImageGenerateWorker();
|
||||
runPerfectSentencePromptWorker();
|
||||
// 运行半小时后停止
|
||||
|
||||
setTimeout(() => {
|
||||
console.log('Stop timeed', new Date().toISOString());
|
||||
process.exit(0);
|
||||
}, 10 * 60 * 60 * 1000); // 10 hours in milliseconds
|
||||
22
prompts/test/common.ts
Normal file
22
prompts/test/common.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { app } from '../src/index.ts'
|
||||
import { config } from '../src/index.ts'
|
||||
export {
|
||||
app
|
||||
}
|
||||
|
||||
// const res = await app.run({
|
||||
// path: 'image-creator',
|
||||
// key: 'create-task',
|
||||
// })
|
||||
|
||||
// console.log('Route run result:', res)
|
||||
|
||||
|
||||
// const res = await app.run({
|
||||
// path: 'image-creator',
|
||||
// key: 'batch-update-tags',
|
||||
// })
|
||||
|
||||
// console.log('Route run result:', res)
|
||||
|
||||
console.log('typeof config.REDIS_PORT:', typeof config.REDIS_PORT);
|
||||
93
prompts/test/generate-perfect.ts
Normal file
93
prompts/test/generate-perfect.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { Kevisual } from "@kevisual/ai";
|
||||
import { useConfig } from '@kevisual/use-config'
|
||||
import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import { createStorage } from "unstorage";
|
||||
import { Prompt } from "../src/index.ts";
|
||||
import fsDriver from "unstorage/drivers/fs";
|
||||
|
||||
const promptPath = path.join(process.cwd(), "./docs/prompts-01.json");
|
||||
|
||||
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
const storage = createStorage({
|
||||
driver: fsDriver({ base: 'storage' }),
|
||||
});
|
||||
const config = useConfig();
|
||||
const kevisual = new Kevisual({
|
||||
token: config.KEVISUAL_NEW_API_KEY || "",
|
||||
model: "qwen-turbo"
|
||||
});
|
||||
|
||||
if (!fs.existsSync(promptPath)) {
|
||||
console.error("Prompt file not found:", promptPath);
|
||||
process.exit(1);
|
||||
}
|
||||
const prompts = JSON.parse(fs.readFileSync(promptPath, "utf-8")) as { key: string, value: string, id: string }[];
|
||||
|
||||
const currentUsage = (await storage.getItem("usage.json") as any) || { prompt_tokens: 0, total_tokens: 0, completion_tokens: 0 };
|
||||
|
||||
console.log("Current usage:", currentUsage);
|
||||
async function generatePerfectPrompts() {
|
||||
for (const promptData of prompts) {
|
||||
const id = promptData.id + '.json';
|
||||
const has = await storage.get(id);
|
||||
if (has) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const perfectPrompt = `请你将以下提示词进行完善,使其更加详细和具体,适合用于生成高质量的像素艺术图像。要求如下:
|
||||
1. 只返回完善后的提示词,不要包含任何多余的内容或解释。
|
||||
2. 确保提示词专注于像素艺术风格,包括但不限于像素化角色、场景和物体的描述。
|
||||
3. 使用具体的细节来增强提示词的表现力,例如颜色、构图、光影效果等。
|
||||
4. 避免使用与像素艺术无关的术语或描述。
|
||||
5. 保持提示词的简洁性,避免过于冗长,但要确保信息量充足。
|
||||
6. 如果需要颜色,需要整个图像的颜色更少的描述,而不是复杂的颜色细节, 背景默认纯蓝色。
|
||||
7. 使用中文进行描述。
|
||||
`;
|
||||
const prompt = new Prompt({ perfectPrompt });
|
||||
const result = await kevisual.chat([
|
||||
{
|
||||
role: "user",
|
||||
content: prompt.perfect(promptData.value),
|
||||
}
|
||||
])
|
||||
const text = prompt.clearPerfectTags(kevisual.responseText);
|
||||
await storage.setItem(id, {
|
||||
value: promptData.value,
|
||||
id: promptData.id,
|
||||
perfect: text,
|
||||
});
|
||||
|
||||
await sleep(2000); // Avoid rate limits
|
||||
const _usage = kevisual.getChatUsage()
|
||||
console.log("Generated perfect prompt for:", promptData.id, _usage?.total_tokens);
|
||||
currentUsage.total_tokens += _usage?.total_tokens || 0;
|
||||
currentUsage.prompt_tokens += _usage?.prompt_tokens || 0;
|
||||
currentUsage.completion_tokens += _usage?.completion_tokens || 0;
|
||||
// Update usage
|
||||
await storage.setItem("usage.json", {
|
||||
prompt_tokens: currentUsage.prompt_tokens,
|
||||
total_tokens: currentUsage.total_tokens,
|
||||
completion_tokens: currentUsage.completion_tokens,
|
||||
});
|
||||
console.log('优化的提示词', text);
|
||||
} catch (error) {
|
||||
console.error("Error generating perfect prompt for:", promptData, error);
|
||||
// 如果是超时错误,等待一段时间后继续
|
||||
if ((error as any)?.message?.includes("timeout")) {
|
||||
console.log("Timeout occurred, waiting for 30 seconds before retrying...");
|
||||
await sleep(10000);
|
||||
continue;
|
||||
} else {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
generatePerfectPrompts();
|
||||
|
||||
// bun test/generate-perfect.ts
|
||||
// pm2 start --name "generate-perfect" --interpreter=bun -- test/generate-perfect.ts
|
||||
57
prompts/test/generate-sentence.ts
Normal file
57
prompts/test/generate-sentence.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { SentenceGenerator } from '../src/module/sentence-generator.ts';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const outputPath = join(__dirname, '../data/sentence-01.json');
|
||||
|
||||
console.log('🚀 开始生成1000条哲理句...\n');
|
||||
|
||||
const generator = new SentenceGenerator({
|
||||
count: 1000,
|
||||
outputFormat: 'json',
|
||||
withTags: true,
|
||||
templateTypes: ['对比', '因果', '隐喻', '判断']
|
||||
});
|
||||
|
||||
const results = generator.generateAndOutput() as any[];
|
||||
|
||||
// 添加元信息
|
||||
const outputData = {
|
||||
meta: {
|
||||
total: results.length,
|
||||
generatedAt: new Date().toISOString(),
|
||||
generator: 'sentence-generator.ts',
|
||||
version: '1.0'
|
||||
},
|
||||
sentences: results
|
||||
};
|
||||
|
||||
// 写入文件
|
||||
writeFileSync(outputPath, JSON.stringify(outputData, null, 2), 'utf-8');
|
||||
|
||||
console.log(`✅ 成功生成 ${results.length} 条句子`);
|
||||
console.log(`📁 输出到: ${outputPath}\n`);
|
||||
|
||||
// 打印统计信息
|
||||
console.log('📊 主题分布统计:');
|
||||
const stats = generator.getStats();
|
||||
const sortedStats = Object.entries(stats)
|
||||
.sort(([, a], [, b]) => b - a)
|
||||
.slice(0, 10);
|
||||
|
||||
sortedStats.forEach(([theme, count]) => {
|
||||
const bar = '█'.repeat(Math.floor(count / 10));
|
||||
console.log(` ${theme.padEnd(6)}: ${count.toString().padStart(4)} ${bar}`);
|
||||
});
|
||||
|
||||
console.log(`\n📋 模板类型分布:`);
|
||||
const templateCount: Record<string, number> = {};
|
||||
results.forEach((s: any) => {
|
||||
const type = s.template.split(/[\d]/)[0] || s.template;
|
||||
templateCount[type] = (templateCount[type] || 0) + 1;
|
||||
});
|
||||
Object.entries(templateCount).forEach(([type, count]) => {
|
||||
console.log(` ${type}: ${count} 条`);
|
||||
});
|
||||
37
prompts/test/import-sentence.ts
Normal file
37
prompts/test/import-sentence.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { app } from './common.ts';
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const sentence = path.join(process.cwd(), 'data', 'sentence-01-optimized.json');
|
||||
|
||||
const data = JSON.parse(fs.readFileSync(sentence, 'utf-8'));
|
||||
|
||||
async function run() {
|
||||
const sentences = data?.sentences || [];
|
||||
console.log(`Importing ${sentences.length} sentences...`);
|
||||
const res = await app.run({
|
||||
path: 'image-creator',
|
||||
// key: 'create-sentence-list',
|
||||
key: 'fix-sentences',
|
||||
payload: {
|
||||
data: sentences
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Import sentence result:', res);
|
||||
}
|
||||
|
||||
// await run();
|
||||
|
||||
async function run2() {
|
||||
const sentences = data?.sentences || [];
|
||||
const res = await app.run({
|
||||
path: 'image-creator',
|
||||
key: 'perfect-sentence',
|
||||
});
|
||||
|
||||
console.log('Import sentence result:', res);
|
||||
}
|
||||
|
||||
await run2();
|
||||
18
prompts/test/jimeng.ts
Normal file
18
prompts/test/jimeng.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { JimengService } from '../src/services/jimeng.service.js';
|
||||
import { useConfig } from '@kevisual/use-config';
|
||||
const config = useConfig();
|
||||
|
||||
export const jimengService = new JimengService({
|
||||
apiKey: config.JIMENG_API_KEY,
|
||||
baseUrl: config.JIMENG_API_URL,
|
||||
timeout: parseInt(config.JIMENG_TIMEOUT || '30000'),
|
||||
});
|
||||
|
||||
const createImage = async () => {
|
||||
const response = await jimengService.generateImage({
|
||||
prompt: 'A beautiful landscape with mountains and a river, in the style of a watercolor painting',
|
||||
});
|
||||
console.log('Generated Image URL:', response);
|
||||
};
|
||||
|
||||
createImage();
|
||||
66
prompts/test/pb.ts
Normal file
66
prompts/test/pb.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { createStorage } from 'unstorage';
|
||||
import { config } from '../src/app.ts'
|
||||
|
||||
import { PBService } from '../src/services/pb.service.ts'
|
||||
import path from "node:path";
|
||||
import fsDriver from "unstorage/drivers/fs";
|
||||
|
||||
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
const storage = createStorage({
|
||||
driver: fsDriver({ base: 'storage' }),
|
||||
});
|
||||
const pbService = new PBService({
|
||||
url: config.POCKETBASE_URL,
|
||||
token: config.POCKETBASE_TOKEN,
|
||||
});
|
||||
|
||||
async function main() {
|
||||
// const listStorage = await storage.getKeys();
|
||||
// const keys = listStorage.filter(key => key !== 'usage.json');
|
||||
// for (const key of keys) {
|
||||
// const value = await storage.getItem<any>(key);
|
||||
// console.log(`Generating PB record for key: ${value}`, value);
|
||||
// const { id, perfect: description, value: summary } = value;
|
||||
// pbService.collection.create({
|
||||
// title: '',
|
||||
// summary,
|
||||
// description,
|
||||
// tags: [],
|
||||
// data: {},
|
||||
// status: '计划中',
|
||||
// });
|
||||
// console.log(`Created record for prompt ID: ${id}`);
|
||||
// await sleep(100); // To avoid hitting rate limits
|
||||
// }
|
||||
|
||||
// const list = await pbService.collection.getFullList({
|
||||
// sort: '-created',
|
||||
// fields: 'id,title,summary,description,tags,status',
|
||||
// })
|
||||
// console.log('PocketBase Records:', list.length);
|
||||
|
||||
// const b = await pbService.client.collections.create({
|
||||
// name: 'exampleBase',
|
||||
// type: 'base',
|
||||
// fields: [
|
||||
// {
|
||||
// name: 'title',
|
||||
// type: 'text',
|
||||
// required: true,
|
||||
// min: 10,
|
||||
// },
|
||||
// {
|
||||
// name: 'status',
|
||||
// type: 'bool',
|
||||
// },
|
||||
// ],
|
||||
// });
|
||||
// console.log('Created collection:', b);
|
||||
|
||||
const c = await pbService.createCollection({
|
||||
name: 'exampleBase',
|
||||
});
|
||||
console.log('Created collection via PBService:', c);
|
||||
}
|
||||
main();
|
||||
48
prompts/test/perfect-sentence-image.ts
Normal file
48
prompts/test/perfect-sentence-image.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { ai } from '../src/index.ts'
|
||||
import { SentenceImage } from '../src/module/sentence-image.ts';
|
||||
|
||||
|
||||
export async function generatePerfectImage() {
|
||||
const sentenceImage = new SentenceImage();
|
||||
// const content = JSON.stringify({
|
||||
// "text": "选择像锁,终将奔跑。",
|
||||
// "theme": "选择",
|
||||
// "template": "{主}像{意象},终将{动}。",
|
||||
// "templateType": "隐喻",
|
||||
// "tags": [
|
||||
// "选择",
|
||||
// "隐喻",
|
||||
// "后悔",
|
||||
// "锁",
|
||||
// "奔跑"
|
||||
// ],
|
||||
// "index": 7,
|
||||
// "optimized": "选择如秤,终需掂量。"
|
||||
// });
|
||||
const content = JSON.stringify({
|
||||
"text": "人生如逆旅,我亦是行人。",
|
||||
"theme": "人生",
|
||||
"template": "人生如{意象},我亦是{身份}。",
|
||||
"templateType": "隐喻",
|
||||
"tags": [
|
||||
"人生",
|
||||
"隐喻",
|
||||
"旅途",
|
||||
"行人"
|
||||
],
|
||||
"index": 1,
|
||||
"optimized": "人生似长河,我自是过客。"
|
||||
});
|
||||
const prompt = sentenceImage.perfect(content);
|
||||
const response = await ai.chat([], {
|
||||
messages: [{ role: "user", content: prompt }],
|
||||
// model: 'qwen-turbo',
|
||||
// model: 'doubao-seed-1-6-251015',
|
||||
model: 'qwen-plus',
|
||||
enable_thinking: true
|
||||
})
|
||||
console.log('生成的海报设计方案:\n', response.choices[0].message.content);
|
||||
return response.choices[0].message.content;
|
||||
}
|
||||
|
||||
generatePerfectImage()
|
||||
90
prompts/test/perfect-sentence.ts
Normal file
90
prompts/test/perfect-sentence.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { SentencePerfect } from '../src/module/sentence-perfect.ts';
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { Kevisual } from '@kevisual/ai';
|
||||
import { config } from '../src/module/config.ts';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// 配置
|
||||
const INPUT_PATH = join(__dirname, '../data/sentence-01.json');
|
||||
const OUTPUT_PATH = join(__dirname, '../data/sentence-01-optimized.json');
|
||||
|
||||
// 读取原始句子
|
||||
const data = JSON.parse(readFileSync(INPUT_PATH, 'utf-8'));
|
||||
const sentences = data.sentences || data;
|
||||
|
||||
console.log(`📖 加载了 ${sentences.length} 条句子\n`);
|
||||
|
||||
// 创建 AI 实例
|
||||
const ai = new Kevisual({
|
||||
apiKey: config.KEVISUAL_NEW_API_KEY || '',
|
||||
model: 'qwen-turbo'
|
||||
});
|
||||
|
||||
// 创建优化器
|
||||
const perfect = new SentencePerfect({
|
||||
ai,
|
||||
concurrency: 3, // 并发数
|
||||
retryTimes: 2, // 重试次数
|
||||
|
||||
// 进度回调
|
||||
onProgress: (progress) => {
|
||||
const { current, total, percentage, successCount, errorCount } = progress;
|
||||
process.stdout.write(`\r🔄 进度: ${current}/${total} (${percentage}%) | 成功: ${successCount} | 失败: ${errorCount}`);
|
||||
},
|
||||
|
||||
// 成功回调
|
||||
onSuccess: (result) => {
|
||||
if (result.index % 10 === 0) {
|
||||
console.log(`\n✅ ${result.index}: "${result.text}"`);
|
||||
console.log(` → "${result.optimized}"`);
|
||||
}
|
||||
},
|
||||
|
||||
// 失败回调
|
||||
onError: (error) => {
|
||||
console.log(`\n❌ ${error.index}: ${error.error}`);
|
||||
}
|
||||
});
|
||||
|
||||
// 准备数据 - 只处理前20条作为测试
|
||||
// const items = sentences.slice(0, 2).map((s: any, i: number) => ({
|
||||
// ...s,
|
||||
// index: s.index || i + 1
|
||||
// }));
|
||||
const items = sentences;
|
||||
console.log('🚀 开始优化句子...\n');
|
||||
|
||||
// 执行优化
|
||||
const startTime = Date.now();
|
||||
const results = await perfect.perfectBatch(items);
|
||||
const elapsed = Date.now() - startTime;
|
||||
|
||||
console.log(`\n\n✅ 完成!耗时: ${(elapsed / 1000).toFixed(2)}秒`);
|
||||
|
||||
// 统计
|
||||
const successResults = results.filter(r => r.optimized !== r.text);
|
||||
console.log(` 成功: ${successResults.length}/${results.length}`);
|
||||
|
||||
// 保存结果
|
||||
const output = {
|
||||
meta: {
|
||||
total: results.length,
|
||||
success: successResults.length,
|
||||
optimizedAt: new Date().toISOString()
|
||||
},
|
||||
sentences: results
|
||||
};
|
||||
|
||||
writeFileSync(OUTPUT_PATH, JSON.stringify(output, null, 2), 'utf-8');
|
||||
console.log(`📁 结果已保存到: ${OUTPUT_PATH}`);
|
||||
|
||||
// 显示示例
|
||||
console.log('\n📝 优化示例:');
|
||||
successResults.slice(0, 5).forEach((r, i) => {
|
||||
console.log(`\n${i + 1}. [${r.theme}]`);
|
||||
console.log(` 原: ${r.text}`);
|
||||
console.log(` 优: ${r.optimized}`);
|
||||
});
|
||||
28
prompts/test/random-pony.ts
Normal file
28
prompts/test/random-pony.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { PromptGenerator, saveToFile } from "../src/index.ts";
|
||||
import { source } from "./source.ts";
|
||||
import path from 'node:path';
|
||||
|
||||
const coreValue = [
|
||||
"像素艺术",
|
||||
"矢量插画",
|
||||
"线条艺术",
|
||||
"可爱",
|
||||
"日式可爱",
|
||||
"Q版",
|
||||
"萌",
|
||||
"简约",
|
||||
"极简主义",
|
||||
"干净线条",
|
||||
"扁平色彩",
|
||||
]
|
||||
const promptGenerator = new PromptGenerator({
|
||||
count: 1000,
|
||||
source: source,
|
||||
additionalValues: coreValue,
|
||||
endString: '纯色背景'
|
||||
});
|
||||
|
||||
const promptMap = promptGenerator.generateMap();
|
||||
|
||||
const outputPath = path.join(process.cwd(), "./docs/prompts.json");
|
||||
await saveToFile(promptMap, outputPath);
|
||||
155
prompts/test/source.ts
Normal file
155
prompts/test/source.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
export const source = {
|
||||
"subject": [
|
||||
"圆滚滚的马",
|
||||
"穿衣服的马",
|
||||
"胖乎乎的小马",
|
||||
"戴帽子的马",
|
||||
"毛茸茸的斑马",
|
||||
"拿气球的独角兽",
|
||||
"瘦长的马",
|
||||
"Q版马",
|
||||
"戴墨镜的马",
|
||||
"拿着星星的马",
|
||||
"古代战马披铠甲",
|
||||
"水墨风格的骏马",
|
||||
"发光的梦幻马",
|
||||
"机械赛博格马",
|
||||
"戴着皇冠的皇家马",
|
||||
"背着书包上学的小马",
|
||||
"会魔法的飞马",
|
||||
"穿着宇航服的太空马",
|
||||
"抱着吉他的摇滚马",
|
||||
"正在画画的艺术家马",
|
||||
"穿唐装的新年马",
|
||||
"骑着扫帚的巫师马",
|
||||
"戴着围巾的冬日小马",
|
||||
"撑伞的雨中马",
|
||||
"戴着耳机听音乐的潮马",
|
||||
"背着礼物袋的圣诞马",
|
||||
"在镜子前自拍的爱美马",
|
||||
"变成龙形的神驹",
|
||||
"半植物半马的自然精灵",
|
||||
"透明水晶质感的玻璃马",
|
||||
"由星尘组成的宇宙马",
|
||||
"纸艺折纸风格小马",
|
||||
"黏土手办风格马",
|
||||
"影子轮廓马",
|
||||
"像素复古游戏马",
|
||||
"剪纸艺术风格马",
|
||||
"霓虹灯线条马",
|
||||
"蒸汽朋克齿轮马",
|
||||
"浮世绘风格日本马",
|
||||
"黄金雕像马"
|
||||
],
|
||||
"action": [
|
||||
"坐着发呆",
|
||||
"奔跑",
|
||||
"跳跃",
|
||||
"飞翔在空中",
|
||||
"游泳",
|
||||
"跳舞",
|
||||
"大笑",
|
||||
"惊讶地看着前方",
|
||||
"抱着胡萝卜",
|
||||
"放风筝",
|
||||
"睡觉",
|
||||
"吃草",
|
||||
"哭泣",
|
||||
"挥手打招呼",
|
||||
"翻滚",
|
||||
"冥想漂浮",
|
||||
"变魔术",
|
||||
"写毛笔字",
|
||||
"演奏小提琴",
|
||||
"骑滑板",
|
||||
"拍照留念",
|
||||
"讲笑话",
|
||||
"种花",
|
||||
"搭建积木城堡",
|
||||
"阅读童话书",
|
||||
"打太极拳",
|
||||
"喷火",
|
||||
"召唤闪电",
|
||||
"隐身消失",
|
||||
"时间暂停",
|
||||
"穿越虫洞",
|
||||
"绘制星空",
|
||||
"拥抱朋友",
|
||||
"滑冰",
|
||||
"攀岩",
|
||||
"倒立行走",
|
||||
"吐彩虹",
|
||||
"用尾巴画画",
|
||||
"变身成光球",
|
||||
"与蝴蝶共舞",
|
||||
"点亮灯笼",
|
||||
"解开谜题"
|
||||
],
|
||||
"scene": [
|
||||
"纯白色背景",
|
||||
"纯黑色背景",
|
||||
"纯红色背景",
|
||||
"纯蓝色背景",
|
||||
"纯绿色背景",
|
||||
"纯黄色背景",
|
||||
"纯橙色背景",
|
||||
"纯紫色背景",
|
||||
"纯粉色背景",
|
||||
"纯灰色背景",
|
||||
"纯米色背景",
|
||||
"纯青色背景",
|
||||
"纯金色背景",
|
||||
"纯银色背景",
|
||||
"纯棕色背景",
|
||||
"纯天蓝色背景",
|
||||
"纯珊瑚色背景",
|
||||
"纯薄荷绿背景",
|
||||
"纯薰衣草紫背景",
|
||||
"纯象牙白背景"
|
||||
],
|
||||
"style": [
|
||||
"卡通渲染风格",
|
||||
"水彩手绘质感",
|
||||
"油画厚涂技法",
|
||||
"中国风水墨画",
|
||||
"赛博朋克霓虹光影",
|
||||
"皮克斯3D动画风格",
|
||||
"低多边形(Low Poly)",
|
||||
"极简扁平设计",
|
||||
"超现实主义梦境感",
|
||||
"蒸汽朋克机械细节",
|
||||
"浮世绘木刻纹理",
|
||||
"儿童绘本插图",
|
||||
"复古8-bit像素风",
|
||||
"哥特式暗黑美学",
|
||||
"波普艺术鲜艳撞色",
|
||||
"剪纸拼贴艺术",
|
||||
"玻璃彩绘效果",
|
||||
"金属蚀刻工艺",
|
||||
"沙画流动质感",
|
||||
"全息投影光泽",
|
||||
"黏土定格动画",
|
||||
"铅笔素描线稿",
|
||||
"荧光夜光风格",
|
||||
"大理石雕塑质感",
|
||||
"丝绸刺绣图案",
|
||||
"动态模糊运动感",
|
||||
"故障艺术(Glitch Art)",
|
||||
"糖霜甜点装饰风",
|
||||
"雪花结晶微观视角",
|
||||
"星空粒子特效",
|
||||
"幻彩渐变晕染",
|
||||
"蜡笔涂鸦童趣风",
|
||||
"青铜器铭文融合",
|
||||
"敦煌壁画色彩",
|
||||
"激光雕刻线条",
|
||||
"毛毡布艺手工感",
|
||||
"琉璃透光材质",
|
||||
"水墨晕染+数字合成",
|
||||
"霓虹灯牌背景",
|
||||
"复古胶片颗粒感",
|
||||
"动态流体模拟",
|
||||
"万圣节南瓜灯氛围"
|
||||
],
|
||||
|
||||
}
|
||||
15
prompts/test/upload-image.ts
Normal file
15
prompts/test/upload-image.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { jimengService, ossService } from '../src/index.ts'
|
||||
const url = 'https://p3-dreamina-sign.byteimg.com/tos-cn-i-tb4s082cfz/4947076125c64a999e4c392de03048f8~tplv-tb4s082cfz-aigc_resize:360:360.webp?lk3s=43402efa&x-expires=1770336000&x-signature=Fjeeb3qloxxzxmHJpmqu6v8fwrM%3D&format=.webp'
|
||||
|
||||
|
||||
const uploadImage = async () => {
|
||||
const response = await jimengService.downloadImage(url);
|
||||
const filename = `uploaded_image_${Date.now()}.png`;
|
||||
await ossService.putObject(filename, response);
|
||||
const ossUrl = ossService.getLink(filename);
|
||||
// console.log('Uploaded Image URL:', response)
|
||||
// const uploadJons = await ossService.putObject('a1.json', { b: '123' })
|
||||
// console.log('Upload JSON Result:', uploadJons)
|
||||
return ossUrl;
|
||||
}
|
||||
uploadImage();
|
||||
24
prompts/tsconfig.json
Normal file
24
prompts/tsconfig.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"extends": "@kevisual/types/json/backend.json",
|
||||
"compilerOptions": {
|
||||
"module": "NodeNext",
|
||||
"target": "esnext",
|
||||
"baseUrl": ".",
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"./node_modules/@kevisual/types/index.d.ts"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
],
|
||||
"@agent/*": [
|
||||
"agent/*"
|
||||
]
|
||||
},
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"agent/**/*",
|
||||
],
|
||||
}
|
||||
Reference in New Issue
Block a user