U
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -53,9 +53,11 @@
|
|||||||
"dotenv": "^17.4.1",
|
"dotenv": "^17.4.1",
|
||||||
"google-trends-api": "^4.9.2",
|
"google-trends-api": "^4.9.2",
|
||||||
"grammy": "^1.42.0",
|
"grammy": "^1.42.0",
|
||||||
|
"helmet": "^8.1.0",
|
||||||
"https-proxy-agent": "^9.0.0",
|
"https-proxy-agent": "^9.0.0",
|
||||||
"install": "^0.13.0",
|
"install": "^0.13.0",
|
||||||
"ioredis": "^5.10.1",
|
"ioredis": "^5.10.1",
|
||||||
|
"localtunnel": "^2.0.2",
|
||||||
"lodash": "^4.18.1",
|
"lodash": "^4.18.1",
|
||||||
"nestjs-telegraf": "^2.9.1",
|
"nestjs-telegraf": "^2.9.1",
|
||||||
"openai": "^6.34.0",
|
"openai": "^6.34.0",
|
||||||
|
|||||||
Generated
+91
-2
@@ -107,6 +107,9 @@ importers:
|
|||||||
grammy:
|
grammy:
|
||||||
specifier: ^1.42.0
|
specifier: ^1.42.0
|
||||||
version: 1.42.0
|
version: 1.42.0
|
||||||
|
helmet:
|
||||||
|
specifier: ^8.1.0
|
||||||
|
version: 8.1.0
|
||||||
https-proxy-agent:
|
https-proxy-agent:
|
||||||
specifier: ^9.0.0
|
specifier: ^9.0.0
|
||||||
version: 9.0.0
|
version: 9.0.0
|
||||||
@@ -116,6 +119,9 @@ importers:
|
|||||||
ioredis:
|
ioredis:
|
||||||
specifier: ^5.10.1
|
specifier: ^5.10.1
|
||||||
version: 5.10.1
|
version: 5.10.1
|
||||||
|
localtunnel:
|
||||||
|
specifier: ^2.0.2
|
||||||
|
version: 2.0.2
|
||||||
lodash:
|
lodash:
|
||||||
specifier: ^4.18.1
|
specifier: ^4.18.1
|
||||||
version: 4.18.1
|
version: 4.18.1
|
||||||
@@ -2072,6 +2078,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==}
|
resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==}
|
||||||
engines: {node: '>= 6.0.0'}
|
engines: {node: '>= 6.0.0'}
|
||||||
|
|
||||||
|
axios@0.21.4:
|
||||||
|
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
|
||||||
|
|
||||||
axios@1.15.0:
|
axios@1.15.0:
|
||||||
resolution: {integrity: sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==}
|
resolution: {integrity: sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==}
|
||||||
|
|
||||||
@@ -2282,6 +2291,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
|
resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==}
|
||||||
engines: {node: '>= 12'}
|
engines: {node: '>= 12'}
|
||||||
|
|
||||||
|
cliui@7.0.4:
|
||||||
|
resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
|
||||||
|
|
||||||
cliui@8.0.1:
|
cliui@8.0.1:
|
||||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -2408,6 +2420,15 @@ packages:
|
|||||||
csstype@3.2.3:
|
csstype@3.2.3:
|
||||||
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||||
|
|
||||||
|
debug@4.3.2:
|
||||||
|
resolution: {integrity: sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==}
|
||||||
|
engines: {node: '>=6.0'}
|
||||||
|
peerDependencies:
|
||||||
|
supports-color: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
supports-color:
|
||||||
|
optional: true
|
||||||
|
|
||||||
debug@4.4.3:
|
debug@4.4.3:
|
||||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||||
engines: {node: '>=6.0'}
|
engines: {node: '>=6.0'}
|
||||||
@@ -2962,6 +2983,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
helmet@8.1.0:
|
||||||
|
resolution: {integrity: sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
hono@4.12.12:
|
hono@4.12.12:
|
||||||
resolution: {integrity: sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==}
|
resolution: {integrity: sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==}
|
||||||
engines: {node: '>=16.9.0'}
|
engines: {node: '>=16.9.0'}
|
||||||
@@ -3354,6 +3379,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==}
|
resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==}
|
||||||
engines: {node: '>=6.11.5'}
|
engines: {node: '>=6.11.5'}
|
||||||
|
|
||||||
|
localtunnel@2.0.2:
|
||||||
|
resolution: {integrity: sha512-n418Cn5ynvJd7m/N1d9WVJISLJF/ellZnfsLnx8WBWGzxv/ntNcFkJ1o6se5quUhCplfLGBNL5tYHiq5WF3Nug==}
|
||||||
|
engines: {node: '>=8.3.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
locate-path@5.0.0:
|
locate-path@5.0.0:
|
||||||
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
|
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -3505,6 +3535,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
|
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
|
ms@2.1.2:
|
||||||
|
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||||
|
|
||||||
ms@2.1.3:
|
ms@2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
@@ -3633,6 +3666,9 @@ packages:
|
|||||||
zod:
|
zod:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
openurl@1.1.1:
|
||||||
|
resolution: {integrity: sha512-d/gTkTb1i1GKz5k3XE3XFV/PxQ1k45zDqGP2OA7YhgsaLoqm6qRvARAZOFer1fcXritWlGBRCu/UgeS4HAnXAA==}
|
||||||
|
|
||||||
optionator@0.9.4:
|
optionator@0.9.4:
|
||||||
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
@@ -4573,10 +4609,18 @@ packages:
|
|||||||
yallist@3.1.1:
|
yallist@3.1.1:
|
||||||
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
||||||
|
|
||||||
|
yargs-parser@20.2.9:
|
||||||
|
resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
yargs-parser@21.1.1:
|
yargs-parser@21.1.1:
|
||||||
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
yargs@17.1.1:
|
||||||
|
resolution: {integrity: sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
yargs@17.7.2:
|
yargs@17.7.2:
|
||||||
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -6919,9 +6963,15 @@ snapshots:
|
|||||||
|
|
||||||
aws-ssl-profiles@1.1.2: {}
|
aws-ssl-profiles@1.1.2: {}
|
||||||
|
|
||||||
|
axios@0.21.4(debug@4.3.2):
|
||||||
|
dependencies:
|
||||||
|
follow-redirects: 1.15.11(debug@4.3.2)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
|
||||||
axios@1.15.0:
|
axios@1.15.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects: 1.15.11
|
follow-redirects: 1.15.11(debug@4.3.2)
|
||||||
form-data: 4.0.5
|
form-data: 4.0.5
|
||||||
proxy-from-env: 2.1.0
|
proxy-from-env: 2.1.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -7192,6 +7242,12 @@ snapshots:
|
|||||||
|
|
||||||
cli-width@4.1.0: {}
|
cli-width@4.1.0: {}
|
||||||
|
|
||||||
|
cliui@7.0.4:
|
||||||
|
dependencies:
|
||||||
|
string-width: 4.2.3
|
||||||
|
strip-ansi: 6.0.1
|
||||||
|
wrap-ansi: 7.0.0
|
||||||
|
|
||||||
cliui@8.0.1:
|
cliui@8.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
string-width: 4.2.3
|
string-width: 4.2.3
|
||||||
@@ -7305,6 +7361,10 @@ snapshots:
|
|||||||
|
|
||||||
csstype@3.2.3: {}
|
csstype@3.2.3: {}
|
||||||
|
|
||||||
|
debug@4.3.2:
|
||||||
|
dependencies:
|
||||||
|
ms: 2.1.2
|
||||||
|
|
||||||
debug@4.4.3:
|
debug@4.4.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
@@ -7690,7 +7750,9 @@ snapshots:
|
|||||||
|
|
||||||
flatted@3.4.2: {}
|
flatted@3.4.2: {}
|
||||||
|
|
||||||
follow-redirects@1.15.11: {}
|
follow-redirects@1.15.11(debug@4.3.2):
|
||||||
|
optionalDependencies:
|
||||||
|
debug: 4.3.2
|
||||||
|
|
||||||
for-in@0.1.8: {}
|
for-in@0.1.8: {}
|
||||||
|
|
||||||
@@ -7878,6 +7940,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind: 1.1.2
|
function-bind: 1.1.2
|
||||||
|
|
||||||
|
helmet@8.1.0: {}
|
||||||
|
|
||||||
hono@4.12.12: {}
|
hono@4.12.12: {}
|
||||||
|
|
||||||
hookified@1.15.1: {}
|
hookified@1.15.1: {}
|
||||||
@@ -8429,6 +8493,15 @@ snapshots:
|
|||||||
|
|
||||||
loader-runner@4.3.1: {}
|
loader-runner@4.3.1: {}
|
||||||
|
|
||||||
|
localtunnel@2.0.2:
|
||||||
|
dependencies:
|
||||||
|
axios: 0.21.4(debug@4.3.2)
|
||||||
|
debug: 4.3.2
|
||||||
|
openurl: 1.1.1
|
||||||
|
yargs: 17.1.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
locate-path@5.0.0:
|
locate-path@5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-locate: 4.1.0
|
p-locate: 4.1.0
|
||||||
@@ -8550,6 +8623,8 @@ snapshots:
|
|||||||
|
|
||||||
mri@1.2.0: {}
|
mri@1.2.0: {}
|
||||||
|
|
||||||
|
ms@2.1.2: {}
|
||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
|
|
||||||
msgpackr-extract@3.0.3:
|
msgpackr-extract@3.0.3:
|
||||||
@@ -8669,6 +8744,8 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
zod: 4.3.6
|
zod: 4.3.6
|
||||||
|
|
||||||
|
openurl@1.1.1: {}
|
||||||
|
|
||||||
optionator@0.9.4:
|
optionator@0.9.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
deep-is: 0.1.4
|
deep-is: 0.1.4
|
||||||
@@ -9616,8 +9693,20 @@ snapshots:
|
|||||||
|
|
||||||
yallist@3.1.1: {}
|
yallist@3.1.1: {}
|
||||||
|
|
||||||
|
yargs-parser@20.2.9: {}
|
||||||
|
|
||||||
yargs-parser@21.1.1: {}
|
yargs-parser@21.1.1: {}
|
||||||
|
|
||||||
|
yargs@17.1.1:
|
||||||
|
dependencies:
|
||||||
|
cliui: 7.0.4
|
||||||
|
escalade: 3.2.0
|
||||||
|
get-caller-file: 2.0.5
|
||||||
|
require-directory: 2.1.1
|
||||||
|
string-width: 4.2.3
|
||||||
|
y18n: 5.0.8
|
||||||
|
yargs-parser: 20.2.9
|
||||||
|
|
||||||
yargs@17.7.2:
|
yargs@17.7.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
cliui: 8.0.1
|
cliui: 8.0.1
|
||||||
|
|||||||
+33
-30
@@ -1,7 +1,8 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import {NestFactory} from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import {AppModule} from './app.module';
|
||||||
import {ValidationPipe} from "@nestjs/common";
|
import {ValidationPipe} from "@nestjs/common";
|
||||||
import {DocumentBuilder, SwaggerModule} from "@nestjs/swagger";
|
import {DocumentBuilder, SwaggerModule} from "@nestjs/swagger";
|
||||||
|
import helmet from "helmet";
|
||||||
|
|
||||||
// const { createBullBoard } = require('@bull-board/api');
|
// const { createBullBoard } = require('@bull-board/api');
|
||||||
// const { BullAdapter } = require('@bull-board/api/bullAdapter');
|
// const { BullAdapter } = require('@bull-board/api/bullAdapter');
|
||||||
@@ -10,41 +11,43 @@ import {DocumentBuilder, SwaggerModule} from "@nestjs/swagger";
|
|||||||
|
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule, {
|
const app = await NestFactory.create(AppModule, {
|
||||||
snapshot: true,
|
snapshot: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Global validation pipe
|
// Global validation pipe
|
||||||
app.useGlobalPipes(
|
app.useGlobalPipes(
|
||||||
new ValidationPipe({
|
new ValidationPipe({
|
||||||
transform: true,
|
transform: true,
|
||||||
whitelist: true,
|
whitelist: true,
|
||||||
forbidNonWhitelisted: true,
|
forbidNonWhitelisted: true,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
app.use(helmet());
|
||||||
|
// CORS
|
||||||
|
app.enableCors();
|
||||||
|
|
||||||
// CORS
|
// Swagger
|
||||||
app.enableCors();
|
const config = new DocumentBuilder()
|
||||||
|
.setTitle('🔥 Trend Hunter API')
|
||||||
|
.setDescription(
|
||||||
|
'API tìm kiếm và tổng hợp trends từ nhiều nguồn social media',
|
||||||
|
)
|
||||||
|
.setVersion('1.0')
|
||||||
|
.addTag('Trends')
|
||||||
|
.build();
|
||||||
|
const document = SwaggerModule.createDocument(app, config);
|
||||||
|
SwaggerModule.setup('docs', app, document);
|
||||||
|
|
||||||
// Swagger
|
const port = process.env.PORT || 3000;
|
||||||
const config = new DocumentBuilder()
|
await app.listen(port);
|
||||||
.setTitle('🔥 Trend Hunter API')
|
|
||||||
.setDescription(
|
|
||||||
'API tìm kiếm và tổng hợp trends từ nhiều nguồn social media',
|
|
||||||
)
|
|
||||||
.setVersion('1.0')
|
|
||||||
.addTag('Trends')
|
|
||||||
.build();
|
|
||||||
const document = SwaggerModule.createDocument(app, config);
|
|
||||||
SwaggerModule.setup('docs', app, document);
|
|
||||||
|
|
||||||
const port = process.env.PORT || 3000;
|
console.log(`
|
||||||
await app.listen(port);
|
|
||||||
|
|
||||||
console.log(`
|
|
||||||
🔥 X-News is running!
|
🔥 X-News is running!
|
||||||
📡 API: http://localhost:${port}
|
📡 API: http://localhost:${port}
|
||||||
📖 Swagger: http://localhost:${port}/docs
|
📖 Swagger: http://localhost:${port}/docs
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { IsEnum, IsString, IsOptional, MaxLength } from 'class-validator';
|
import { IsEnum, IsString, IsOptional, MaxLength } from 'class-validator';
|
||||||
import { ContentTone } from '../enum/tone.enum';
|
import { ContentTone } from '../enum/tone.enum';
|
||||||
import * as languagePromptInterface from "../../../common/interfaces/language.prompt.interface";
|
import * as languagePromptInterface from "../../../common/interfaces/language.prompt.interface";
|
||||||
|
import {AngleEnum} from "../enum/angle.enum";
|
||||||
|
|
||||||
export class GenerateCommentDto {
|
export class GenerateCommentDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
@@ -10,7 +11,7 @@ export class GenerateCommentDto {
|
|||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
angle?: string; // góc nhìn muốn comment: "agree", "challenge", "add-info", "funny"
|
angle?: AngleEnum; // góc nhìn muốn comment: "agree", "challenge", "add-info", "funny"
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
language: languagePromptInterface.Language;
|
language: languagePromptInterface.Language;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { QuoteType } from '../enum/quote-type.enum';
|
|||||||
import { ContentTone } from '../enum/tone.enum';
|
import { ContentTone } from '../enum/tone.enum';
|
||||||
import { PostLength } from '../enum/post-length.enum';
|
import { PostLength } from '../enum/post-length.enum';
|
||||||
import { AccountTier } from '../enum/account-tier.enum';
|
import { AccountTier } from '../enum/account-tier.enum';
|
||||||
|
import {AngleEnum} from "../enum/angle.enum";
|
||||||
|
|
||||||
export class GenerateQuoteDto {
|
export class GenerateQuoteDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
@@ -39,7 +40,7 @@ export class GenerateQuoteDto {
|
|||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
yourAngle?: string; // Góc nhìn riêng của bạn muốn express
|
yourAngle?: AngleEnum; // Góc nhìn riêng của bạn muốn express
|
||||||
|
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import {ContentTone} from "./tone.enum";
|
||||||
|
|
||||||
export enum AngleEnum {
|
export enum AngleEnum {
|
||||||
AGREE = 'agree',
|
AGREE = 'agree',
|
||||||
CHALLENGE = 'challenge',
|
CHALLENGE = 'challenge',
|
||||||
@@ -8,7 +10,26 @@ export enum AngleEnum {
|
|||||||
DEVIL_ADVOCATE = 'devil_advocate',
|
DEVIL_ADVOCATE = 'devil_advocate',
|
||||||
EXPAND = 'expand',
|
EXPAND = 'expand',
|
||||||
VALIDATE = 'validate',
|
VALIDATE = 'validate',
|
||||||
CTA = 'cta'
|
CTA = 'cta',
|
||||||
|
|
||||||
|
// === Empathy/Support angles ===
|
||||||
|
WISH_RECOVERY = 'wish_recovery', // Chấn thương, bệnh tật
|
||||||
|
TRIBUTE = 'tribute', // Người đã mất, di sản
|
||||||
|
SOLIDARITY = 'solidarity', // Tragedy chung, community
|
||||||
|
PERSONAL_SUPPORT = 'personal_support', // Hỗ trợ cá nhân 1-1
|
||||||
|
SHARED_GRIEF = 'shared_grief',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EMPATHYTONE_ANGLE = new Set<AngleEnum>([
|
||||||
|
AngleEnum.WISH_RECOVERY,
|
||||||
|
AngleEnum.TRIBUTE,
|
||||||
|
AngleEnum.SOLIDARITY,
|
||||||
|
AngleEnum.PERSONAL_SUPPORT,
|
||||||
|
AngleEnum.SHARED_GRIEF,
|
||||||
|
]);
|
||||||
|
|
||||||
|
export function isEMPATHYToneAngle(angle: AngleEnum): boolean {
|
||||||
|
return EMPATHYTONE_ANGLE.has(angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ANGLE_HINTS: Record<AngleEnum, string> = {
|
export const ANGLE_HINTS: Record<AngleEnum, string> = {
|
||||||
@@ -21,21 +42,44 @@ export const ANGLE_HINTS: Record<AngleEnum, string> = {
|
|||||||
[AngleEnum.DEVIL_ADVOCATE]: `Play devil's advocate. Present the opposite view fairly without being hostile`,
|
[AngleEnum.DEVIL_ADVOCATE]: `Play devil's advocate. Present the opposite view fairly without being hostile`,
|
||||||
[AngleEnum.EXPAND]: 'Take one point from the post and zoom in deeper with more nuance',
|
[AngleEnum.EXPAND]: 'Take one point from the post and zoom in deeper with more nuance',
|
||||||
[AngleEnum.VALIDATE]: `Affirm the post's point with evidence or strong agreement, boost credibility`,
|
[AngleEnum.VALIDATE]: `Affirm the post's point with evidence or strong agreement, boost credibility`,
|
||||||
[AngleEnum.CTA]: 'End with a soft call-to-action: ask others to share their view'
|
[AngleEnum.CTA]: 'End with a soft call-to-action: ask others to share their view',
|
||||||
|
|
||||||
|
[AngleEnum.WISH_RECOVERY]: '',
|
||||||
|
[AngleEnum.TRIBUTE]: '',
|
||||||
|
[AngleEnum.SOLIDARITY]: '',
|
||||||
|
[AngleEnum.PERSONAL_SUPPORT]: '',
|
||||||
|
[AngleEnum.SHARED_GRIEF]: '',
|
||||||
}
|
}
|
||||||
export const ANGLE_HINTS_TELEGRAM_BUTTON: Record<AngleEnum, Object> = {
|
export const get_ANGLE_HINTS_TELEGRAM_BUTTON=(tone:ContentTone)=>{
|
||||||
|
let buttons = DEFAULT_ANGLE_HINTS_TELEGRAM_BUTTON;
|
||||||
|
if(tone === ContentTone.EMPATHETIC) {
|
||||||
|
return {
|
||||||
|
...buttons,
|
||||||
|
...EMPATHETIC_ANGLE_TELEGRAM_BUTTON
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buttons;
|
||||||
|
}
|
||||||
|
export const DEFAULT_ANGLE_HINTS_TELEGRAM_BUTTON: Partial<Record<AngleEnum, Object>> = {
|
||||||
[AngleEnum.AGREE]: {text: 'Đồng ý'},
|
[AngleEnum.AGREE]: {text: 'Đồng ý'},
|
||||||
[AngleEnum.CHALLENGE]: {text: 'Không đồng ý'},
|
[AngleEnum.CHALLENGE]: {text: 'Không đồng ý'},
|
||||||
[AngleEnum.ADD_INFO]: {text: 'thêm thông tin liên quan hữu ích'},
|
[AngleEnum.ADD_INFO]: {text: 'thêm thông tin liên quan hữu ích'},
|
||||||
[AngleEnum.FUNNY]: {text: 'Hóm hỉnh, hài hước nhẹ nhàng, không gây khó chịu'},
|
[AngleEnum.FUNNY]: {text: 'Hóm hỉnh, hài hước nhẹ nhàng, không gây khó chịu'},
|
||||||
[AngleEnum.QUESTION]: {text: 'Đặt một câu hỏi tiếp theo thông minh'},
|
[AngleEnum.QUESTION]: {text: 'Đặt một câu hỏi tiếp theo thông minh'},
|
||||||
[AngleEnum.RELATE]: {text: 'Chia sẻ một trải nghiệm \n hoặc cảm xúc cá nhân tương tự như bài đăng gốc.'},
|
[AngleEnum.RELATE]: {text: 'Chia sẻ một trải nghiệm hoặc cảm xúc cá nhân tương tự như bài đăng gốc.'},
|
||||||
[AngleEnum.DEVIL_ADVOCATE]: {
|
[AngleEnum.DEVIL_ADVOCATE]: {
|
||||||
text: `Hãy đóng vai trò người phản biện. \n Trình bày quan điểm trái chiều một cách công bằng mà không tỏ ra thù địch.`
|
text: `Hãy đóng vai trò người phản biện.Trình bày quan điểm trái chiều một cách công bằng mà không tỏ ra thù địch.`
|
||||||
},
|
},
|
||||||
[AngleEnum.EXPAND]: {text: 'expand-Chọn một điểm từ bài viết và phân tích sâu hơn với nhiều sắc thái khác nhau.'},
|
[AngleEnum.EXPAND]: {text: 'expand-Chọn 1điểm phân tích sâuhơn với nhiều sắc thái khác nhau.'},
|
||||||
[AngleEnum.VALIDATE]: {
|
[AngleEnum.VALIDATE]: {
|
||||||
text: `validate-Khẳng định luận điểm của bài đăng bằng bằng chứng hoặc sự đồng tình mạnh mẽ, tăng cường độ tin cậy.`
|
text: `validate-Khẳngđịnhluậnđiểm = bằngchứng hoặc sựđồngtìnhmạnhmẽ, tăng cường độ tin cậy.`
|
||||||
},
|
},
|
||||||
[AngleEnum.CTA]: {text: 'cta-Kết thúc bằng lời kêu gọi hành động nhẹ nhàng'}
|
[AngleEnum.CTA]: {text: 'cta-Kết thúc bằng lời kêu gọi hành động nhẹ nhàng'},
|
||||||
|
}
|
||||||
|
export const EMPATHETIC_ANGLE_TELEGRAM_BUTTON: Partial<Record<AngleEnum, Object>> = {
|
||||||
|
[AngleEnum.WISH_RECOVERY]: {text: 'Chúc hồi phục'},
|
||||||
|
[AngleEnum.TRIBUTE]: {text: 'Tưởng nhớ / RIP'},
|
||||||
|
[AngleEnum.SOLIDARITY]: {text: 'Đồng lòng / Đứng cùng'},
|
||||||
|
[AngleEnum.PERSONAL_SUPPORT]: {text: 'Hỗ trợ cá nhân'},
|
||||||
|
[AngleEnum.SHARED_GRIEF]: {text: 'Cùng nỗi buồn'},
|
||||||
}
|
}
|
||||||
@@ -3,12 +3,13 @@
|
|||||||
import {Language} from "../../../common/interfaces/language.prompt.interface";
|
import {Language} from "../../../common/interfaces/language.prompt.interface";
|
||||||
import {calculateLengthBudget} from "../../../common/utils/token-calculator";
|
import {calculateLengthBudget} from "../../../common/utils/token-calculator";
|
||||||
import {Platform} from "../enum/platform.enum";
|
import {Platform} from "../enum/platform.enum";
|
||||||
import {ANGLE_HINTS} from "../enum/angle.enum";
|
import {AngleEnum, isEMPATHYToneAngle} from "../enum/angle.enum";
|
||||||
import {ContentTone, isEdgyTone} from "../enum/tone.enum";
|
import {ContentTone, isEdgyTone} from "../enum/tone.enum";
|
||||||
import {buildEdgySystemPrompt, mapToneToStarterCategory} from "./quote.templates";
|
import {buildEdgySystemPrompt, mapToneToStarterCategory} from "./quote.templates";
|
||||||
import {getToneInstruction} from "./edgy-tones";
|
import {getToneInstruction} from "./edgy-tones";
|
||||||
import {LANGUAGE_LOCK} from "./templates";
|
|
||||||
import {getJpContextBlock} from "./jp-cultural-context";
|
import {getJpContextBlock} from "./jp-cultural-context";
|
||||||
|
import {getAngleHint} from "./empathy-angles";
|
||||||
|
import {buildEmpathySystemProgram} from "./empathy-system";
|
||||||
|
|
||||||
export const COMMENT_SYSTEM_PROMPTS = {
|
export const COMMENT_SYSTEM_PROMPTS = {
|
||||||
en: 'You write casual, human replies on X. Sound like a real person, NOT AI. No hashtags unless natural.',
|
en: 'You write casual, human replies on X. Sound like a real person, NOT AI. No hashtags unless natural.',
|
||||||
@@ -31,7 +32,7 @@ export const COMMENT_SYSTEM_PROMPTS = {
|
|||||||
|
|
||||||
export function buildCommentPrompt(params: {
|
export function buildCommentPrompt(params: {
|
||||||
originalPost: string;
|
originalPost: string;
|
||||||
angle?: string;
|
angle?: AngleEnum;
|
||||||
language: Language;
|
language: Language;
|
||||||
persona?: string;
|
persona?: string;
|
||||||
tone?: ContentTone;
|
tone?: ContentTone;
|
||||||
@@ -44,9 +45,18 @@ export function buildCommentPrompt(params: {
|
|||||||
// question: 'question:Đặt một câu hỏi tiếp theo thông minh',
|
// question: 'question:Đặt một câu hỏi tiếp theo thông minh',
|
||||||
// };
|
// };
|
||||||
const tone = params.tone ?? ContentTone.CASUAL;
|
const tone = params.tone ?? ContentTone.CASUAL;
|
||||||
const system = isEdgyTone(tone)
|
|
||||||
? buildEdgySystemPrompt(params.language)
|
let system = ''
|
||||||
: COMMENT_SYSTEM_PROMPTS[params.language];
|
if (isEdgyTone(tone)) {
|
||||||
|
system = buildEdgySystemPrompt(params.language);
|
||||||
|
} else if (isEMPATHYToneAngle(params.angle!)) {
|
||||||
|
system = buildEmpathySystemProgram(params.language);
|
||||||
|
} else {
|
||||||
|
system = COMMENT_SYSTEM_PROMPTS[params.language];
|
||||||
|
}
|
||||||
|
// const system = isEdgyTone(tone)
|
||||||
|
// ? buildEdgySystemPrompt(params.language)
|
||||||
|
// : COMMENT_SYSTEM_PROMPTS[params.language];
|
||||||
|
|
||||||
const toneInstruction = getToneInstruction(tone, params.language, '-')
|
const toneInstruction = getToneInstruction(tone, params.language, '-')
|
||||||
|
|
||||||
@@ -60,13 +70,18 @@ export function buildCommentPrompt(params: {
|
|||||||
: '';
|
: '';
|
||||||
const budget = calculateLengthBudget(Platform.X, params.language);
|
const budget = calculateLengthBudget(Platform.X, params.language);
|
||||||
|
|
||||||
|
const angleInstruction = getAngleHint(params.angle!, params.language, '- ANGLE:');
|
||||||
|
|
||||||
|
console.debug({toneInstruction, angleInstruction});
|
||||||
|
|
||||||
const user = [
|
const user = [
|
||||||
`Original X post:\n"""${params.originalPost}"""`,
|
`Original X post:\n"""${params.originalPost}"""`,
|
||||||
``,
|
``,
|
||||||
`Write a comment/reply target length: ${budget.minChars}-${budget.maxChars} characters:`,
|
`Write a comment/reply target length: ${budget.minChars}-${budget.maxChars} characters:`,
|
||||||
`[Target Language: ${params.language}]
|
`[Target Language: ${params.language}]
|
||||||
Rewrite strictly in ${params.language} only.`,
|
Rewrite strictly in ${params.language} only.`,
|
||||||
params.angle ? `- Angle: ${ANGLE_HINTS[params.angle] ?? params.angle}` : '- Angle: natural reaction',
|
angleInstruction,
|
||||||
|
// params.angle ? `- Angle: ${ANGLE_HINTS[params.angle] ?? params.angle}` : '- Angle: natural reaction',
|
||||||
params.persona ? `- Speak as: ${params.persona}` : '',
|
params.persona ? `- Speak as: ${params.persona}` : '',
|
||||||
toneInstruction,
|
toneInstruction,
|
||||||
`- Sound HUMAN, not AI. No "Great post!" openings.`,
|
`- Sound HUMAN, not AI. No "Great post!" openings.`,
|
||||||
@@ -77,5 +92,5 @@ export function buildCommentPrompt(params: {
|
|||||||
`- Output ONLY the reply text.`,
|
`- Output ONLY the reply text.`,
|
||||||
].filter(Boolean).join('\n');
|
].filter(Boolean).join('\n');
|
||||||
|
|
||||||
return {system:system, user};
|
return {system: system, user};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,305 @@
|
|||||||
|
// prompts/empathy-angles.ts
|
||||||
|
|
||||||
|
|
||||||
|
import {Language} from "../../../common/interfaces/language.prompt.interface";
|
||||||
|
import {ANGLE_HINTS, AngleEnum, isEMPATHYToneAngle} from "../enum/angle.enum";
|
||||||
|
import {getToneHint} from "./templates";
|
||||||
|
|
||||||
|
export interface EmpathyAngleSpec {
|
||||||
|
name: Partial<Record<Language, string>>;
|
||||||
|
instruction: Partial<Record<Language, string>>;
|
||||||
|
examples: Partial<Record<Language, string[]>>;
|
||||||
|
avoid: Partial<Record<Language, string>>;
|
||||||
|
contextNote: Partial<Record<Language, string>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EMPATHY_ANGLE_SPECS: Partial<Record<AngleEnum, EmpathyAngleSpec>> = {
|
||||||
|
[AngleEnum.WISH_RECOVERY]: {
|
||||||
|
name: {
|
||||||
|
en: 'Wish for recovery',
|
||||||
|
vi: 'Chúc hồi phục',
|
||||||
|
ja: '回復を願う',
|
||||||
|
ko: '회복 기원',
|
||||||
|
},
|
||||||
|
instruction: {
|
||||||
|
en: 'Express genuine concern + wish for fast recovery. Acknowledge as a fan/observer. Short, sincere.',
|
||||||
|
vi: 'Bày tỏ lo lắng chân thành + mong chóng hồi phục. Đứng từ góc fan/người quan sát. Ngắn, chân thành.',
|
||||||
|
ja: 'ファン視点で心配と回復への願いを表現。短く誠実に。大げさにしない。',
|
||||||
|
ko: '팬/관찰자 시점에서 진심 어린 걱정과 빠른 회복 기원. 짧고 진실되게.',
|
||||||
|
},
|
||||||
|
examples: {
|
||||||
|
en: [
|
||||||
|
'Damn, hate to see this. Wishing him a speedy recovery 🙏',
|
||||||
|
'Tough break. Hope he comes back stronger.',
|
||||||
|
'Praying for a clean recovery. The game won\'t be the same without him.',
|
||||||
|
],
|
||||||
|
vi: [
|
||||||
|
'Buồn quá. Mong anh sớm hồi phục.',
|
||||||
|
'Chấn thương đau lòng. Chờ ngày anh trở lại mạnh mẽ hơn.',
|
||||||
|
'Cầu mong anh bình phục thật nhanh. Fan vẫn ở đây 🙏',
|
||||||
|
],
|
||||||
|
ja: [
|
||||||
|
'心配です…早く回復してほしい',
|
||||||
|
'これは見るのが辛い。早期回復を祈ってます',
|
||||||
|
'無理せず、ゆっくり治してほしい。応援してます',
|
||||||
|
],
|
||||||
|
ko: [
|
||||||
|
'진짜 마음 아프네요. 빠른 쾌유 기원합니다',
|
||||||
|
'안타깝네요… 무리하지 말고 푹 쉬셨으면',
|
||||||
|
'꼭 건강한 모습으로 돌아오시길 🙏',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
avoid: {
|
||||||
|
en: 'No "RIP" (he\'s alive!). No "deeply saddened" (AI-ish). No medical advice.',
|
||||||
|
vi: 'Không dùng "RIP" (còn sống!). Không "vô cùng đau xót" (AI-ish). Không cho lời khuyên y tế.',
|
||||||
|
ja: '「ご冥福」絶対NG(生きてる!)。大げさな表現避ける。医療アドバイス禁止。',
|
||||||
|
ko: '"명복" 절대 금지(살아있음!). 과장된 표현 피하기. 의료 조언 금지.',
|
||||||
|
},
|
||||||
|
contextNote: {
|
||||||
|
en: 'Person is ALIVE but injured/sick. Tone is concerned but hopeful.',
|
||||||
|
vi: 'Người đó CÒN SỐNG nhưng bị thương/bệnh. Tone lo lắng nhưng hy vọng.',
|
||||||
|
ja: '本人は生きている。心配だが希望的なトーン。',
|
||||||
|
ko: '본인은 살아있음. 걱정스럽지만 희망적인 톤.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[AngleEnum.TRIBUTE]: {
|
||||||
|
name: {
|
||||||
|
en: 'Tribute / RIP',
|
||||||
|
vi: 'Tưởng nhớ / RIP',
|
||||||
|
ja: '追悼',
|
||||||
|
ko: '추모',
|
||||||
|
},
|
||||||
|
instruction: {
|
||||||
|
en: 'Honor the deceased. Acknowledge their impact/legacy. Sincere, not performative. Share a brief specific memory/impact if relevant.',
|
||||||
|
vi: 'Tôn vinh người đã khuất. Ghi nhận đóng góp/di sản. Chân thành, không phô trương. Có thể kể 1 kỷ niệm/ấn tượng cụ thể.',
|
||||||
|
ja: '故人を偲ぶ。功績・影響を認める。誠実に、わざとらしくない。具体的な思い出/影響を一つ短く。',
|
||||||
|
ko: '고인을 기리기. 업적/영향 인정. 진실되게, 과시적이지 않게. 구체적인 추억/영향 하나 짧게.',
|
||||||
|
},
|
||||||
|
examples: {
|
||||||
|
en: [
|
||||||
|
'A true legend. The way he changed the game will outlive all of us. RIP 🕊️',
|
||||||
|
'Gutted. Grew up watching him. Rest in peace.',
|
||||||
|
'No words. His [specific work/moment] meant everything. Thank you for everything.',
|
||||||
|
],
|
||||||
|
vi: [
|
||||||
|
'Một huyền thoại thực sự. Cách anh thay đổi cuộc chơi sẽ còn mãi. Yên nghỉ nhé.',
|
||||||
|
'Buồn quá. Lớn lên xem anh. Cầu mong anh yên nghỉ.',
|
||||||
|
'Không lời nào diễn tả được. [Tác phẩm/khoảnh khắc] của anh là tuổi thơ của tôi. Cảm ơn anh tất cả.',
|
||||||
|
],
|
||||||
|
ja: [
|
||||||
|
'ご冥福をお祈りします。あの[作品/瞬間]は今も心に残ってる',
|
||||||
|
'言葉が出ない…本当に偉大な方だった。安らかに',
|
||||||
|
'一つの時代が終わった気がする。ありがとうございました',
|
||||||
|
],
|
||||||
|
ko: [
|
||||||
|
'삼가 고인의 명복을 빕니다. [작품/순간]은 평생 잊지 못할 거예요',
|
||||||
|
'말이 안 나옵니다… 정말 큰 분이셨습니다. 편히 쉬세요',
|
||||||
|
'한 시대가 저무는 느낌입니다. 감사했습니다',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
avoid: {
|
||||||
|
en: 'NO self-promo. NO "anyway, my project..." NO emoji spam (1-2 max). NO platitudes like "thoughts and prayers" alone.',
|
||||||
|
vi: 'KHÔNG quảng cáo bản thân. KHÔNG "btw, dự án của tôi...". KHÔNG spam emoji (1-2 là đủ). KHÔNG sáo rỗng kiểu "thành kính phân ưu" trống rỗng.',
|
||||||
|
ja: '自己宣伝絶対NG。「ところで〜」NG。絵文字スパムNG(1-2個まで)。空虚な定型句のみは避ける。',
|
||||||
|
ko: '자기 홍보 절대 금지. "그런데 제 프로젝트는~" 금지. 이모지 스팸 금지(1-2개). 공허한 상투어만은 피하기.',
|
||||||
|
},
|
||||||
|
contextNote: {
|
||||||
|
en: 'Person has DIED. Be respectful. Specificity > generality. Brevity OK — silence is OK too.',
|
||||||
|
vi: 'Người đó ĐÃ MẤT. Tôn trọng. Cụ thể > chung chung. Ngắn cũng được — đôi khi im lặng là tôn trọng.',
|
||||||
|
ja: '故人。敬意を持って。具体性 > 一般論。短くてOK — 沈黙も尊重。',
|
||||||
|
ko: '고인. 존중하며. 구체성 > 일반론. 짧아도 OK — 침묵도 존중.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[AngleEnum.SOLIDARITY]: {
|
||||||
|
name: {
|
||||||
|
en: 'Solidarity / Stand with',
|
||||||
|
vi: 'Đồng lòng / Đứng cùng',
|
||||||
|
ja: '連帯',
|
||||||
|
ko: '연대',
|
||||||
|
},
|
||||||
|
instruction: {
|
||||||
|
en: 'Express solidarity with affected group. "We are with you." Acknowledge difficulty without minimizing. Action-oriented if appropriate (donate, support, etc.).',
|
||||||
|
vi: 'Bày tỏ đồng lòng với nhóm bị ảnh hưởng. "Chúng ta cùng bạn." Ghi nhận khó khăn không giảm nhẹ. Hành động nếu phù hợp (quyên góp, hỗ trợ).',
|
||||||
|
ja: '被害を受けた人々への連帯を表明。「共にいる」気持ち。困難を矮小化せず認める。可能なら行動的に(寄付・支援)。',
|
||||||
|
ko: '피해자들과의 연대 표현. "함께 있다"는 마음. 어려움을 축소하지 않고 인정. 가능하면 행동 지향(기부, 지원).',
|
||||||
|
},
|
||||||
|
examples: {
|
||||||
|
en: [
|
||||||
|
'Heart breaks for everyone affected. If anyone needs help with [X], DM me.',
|
||||||
|
'Standing with [community]. This is devastating. What\'s the best way to support right now?',
|
||||||
|
'No one should go through this alone. Sending real support, not just words.',
|
||||||
|
],
|
||||||
|
vi: [
|
||||||
|
'Lòng đau cho tất cả mọi người bị ảnh hưởng. Ai cần hỗ trợ [X] có thể DM mình.',
|
||||||
|
'Đứng cùng [cộng đồng]. Quá đau lòng. Cách tốt nhất để hỗ trợ lúc này là gì?',
|
||||||
|
'Không ai nên trải qua điều này một mình. Gửi hỗ trợ thật, không chỉ lời nói.',
|
||||||
|
],
|
||||||
|
ja: [
|
||||||
|
'被災された皆様に心を寄せています。[具体的支援]できることがあれば言ってください',
|
||||||
|
'本当に胸が痛みます。今、一番有効な支援方法を共有してほしい',
|
||||||
|
'言葉だけじゃなく、実際にできることをやります',
|
||||||
|
],
|
||||||
|
ko: [
|
||||||
|
'피해 입으신 모든 분들께 마음을 보냅니다. [구체적 지원] 도울 일 있으면 말씀해 주세요',
|
||||||
|
'정말 가슴이 아픕니다. 지금 가장 효과적인 지원 방법을 알려주세요',
|
||||||
|
'말뿐 아니라 실제로 할 수 있는 걸 하겠습니다',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
avoid: {
|
||||||
|
en: 'No empty "thoughts and prayers" if you can offer concrete help. No making it about yourself. No political opportunism on tragedy.',
|
||||||
|
vi: 'Không "cầu nguyện" sáo rỗng nếu có thể giúp cụ thể. Không biến chuyện thành về mình. Không lợi dụng chính trị trên tragedy.',
|
||||||
|
ja: '具体的支援できるなら「祈ってます」だけは避ける。自分の話にしない。悲劇を政治利用しない。',
|
||||||
|
ko: '구체적 도움 가능하면 "기도합니다"만으로 끝내지 말기. 자기 얘기로 만들지 말기. 비극을 정치적으로 이용하지 말기.',
|
||||||
|
},
|
||||||
|
contextNote: {
|
||||||
|
en: 'Group/community affected. Focus on THEM, not yourself.',
|
||||||
|
vi: 'Nhóm/cộng đồng bị ảnh hưởng. Tập trung vào HỌ, không phải bản thân.',
|
||||||
|
ja: '集団・コミュニティが影響を受けた。彼らに焦点、自分の話ではなく。',
|
||||||
|
ko: '집단/커뮤니티가 영향받음. 그들에 집중, 자기 얘기 아님.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[AngleEnum.PERSONAL_SUPPORT]: {
|
||||||
|
name: {
|
||||||
|
en: 'Personal support',
|
||||||
|
vi: 'Hỗ trợ cá nhân',
|
||||||
|
ja: '個人的サポート',
|
||||||
|
ko: '개인적 지원',
|
||||||
|
},
|
||||||
|
instruction: {
|
||||||
|
en: 'Direct 1-on-1 support to someone sharing personal grief. Acknowledge feeling first, validate, then optionally offer ear/help. Don\'t fix unless asked.',
|
||||||
|
vi: 'Hỗ trợ trực tiếp 1-1 cho người chia sẻ nỗi buồn cá nhân. Ghi nhận cảm xúc TRƯỚC, validate, sau đó có thể đề nghị lắng nghe/giúp đỡ. Đừng "fix" trừ khi được hỏi.',
|
||||||
|
ja: '個人的な悲しみを共有した人への1対1サポート。まず感情を認める、validateする、必要なら聞き役を申し出る。求められない限り「解決」しない。',
|
||||||
|
ko: '개인적 슬픔을 공유한 사람에게 1대1 지원. 먼저 감정 인정, validate, 필요시 들어주기 제안. 요청 없으면 "해결" 시도 말기.',
|
||||||
|
},
|
||||||
|
examples: {
|
||||||
|
en: [
|
||||||
|
'Hey, that\'s a lot to carry. Take your time. Here if you need to talk.',
|
||||||
|
'I\'m so sorry. There\'s no right way to feel this — whatever you\'re feeling is valid.',
|
||||||
|
'Sending love. No need to be okay right now.',
|
||||||
|
],
|
||||||
|
vi: [
|
||||||
|
'Anh/chị ơi, nặng nề quá. Cứ từ từ thôi. Cần tâm sự gì cứ nói.',
|
||||||
|
'Mình rất tiếc. Không có cách "đúng" để cảm nhận chuyện này — mọi cảm xúc đều ổn.',
|
||||||
|
'Gửi tình thương. Không cần phải ổn ngay đâu.',
|
||||||
|
],
|
||||||
|
ja: [
|
||||||
|
'辛いですね…無理に元気にならなくていいです。いつでも話聞きます',
|
||||||
|
'本当にお辛いですね。感じることに「正しい」も「間違い」もないですよ',
|
||||||
|
'何も言えないけど、ここにいます。一人じゃないですよ',
|
||||||
|
],
|
||||||
|
ko: [
|
||||||
|
'많이 힘드시겠어요… 억지로 괜찮은 척하지 않아도 돼요. 언제든 얘기 들을게요',
|
||||||
|
'정말 마음 아프시겠어요. 어떻게 느끼시든 다 괜찮은 감정이에요',
|
||||||
|
'뭐라 말씀드려야 할지… 여기 있을게요. 혼자 아니에요',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
avoid: {
|
||||||
|
en: 'NO "everything happens for a reason." NO "stay strong" (pressures them). NO unsolicited advice. NO "I know how you feel" (you don\'t).',
|
||||||
|
vi: 'KHÔNG "mọi chuyện đều có lý do". KHÔNG "cố lên" (tạo áp lực). KHÔNG khuyên khi không được hỏi. KHÔNG "anh hiểu mà" (bạn không).',
|
||||||
|
ja: '「すべてに意味がある」NG。「頑張って」(プレッシャーになる)NG。求められない助言NG。「気持ち分かるよ」(分からない)NG。',
|
||||||
|
ko: '"모든 일에는 이유가 있다" 금지. "힘내세요"(부담 줌) 금지. 요청 없는 조언 금지. "마음 알아요"(모름) 금지.',
|
||||||
|
},
|
||||||
|
contextNote: {
|
||||||
|
en: 'Individual person sharing personal pain. Be a witness, not a fixer.',
|
||||||
|
vi: 'Cá nhân chia sẻ nỗi đau riêng. Là người chứng kiến, không phải người sửa.',
|
||||||
|
ja: '個人が個人的な痛みを共有。証人になる、解決者ではなく。',
|
||||||
|
ko: '개인이 개인적 고통 공유. 증인이 되기, 해결사 아님.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
[AngleEnum.SHARED_GRIEF]: {
|
||||||
|
name: {
|
||||||
|
en: 'Shared grief',
|
||||||
|
vi: 'Cùng nỗi buồn',
|
||||||
|
ja: '共有する悲しみ',
|
||||||
|
ko: '함께하는 슬픔',
|
||||||
|
},
|
||||||
|
instruction: {
|
||||||
|
en: 'You\'re also affected (fan of same person, member of same community). Share YOUR specific grief briefly. Connects through shared loss.',
|
||||||
|
vi: 'Bạn cũng bị ảnh hưởng (cùng fan, cùng cộng đồng). Chia sẻ nỗi buồn CỦA BẠN ngắn gọn. Kết nối qua mất mát chung.',
|
||||||
|
ja: '自分も影響を受けた立場(同じファン、同じコミュニティ)。自分の悲しみを短く共有。共通の喪失で繋がる。',
|
||||||
|
ko: '자신도 영향받은 입장(같은 팬, 같은 커뮤니티). 자신의 슬픔을 짧게 공유. 공통의 상실로 연결.',
|
||||||
|
},
|
||||||
|
examples: {
|
||||||
|
en: [
|
||||||
|
'Sitting with this today. Grew up watching him. The [specific moment] stays with me.',
|
||||||
|
'Can\'t process this. He was part of my entire fandom journey.',
|
||||||
|
'My day stopped when I saw the news. We lost something real.',
|
||||||
|
],
|
||||||
|
vi: [
|
||||||
|
'Ngồi đây với cảm xúc này. Lớn lên xem anh. [Khoảnh khắc cụ thể] sẽ ở mãi với tôi.',
|
||||||
|
'Không xử lý nổi. Anh là một phần hành trình fan của tôi.',
|
||||||
|
'Ngày của tôi dừng lại khi thấy tin. Chúng ta mất đi điều gì đó thật.',
|
||||||
|
],
|
||||||
|
ja: [
|
||||||
|
'今日はこの感情と一緒にいる。子供の頃から見てた。[具体的瞬間]は一生忘れない',
|
||||||
|
'まだ受け入れられない。ファン人生の一部だった',
|
||||||
|
'ニュース見て時が止まった。本物を失った気がする',
|
||||||
|
],
|
||||||
|
ko: [
|
||||||
|
'오늘은 이 감정과 함께 있는 중. 어릴 때부터 봤는데. [구체적 순간]은 평생 못 잊을 듯',
|
||||||
|
'아직 받아들이지 못하겠어요. 팬 인생의 일부였는데',
|
||||||
|
'뉴스 보고 시간이 멈췄어요. 진짜 무언가를 잃은 느낌',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
avoid: {
|
||||||
|
en: 'No making it about you exclusively. Specific shared memory > generic "I\'m sad too."',
|
||||||
|
vi: 'Đừng biến hoàn toàn về mình. Kỷ niệm cụ thể chung > "tôi cũng buồn" chung chung.',
|
||||||
|
ja: '完全に自分の話にしないこと。具体的な共通の思い出 > 「私も悲しい」だけの一般論。',
|
||||||
|
ko: '완전히 자기 얘기로 만들지 말기. 구체적인 공유 추억 > "나도 슬프다" 일반론.',
|
||||||
|
},
|
||||||
|
contextNote: {
|
||||||
|
en: 'Both you and the person posting are affected. Connect via shared experience.',
|
||||||
|
vi: 'Cả bạn và người đăng đều bị ảnh hưởng. Kết nối qua trải nghiệm chung.',
|
||||||
|
ja: 'あなたと投稿者の両方が影響を受けた。共通体験で繋がる。',
|
||||||
|
ko: '본인과 게시자 모두 영향받음. 공유 경험으로 연결.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const get_EMPATHY_ANGLE_TELEGRAM_BUTTONS = () => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEmpathyAngleInstruction(
|
||||||
|
angle: string,
|
||||||
|
language: Language,
|
||||||
|
): string {
|
||||||
|
const spec = EMPATHY_ANGLE_SPECS[angle];
|
||||||
|
if (!spec) return '';
|
||||||
|
|
||||||
|
let s_examples = spec.examples[language];
|
||||||
|
if (!!s_examples) {
|
||||||
|
//move qua language =en;
|
||||||
|
language = 'en';
|
||||||
|
console.log('==> getEmpathyAngleInstruction=>fallback lang to eng')
|
||||||
|
}
|
||||||
|
s_examples = spec.examples[language];
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const examples = s_examples.slice(0, 3)
|
||||||
|
.map((ex, i) => ` ${i + 1}. ${ex}`).join('\n');
|
||||||
|
|
||||||
|
return [
|
||||||
|
`💝 ANGLE: ${spec.name[language]}`,
|
||||||
|
`Context: ${spec.contextNote[language]}`,
|
||||||
|
`Instruction: ${spec.instruction[language]}`,
|
||||||
|
`Examples (style only, DON'T copy):`,
|
||||||
|
examples,
|
||||||
|
`⚠️ AVOID: ${spec.avoid[language]}`,
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAngleHint(angle: AngleEnum, language: Language, prefix = '- '): string {
|
||||||
|
if (!angle) return '';
|
||||||
|
if (isEMPATHYToneAngle(angle)) {
|
||||||
|
return getEmpathyAngleInstruction(angle, language)
|
||||||
|
}
|
||||||
|
const agHint = ANGLE_HINTS[language];
|
||||||
|
return agHint
|
||||||
|
? `${prefix} ${agHint}`
|
||||||
|
: ``;
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
// prompts/empathy-system.ts
|
||||||
|
|
||||||
|
import {Language} from "../../../common/interfaces/language.prompt.interface";
|
||||||
|
|
||||||
|
|
||||||
|
export const buildEmpathySystemProgram = (lang: Language) => {
|
||||||
|
const prompts: Record<Language, string> = {
|
||||||
|
en: [
|
||||||
|
'You write empathetic comments on sensitive posts (loss, injury, grief, tragedy).',
|
||||||
|
'You sound like a real human, not a sympathy card. Be specific, brief, sincere.',
|
||||||
|
'Acknowledge feelings WITHOUT trying to fix. Validate WITHOUT minimizing.',
|
||||||
|
'Brevity is respectful. 1-3 sentences usually best.',
|
||||||
|
'Output ONLY the comment text. No preamble, no quotes, no disclaimers.',
|
||||||
|
].join(' '),
|
||||||
|
cn: [
|
||||||
|
'You write empathetic comments on sensitive posts (loss, injury, grief, tragedy).',
|
||||||
|
'You sound like a real human, not a sympathy card. Be specific, brief, sincere.',
|
||||||
|
'Acknowledge feelings WITHOUT trying to fix. Validate WITHOUT minimizing.',
|
||||||
|
'Brevity is respectful. 1-3 sentences usually best.',
|
||||||
|
'Output ONLY the comment text. No preamble, no quotes, no disclaimers.',
|
||||||
|
].join(' '),
|
||||||
|
|
||||||
|
vi: [
|
||||||
|
'Bạn viết comment đồng cảm cho post nhạy cảm (mất mát, chấn thương, buồn, tragedy).',
|
||||||
|
'Bạn giống người thật, không phải thiệp chia buồn. Cụ thể, ngắn gọn, chân thành.',
|
||||||
|
'Ghi nhận cảm xúc, KHÔNG cố "sửa". Validate, KHÔNG giảm nhẹ.',
|
||||||
|
'Ngắn gọn là tôn trọng. 1-3 câu thường tốt nhất.',
|
||||||
|
'CHỈ output comment bằng Tiếng Việt. Không preamble, không disclaimer.',
|
||||||
|
].join(' '),
|
||||||
|
|
||||||
|
ja: [
|
||||||
|
'あなたはセンシティブな投稿(喪失、怪我、悲しみ、悲劇)に共感的なコメントを書きます。',
|
||||||
|
'お悔やみカードではなく、生身の人間として書く。具体的、簡潔、誠実に。',
|
||||||
|
'感情を認める、「解決」しようとしない。validateする、矮小化しない。',
|
||||||
|
'簡潔さは敬意。1-3文がベスト。',
|
||||||
|
'日本語のコメント本文のみ出力。前置き・引用符・免責一切なし。',
|
||||||
|
].join(' '),
|
||||||
|
|
||||||
|
ko: [
|
||||||
|
'민감한 게시물(상실, 부상, 슬픔, 비극)에 공감적 댓글을 작성합니다.',
|
||||||
|
'위로 카드가 아닌 실제 사람처럼. 구체적이고 간결하며 진실되게.',
|
||||||
|
'감정 인정, "해결" 시도 말기. validate, 축소 말기.',
|
||||||
|
'간결함이 존중. 1-3문장이 최선.',
|
||||||
|
'한국어 댓글 본문만 출력. 서두/인용/면책 없음.',
|
||||||
|
].join(' '),
|
||||||
|
};
|
||||||
|
|
||||||
|
return prompts[lang];
|
||||||
|
}
|
||||||
@@ -9,6 +9,10 @@ import {ContentTone, isEdgyTone} from '../enum/tone.enum';
|
|||||||
import {Language} from "../../../common/interfaces/language.prompt.interface";
|
import {Language} from "../../../common/interfaces/language.prompt.interface";
|
||||||
import {getToneInstruction} from "./edgy-tones";
|
import {getToneInstruction} from "./edgy-tones";
|
||||||
import {getJpContextBlock, JP_X_CULTURE} from "./jp-cultural-context";
|
import {getJpContextBlock, JP_X_CULTURE} from "./jp-cultural-context";
|
||||||
|
import {AngleEnum, isEMPATHYToneAngle} from "../enum/angle.enum";
|
||||||
|
import {buildEmpathySystemProgram} from "./empathy-system";
|
||||||
|
import {COMMENT_SYSTEM_PROMPTS} from "./comment.templates";
|
||||||
|
import {getAngleHint} from "./empathy-angles";
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// SYSTEM PROMPTS — native per language (chống đổi ngôn ngữ)
|
// SYSTEM PROMPTS — native per language (chống đổi ngôn ngữ)
|
||||||
@@ -606,7 +610,7 @@ export interface QuotePromptParams {
|
|||||||
language: Language;
|
language: Language;
|
||||||
tone?: ContentTone;
|
tone?: ContentTone;
|
||||||
persona?: string;
|
persona?: string;
|
||||||
yourAngle?: string;
|
yourAngle?: AngleEnum;
|
||||||
lengthRange: LengthRange;
|
lengthRange: LengthRange;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -670,9 +674,18 @@ export function buildQuotePrompt(params: QuotePromptParams): {
|
|||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
// System prompt: nếu edgy tone, dùng version "unhinged" hơn
|
// System prompt: nếu edgy tone, dùng version "unhinged" hơn
|
||||||
const system = isEdgyTone(tone ?? ContentTone.CASUAL)
|
// const system = isEdgyTone(tone ?? ContentTone.CASUAL)
|
||||||
? buildEdgySystemPrompt(language)
|
// ? buildEdgySystemPrompt(language)
|
||||||
: QUOTE_SYSTEM_PROMPTS[language];
|
// : QUOTE_SYSTEM_PROMPTS[language];
|
||||||
|
|
||||||
|
let system = '';
|
||||||
|
if (isEdgyTone(tone ?? ContentTone.CASUAL)) {
|
||||||
|
system = buildEdgySystemPrompt(language);
|
||||||
|
} else if (isEMPATHYToneAngle(yourAngle!)) {
|
||||||
|
system = buildEmpathySystemProgram(language);
|
||||||
|
} else {
|
||||||
|
system = QUOTE_SYSTEM_PROMPTS[language];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const spec = QUOTE_TYPE_SPECS[quoteType];
|
const spec = QUOTE_TYPE_SPECS[quoteType];
|
||||||
@@ -702,6 +715,10 @@ export function buildQuotePrompt(params: QuotePromptParams): {
|
|||||||
// : '';
|
// : '';
|
||||||
const toneInstruction = getToneInstruction(tone!, language)
|
const toneInstruction = getToneInstruction(tone!, language)
|
||||||
|
|
||||||
|
const angleInstruction = getAngleHint(yourAngle!, language, 'Your specific angle:');
|
||||||
|
|
||||||
|
console.debug({toneInstruction, angleInstruction});
|
||||||
|
|
||||||
// const user = [
|
// const user = [
|
||||||
// `=== ${authorLine} ===`,
|
// `=== ${authorLine} ===`,
|
||||||
// `"""${originalPost}"""`,
|
// `"""${originalPost}"""`,
|
||||||
@@ -739,14 +756,15 @@ export function buildQuotePrompt(params: QuotePromptParams): {
|
|||||||
`"""${params.originalPost}"""`,
|
`"""${params.originalPost}"""`,
|
||||||
``,
|
``,
|
||||||
`=== Your quote-tweet task ===`,
|
`=== Your quote-tweet task ===`,
|
||||||
`Quote type: ${spec.name[params.language]}`,
|
`Quote type: ${spec.name[language]}`,
|
||||||
`Instruction: ${spec.instruction[params.language]}`,
|
`Instruction: ${spec.instruction[language]}`,
|
||||||
`Avoid: ${spec.avoid[params.language]}`,
|
`Avoid: ${spec.avoid[language]}`,
|
||||||
``,
|
``,
|
||||||
`Length: ${params.lengthRange.min}-${params.lengthRange.max} chars (aim ~${params.lengthRange.sweet})`,
|
`Length: ${params.lengthRange.min}-${params.lengthRange.max} chars (aim ~${params.lengthRange.sweet})`,
|
||||||
toneInstruction,
|
toneInstruction,
|
||||||
params.persona ? `Voice/persona: ${params.persona}` : '',
|
params.persona ? `Voice/persona: ${params.persona}` : '',
|
||||||
params.yourAngle ? `Your specific angle: ${params.yourAngle}` : '',
|
angleInstruction,
|
||||||
|
// params.yourAngle ? `Your specific angle: ${params.yourAngle}` : '',
|
||||||
// 👇 INJECT JP CONTEXT
|
// 👇 INJECT JP CONTEXT
|
||||||
jpContext,
|
jpContext,
|
||||||
``,
|
``,
|
||||||
|
|||||||
@@ -72,21 +72,7 @@ export const STYLE_HINTS: Record<ContentStyle, string> = {
|
|||||||
[ContentStyle.THREAD]: 'Thread format. Start with hook tweet. Each point numbered. End with CTA or summary.',
|
[ContentStyle.THREAD]: 'Thread format. Start with hook tweet. Each point numbered. End with CTA or summary.',
|
||||||
};
|
};
|
||||||
|
|
||||||
const TONE_HINTS_TELEGRAM_BUTTON_DEFAULT = {
|
|
||||||
[ContentTone.PROFESSIONAL]: {text: 'chuyên nghiệp, rõ ràng, đáng tin cậy'},
|
|
||||||
[ContentTone.CASUAL]: {text: 'Giản dị,thân thiện'},
|
|
||||||
[ContentTone.HYPE]: {text: 'Hype-Hào hứng,tràn đầy năng lượng'},
|
|
||||||
[ContentTone.URGENT]: {text: 'urgent'},
|
|
||||||
[ContentTone.HUMOROUS]: {text: 'Dí dỏm, hài hước'},
|
|
||||||
[ContentTone.INFORMATIVE]: {text: 'Thông tin, chính xác'},
|
|
||||||
[ContentTone.EMPATHETIC]: {text: 'empathetic-Đồng cảm,thấu hiểu cảm xúc,biếttrântrọngngườikhác.'},
|
|
||||||
[ContentTone.PROVOCATIVE]: {text: 'provocative-Gợimở suynghĩ,hơi gâytranhcãi,tháchthức cácgiảđịnh.'},
|
|
||||||
[ContentTone.AUTHORITATIVE]: {text: 'authoritative-giọng tự tin,uyquyền,chuyênnghiệp'},
|
|
||||||
|
|
||||||
// [ContentStyle.OPINION]: {text: 'Quan điểm táo bạo. \n"Tôi nghĩ / Quan điểm gây tranh cãi:". Mời gọi tranh luận.'},
|
|
||||||
// [ContentStyle.STORYTELLING]: {text: 'STORYTELLING-Cấu trúc tự sự. Mở đầu bằng sự căng thẳng → phát triển → kết thúc.\n Có thể là câu chuyện cá nhân hoặc nghiên cứu trường hợp.'},
|
|
||||||
// [ContentStyle.THREAD]: {text: 'THREAD-Bắt đầu bằng một tweet thu hút sự chú ý. \nMỗi điểm được đánh số. \nKết thúc bằng lời kêu gọi hành động (CTA) hoặc tóm tắt.'},
|
|
||||||
}
|
|
||||||
export const TONE_HINTS: Record<ContentTone, string> = {
|
export const TONE_HINTS: Record<ContentTone, string> = {
|
||||||
[ContentTone.PROFESSIONAL]: 'professional, clear, credible',
|
[ContentTone.PROFESSIONAL]: 'professional, clear, credible',
|
||||||
[ContentTone.CASUAL]: 'casual, friendly, conversational',
|
[ContentTone.CASUAL]: 'casual, friendly, conversational',
|
||||||
@@ -105,10 +91,27 @@ export const TONE_HINTS: Record<ContentTone, string> = {
|
|||||||
[ContentTone.INFLAMMATORY]: 'polarizing, controversial takes, generates strong reactions, defensible but provocative',
|
[ContentTone.INFLAMMATORY]: 'polarizing, controversial takes, generates strong reactions, defensible but provocative',
|
||||||
[ContentTone.SAVAGE]: 'brutal roast mode, surgical wit, mocking, punch up only (public figures/ideas)',
|
[ContentTone.SAVAGE]: 'brutal roast mode, surgical wit, mocking, punch up only (public figures/ideas)',
|
||||||
};
|
};
|
||||||
|
const TONE_HINTS_TELEGRAM_BUTTON_DEFAULT = {
|
||||||
|
[ContentTone.PROFESSIONAL]: {text: 'chuyên nghiệp, rõ ràng, đáng tin cậy'},
|
||||||
|
[ContentTone.CASUAL]: {text: 'Giản dị,thân thiện'},
|
||||||
|
[ContentTone.HYPE]: {text: 'Hype-Hào hứng,tràn đầy năng lượng'},
|
||||||
|
[ContentTone.URGENT]: {text: 'urgent'},
|
||||||
|
[ContentTone.HUMOROUS]: {text: 'Dí dỏm, hài hước'},
|
||||||
|
[ContentTone.INFORMATIVE]: {text: 'Thông tin, chính xác'},
|
||||||
|
[ContentTone.EMPATHETIC]: {text: 'empathetic-Đồng cảm,thấu hiểu cảm xúc,biếttrântrọngngườikhác.'},
|
||||||
|
[ContentTone.PROVOCATIVE]: {text: 'provocative-Gợimở suynghĩ,hơi gâytranhcãi,tháchthức cácgiảđịnh.'},
|
||||||
|
[ContentTone.AUTHORITATIVE]: {text: 'authoritative-giọng tự tin,uyquyền,chuyênnghiệp'},
|
||||||
|
|
||||||
|
// [ContentStyle.OPINION]: {text: 'Quan điểm táo bạo. \n"Tôi nghĩ / Quan điểm gây tranh cãi:". Mời gọi tranh luận.'},
|
||||||
|
// [ContentStyle.STORYTELLING]: {text: 'STORYTELLING-Cấu trúc tự sự. Mở đầu bằng sự căng thẳng → phát triển → kết thúc.\n Có thể là câu chuyện cá nhân hoặc nghiên cứu trường hợp.'},
|
||||||
|
// [ContentStyle.THREAD]: {text: 'THREAD-Bắt đầu bằng một tweet thu hút sự chú ý. \nMỗi điểm được đánh số. \nKết thúc bằng lời kêu gọi hành động (CTA) hoặc tóm tắt.'},
|
||||||
|
}
|
||||||
export const get_TONE_HINTS_TELEGRAM_BUTTON = (lang: Language) => {
|
export const get_TONE_HINTS_TELEGRAM_BUTTON = (lang: Language) => {
|
||||||
if (lang !== 'ja') {
|
if (lang !== 'ja') {
|
||||||
return TONE_HINTS_TELEGRAM_BUTTON_DEFAULT;
|
return {
|
||||||
|
...TONE_HINTS_TELEGRAM_BUTTON_DEFAULT,
|
||||||
|
[ContentTone.SPICY]: {text: 'spicy-Tự tin,hơiđốiđầu.KHÔNG giận dữ—chỉ thẳng.'}, // Blunt, sharp, mild profanity OK
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...TONE_HINTS_TELEGRAM_BUTTON_DEFAULT,
|
...TONE_HINTS_TELEGRAM_BUTTON_DEFAULT,
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ export class ContentSafetyService {
|
|||||||
// Hard block check
|
// Hard block check
|
||||||
for (const pattern of this.HARD_BLOCK_PATTERNS) {
|
for (const pattern of this.HARD_BLOCK_PATTERNS) {
|
||||||
if (pattern.test(content)) {
|
if (pattern.test(content)) {
|
||||||
|
this.logger.error(`checkOutput ->FALSE -> Output contains prohibited language`);
|
||||||
|
this.logger.error({content});
|
||||||
return {
|
return {
|
||||||
safe: false,
|
safe: false,
|
||||||
blockReason: 'Output contains prohibited language',
|
blockReason: 'Output contains prohibited language',
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {ContentStyle} from '../enum/style.enum';
|
|||||||
import {ProviderName} from '../providers/ai-provider.factory';
|
import {ProviderName} from '../providers/ai-provider.factory';
|
||||||
import {Language} from "../../../common/interfaces/language.prompt.interface";
|
import {Language} from "../../../common/interfaces/language.prompt.interface";
|
||||||
import {ContentTone, isEdgyTone} from "../enum/tone.enum";
|
import {ContentTone, isEdgyTone} from "../enum/tone.enum";
|
||||||
|
import {AngleEnum} from "../enum/angle.enum";
|
||||||
|
|
||||||
interface ProviderPair {
|
interface ProviderPair {
|
||||||
writer: ProviderName;
|
writer: ProviderName;
|
||||||
@@ -54,6 +55,15 @@ export class ProviderRouterService {
|
|||||||
}): RoutingDecision {
|
}): RoutingDecision {
|
||||||
const { language, contentType, style, tone } = params;
|
const { language, contentType, style, tone } = params;
|
||||||
|
|
||||||
|
if (tone === ContentTone.EMPATHETIC) {
|
||||||
|
return {
|
||||||
|
writer: 'openai', // warmest voice, less "AI-ish"
|
||||||
|
reviewer: 'openai',
|
||||||
|
useXEnrichment: false,
|
||||||
|
reason: 'Empathy context: GPT for warmer human-like tone',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// 🔥 EDGY TONES: route mạnh sang Grok (EN) hoặc DeepSeek (others)
|
// 🔥 EDGY TONES: route mạnh sang Grok (EN) hoặc DeepSeek (others)
|
||||||
// GPT thường refuse hoặc water down → tránh
|
// GPT thường refuse hoặc water down → tránh
|
||||||
if (tone && isEdgyTone(tone)) {
|
if (tone && isEdgyTone(tone)) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// services/quote-writer.service.ts
|
// services/quote-writer.service.ts
|
||||||
import {Injectable, Logger} from '@nestjs/common';
|
import {Injectable, Logger, UnprocessableEntityException} from '@nestjs/common';
|
||||||
import {ConfigService} from '@nestjs/config';
|
import {ConfigService} from '@nestjs/config';
|
||||||
import {AIProviderFactory} from '../providers/ai-provider.factory';
|
import {AIProviderFactory} from '../providers/ai-provider.factory';
|
||||||
import {ProviderRouterService} from './provider-router.service';
|
import {ProviderRouterService} from './provider-router.service';
|
||||||
@@ -28,8 +28,16 @@ export class QuoteWriterService {
|
|||||||
private safety: ContentSafetyService
|
private safety: ContentSafetyService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async generateQuote(dto: GenerateQuoteDto) {
|
async generateQuote(dto: GenerateQuoteDto, _retryCount = 0) {
|
||||||
this.logger.debug(`==> QuoteWriterService_generateQuote`);
|
const MAX_RETRIES = 2;
|
||||||
|
|
||||||
|
if (_retryCount >= MAX_RETRIES) {
|
||||||
|
throw new UnprocessableEntityException(
|
||||||
|
`Cannot generate safe content after ${MAX_RETRIES + 1} attempts`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.debug(`==> QuoteWriterService_generateQuote lần:${_retryCount}`);
|
||||||
// 1. Auto-detect quote type nếu không có
|
// 1. Auto-detect quote type nếu không có
|
||||||
const quoteType = dto.quoteType ?? suggestQuoteType(dto.originalPost, dto.yourAngle);
|
const quoteType = dto.quoteType ?? suggestQuoteType(dto.originalPost, dto.yourAngle);
|
||||||
this.logger.log(`Quote type: ${quoteType}`);
|
this.logger.log(`Quote type: ${quoteType}`);
|
||||||
@@ -132,7 +140,9 @@ export class QuoteWriterService {
|
|||||||
return this.generateQuote({
|
return this.generateQuote({
|
||||||
...dto,
|
...dto,
|
||||||
tone: this.downgradeTone(dto.tone!),
|
tone: this.downgradeTone(dto.tone!),
|
||||||
});
|
},
|
||||||
|
_retryCount + 1,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import {Action, Command, Ctx, On, Wizard, WizardStep} from "nestjs-telegraf";
|
|||||||
import {WIZARD_COMMENT2_SCENE_ID} from "../telegram.constants";
|
import {WIZARD_COMMENT2_SCENE_ID} from "../telegram.constants";
|
||||||
import * as scenes from "telegraf/scenes";
|
import * as scenes from "telegraf/scenes";
|
||||||
import {ManagerService} from "../../manager/manager.service";
|
import {ManagerService} from "../../manager/manager.service";
|
||||||
import {ANGLE_HINTS_TELEGRAM_BUTTON} from "../../content-writer/enum/angle.enum";
|
|
||||||
import {get_TONE_HINTS_TELEGRAM_BUTTON} from "../../content-writer/prompts/templates";
|
import {get_TONE_HINTS_TELEGRAM_BUTTON} from "../../content-writer/prompts/templates";
|
||||||
|
import {get_ANGLE_HINTS_TELEGRAM_BUTTON} from "../../content-writer/enum/angle.enum";
|
||||||
|
|
||||||
@Wizard(WIZARD_COMMENT2_SCENE_ID)
|
@Wizard(WIZARD_COMMENT2_SCENE_ID)
|
||||||
export class Comment2Wizard {
|
export class Comment2Wizard {
|
||||||
@@ -105,6 +105,7 @@ export class Comment2Wizard {
|
|||||||
(ctx.wizard.state as any).tone = tone;
|
(ctx.wizard.state as any).tone = tone;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const inline_keyboards = [];
|
const inline_keyboards = [];
|
||||||
|
const ANGLE_HINTS_TELEGRAM_BUTTON = get_ANGLE_HINTS_TELEGRAM_BUTTON(tone!);
|
||||||
Object.keys(ANGLE_HINTS_TELEGRAM_BUTTON).map(key => {
|
Object.keys(ANGLE_HINTS_TELEGRAM_BUTTON).map(key => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
inline_keyboards.push([{
|
inline_keyboards.push([{
|
||||||
|
|||||||
Reference in New Issue
Block a user