马良AI写作初始化仓库

This commit is contained in:
邓滨杰
2025-09-10 00:07:52 +08:00
parent 3c06bb1a03
commit 39c0f8840f
1309 changed files with 318528 additions and 0 deletions

View File

@@ -0,0 +1,142 @@
server:
port: 18080
shutdown: graceful
netty:
connection-timeout: 5s
error:
include-message: always
include-binding-errors: always
include-stacktrace: on-param
include-exception: true
proxy:
enabled: true
host: 127.0.0.1 # 容器内改为 host.docker.internal 或代理容器名
port: 6888 # 若是 socks 监听端口不同,请改为实际端口
type: socks # 关键:切换为 socks
applySystemProperties: true
applyProxySelector: false
trustAllCerts: true # 排障时可临时 true生产请 false
spring:
application:
name: ai-novel-server
data:
mongodb:
uri: mongodb://localhost:27017/ainoval?
auto-index-creation: true
database: ainovel
authentication-database: admin
webflux:
base-path: /
lifecycle:
timeout-per-shutdown-phase: 30s
logging:
level:
root: INFO
com.ainovel: DEBUG
# 添加MongoDB查询日志配置
org.springframework.data.mongodb: WARN
com.ainovel.server.service.impl.ImportServiceImpl: DEBUG
com.ainovel.server.config.MongoQueryCounterAspect: WARN
org.springframework.data.mongodb.core.ReactiveMongoTemplate: WARN
org.springframework.data.mongodb.core.MongoTemplate: WARN
org.springframework.data.mongodb.repository.query: WARN
org.springframework.web: WARN
org.springframework.security: WARN
org.eclipse.angus.mail.smtp: DEBUG
reactor.netty: WARN
# MongoDB 映射调试日志 - 用于排查 MappingException
org.springframework.data.mongodb.core.convert: WARN
org.springframework.data.mongodb.core.mapping: WARN
org.springframework.data.mapping: WARN
org.springframework.data.mapping.model: WARN
# MongoDB Java Driver 调试(可选)
org.mongodb.driver: WARN
# MongoDB Event Listener - 关闭详细的文档内容日志
org.springframework.data.mongodb.core.mapping.event.LoggingEventListener: WARN
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus,jmx
jmx:
exposure:
include: "*"
endpoint:
health:
show-details: always
jmx:
enabled: true
jmx:
enabled: true
# 更新过时的配置
prometheus:
metrics:
export:
enabled: true
tracing:
enabled: true
sampling:
probability: 1.0
exporter:
otlp:
enabled: true
endpoint: http://${OTLP_TRACES_HOST:localhost}:${OTLP_TRACES_PORT:11800}
metrics:
tags:
application: ${spring.application.name}
environment: dev
distribution:
percentiles-histogram:
"[tasks.execution.time]": true
"[http.server.requests]": true
percentiles:
"[tasks.execution.time]": [0.5, 0.95, 0.99]
"[http.server.requests]": [0.5, 0.95, 0.99]
slo:
"[tasks.execution.time]": [2000ms, 10000ms, 30000ms]
"[http.server.requests]": [100ms, 300ms, 1s, 3s, 5s]
# 自定义配置
ainovel:
security:
jwt:
# 为了兼容旧代码读取此键的场景,这里也统一引用同一个环境变量
secret-key: ${JWT_SECRET:aiNovelSecretKey12345678901234567890}
expiration-time: 86400000 # 24小时单位毫秒
refresh-token-expiration: 604800000 # 7天单位毫秒
performance:
virtual-threads:
enabled: true
monitoring:
enabled: true
testing:
security-disabled: true # 禁用安全验证,方便测试
# 添加MongoDB查询日志配置
mongodb:
logging:
enabled: true
query-level: DEBUG
result-count: true
# 禁用JWT验证方便测试
security:
jwt:
disabled: false
# 统一 JwtServiceImpl 的读取来源dev 环境)
jwt:
secret: ${JWT_SECRET:aiNovelSecretKey12345678901234567890}
expiration: 86400000
refresh-expiration: 604800000
# 显式固定开发环境的 Jasypt 加解密配置,避免被外部环境变量干扰
jasypt:
encryptor:
password: ${JASYPT_ENCRYPTOR_PASSWORD:${JASYPT_PASSWORD:MaliangAI_SecretKey_PleaseChangeThis_2025}}
algorithm: PBEWITHHMACSHA512ANDAES_256
iv-generator-classname: org.jasypt.iv.RandomIvGenerator

View File

@@ -0,0 +1,58 @@
server:
port: 18088
shutdown: graceful
netty:
connection-timeout: 5s
spring:
application:
name: ai-novel-server
data:
mongodb:
uri: localhost:27017/ainoval
auto-index-creation: true
database: ainovel
authentication-database: admin
webflux:
base-path: /api
lifecycle:
timeout-per-shutdown-phase: 30s
logging:
level:
root: INFO
com.ainovel: DEBUG
org.springframework.data.mongodb: INFO
org.springframework.web: INFO
org.springframework.security: DEBUG
reactor.netty: INFO
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
metrics:
export:
prometheus:
enabled: true
tags:
application: ${spring.application.name}
# 自定义配置
ainovel:
security:
jwt:
secret-key: test_secret_key_for_performance_testing
expiration-time: 86400000 # 24小时单位毫秒
refresh-token-expiration: 604800000 # 7天单位毫秒
performance:
virtual-threads:
enabled: true
monitoring:
enabled: true
testing:
security-disabled: true # 禁用安全验证,方便测试

View File

@@ -0,0 +1,272 @@
server:
port: 18080
shutdown: graceful
netty:
connection-timeout: 10s
error:
include-message: never
include-binding-errors: never
include-stacktrace: never
include-exception: false
spring:
application:
name: ai-novel-server
data:
mongodb:
uri: mongodb://${MONGO_USER:${MONGO_USERNAME:}}:${MONGO_PASSWORD}@${MONGO_HOST:localhost}:${MONGO_PORT:27017}/${MONGO_DATABASE:ainovel}?authSource=${MONGO_AUTH_DB:admin}
auto-index-creation: true
database: ${MONGO_DATABASE:ainovel}
authentication-database: ${MONGO_AUTH_DB:admin}
map-key-dot-replacement: "#DOT#"
webflux:
base-path: /
lifecycle:
timeout-per-shutdown-phase: 30s
rabbitmq:
host: ${RABBITMQ_HOST:localhost}
port: ${RABBITMQ_PORT:5672}
username: ${RABBITMQ_USER:guest}
password: ${RABBITMQ_PASSWORD:guest}
virtual-host: ${RABBITMQ_VHOST:/}
publisher-confirm-type: correlated
publisher-returns: true
listener:
simple:
acknowledge-mode: manual
prefetch: 5
concurrency: 8
max-concurrency: 16
retry:
enabled: true
max-attempts: 3
connection-timeout: 10000
template:
mandatory: true
receive-timeout: 60000
reply-timeout: 60000
logging:
level:
root: WARN
com.ainovel: INFO
org.springframework.data.mongodb: WARN
org.springframework.web: WARN
reactor.netty: WARN
com.ainovel.server: INFO
com.ainovel.server.task: WARN
org.springframework.amqp: WARN
request: false
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%X{traceId:-}] [%X{userId:-}] [%thread] %logger{36} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%X{traceId:-}] [%X{userId:-}] [%thread] %logger{36} - %msg%n"
file:
name: /var/log/ainoval/application.log
max-size: 100MB
max-history: 30
total-size-cap: 3GB
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
jmx:
exposure:
include: "*"
endpoint:
health:
show-details: when-authorized
jmx:
enabled: true
jmx:
enabled: true
prometheus:
metrics:
export:
enabled: true
metrics:
tags:
application: ${spring.application.name}
environment: production
distribution:
percentiles-histogram:
"[tasks.execution.time]": true
"[http.server.requests]": true
percentiles:
"[tasks.execution.time]": [0.5, 0.95, 0.99]
"[http.server.requests]": [0.5, 0.95, 0.99]
slo:
"[tasks.execution.time]": [2000ms, 10000ms, 30000ms]
"[http.server.requests]": [100ms, 300ms, 1s, 3s, 5s]
# 生产环境配置
ainovel:
security:
jwt:
secret-key: ${JWT_SECRET_KEY}
expiration-time: 86400000 # 24小时
refresh-token-expiration: 604800000 # 7天
performance:
virtual-threads:
enabled: true
monitoring:
enabled: true
testing:
security-disabled: false # 生产环境启用安全验证
version-control:
enabled: true
auto-save-history: true
max-history-count: 50
storage:
default-provider: ${STORAGE_PROVIDER:alioss}
covers-path: ${STORAGE_COVERS_PATH:covers}
test-on-startup: true
aliyun:
endpoint: ${ALIYUN_OSS_ENDPOINT}
access-key-id: ${ALIYUN_OSS_ACCESS_KEY_ID}
access-key-secret: ${ALIYUN_OSS_ACCESS_KEY_SECRET}
bucket-name: ${ALIYUN_OSS_BUCKET_NAME}
base-url: ${ALIYUN_OSS_BASE_URL}
region: ${ALIYUN_OSS_REGION:cn-shanghai}
ai:
default-prompts:
scene-to-summary: "请根据以下小说场景内容,生成一段简洁的摘要。\n场景内容:\n{input}\n参考信息:\n{context}"
summary-to-scene: "请根据以下摘要/大纲,结合参考信息,生成一段详细的小说场景。\n摘要/大纲:\n{input}\n参考信息:\n{context}"
rag:
retrieval-k: 5
resilience:
timeout:
duration: 60s
retry:
max-attempts: 5
backoff:
initial-delay: 2s
multiplier: 2
# 向量存储配置
vectorstore:
chroma:
url: ${CHROMA_URL:http://localhost:18000}
collection: ${CHROMA_COLLECTION:ainovel}
use-random-collection: true
reuse-collection: true
# 代理配置(生产环境可能不需要)
proxy:
enabled: ${PROXY_ENABLED:true}
host: ${PROXY_HOST:127.0.0.1}
port: ${PROXY_PORT:6888}
type: http # 关键:切换为 socks
applySystemProperties: true
applyProxySelector: true
trustAllCerts: false # 排障时可临时 true生产请 false
jwt:
secret: ${JWT_SECRET}
expiration: 86400000
refresh-expiration: 604800000
ai:
model:
default: ${AI_DEFAULT_MODEL:gpt-3.5-turbo}
temperature: ${AI_TEMPERATURE:0.7}
max-tokens: ${AI_MAX_TOKENS:204800}
openai:
api-key: ${OPENAI_API_KEY}
gemini:
api-key: ${GEMINI_API_KEY}
jasypt:
encryptor:
password: ${JASYPT_PASSWORD:MaliangAI_SecretKey_PleaseChangeThis_2025}
algorithm: PBEWITHHMACSHA512ANDAES_256
iv-generator-classname: org.jasypt.iv.RandomIvGenerator
# 生产环境限流配置
task:
ratelimiter:
type: ${RATE_LIMITER_TYPE:memory}
dimensions:
default: USER_PROVIDER_MODEL
gemini: GLOBAL
sensitive_tasks: HYBRID
high_performance: PROVIDER_MODEL
default:
rate: 20.0
burstCapacity: 40
defaultTimeoutMillis: 10000
dimension: USER_PROVIDER_MODEL
providers:
gemini:
strategy: CONSERVATIVE
dimension: GLOBAL
rate: 0.2
burstCapacity: 1
dailyLimit: 180
safetyBuffer: 20
retryStrategy: EXPONENTIAL_BACKOFF
maxRetryAttempts: 5
defaultTimeoutMillis: 60000
openai:
strategy: STANDARD
dimension: USER_PROVIDER_MODEL
rate: 10.0
burstCapacity: 20
retryStrategy: LINEAR_BACKOFF
maxRetryAttempts: 3
defaultTimeoutMillis: 10000
anthropic:
strategy: STANDARD
dimension: USER_PROVIDER_MODEL
rate: 8.0
burstCapacity: 16
retryStrategy: LINEAR_BACKOFF
maxRetryAttempts: 3
defaultTimeoutMillis: 10000
gpt-4:
strategy: CONSERVATIVE
rate: 5.0
burstCapacity: 10
retryStrategy: EXPONENTIAL_BACKOFF
maxRetryAttempts: 4
defaultTimeoutMillis: 15000
openrouter:
strategy: STANDARD
rate: 15.0
burstCapacity: 30
retryStrategy: INTELLIGENT_BACKOFF
maxRetryAttempts: 4
defaultTimeoutMillis: 10000
siliconflow:
strategy: STANDARD
rate: 20.0
burstCapacity: 40
retryStrategy: LINEAR_BACKOFF
maxRetryAttempts: 3
defaultTimeoutMillis: 10000
retry:
maxAttempts: 5
initialDelayMillis: 2000
maxDelayMillis: 3600000
backoffFactor: 2.0
jitterFactor: 0.1
delays: 5000,15000,60000
shutdown:
awaitTerminationTimeout: PT60S
# 安全配置
security:
jwt:
disabled: false

View File

@@ -0,0 +1,80 @@
server:
port: 18080
shutdown: graceful
netty:
connection-timeout: 5s
error:
include-message: always
include-binding-errors: always
include-stacktrace: on-param
include-exception: true
spring:
application:
name: ai-novel-server
data:
mongodb:
uri: mongodb://localhost:27017/ainoval?
auto-index-creation: true
database: ainovel
authentication-database: admin
webflux:
base-path: /
lifecycle:
timeout-per-shutdown-phase: 30s
logging:
level:
root: INFO
com.ainovel: DEBUG
# 添加MongoDB查询日志配置
org.springframework.data.mongodb: DEBUG
org.springframework.data.mongodb.core.ReactiveMongoTemplate: DEBUG
org.springframework.data.mongodb.core.MongoTemplate: DEBUG
org.springframework.data.mongodb.repository.query: DEBUG
org.springframework.web: DEBUG
org.springframework.security: DEBUG
reactor.netty: DEBUG
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
# 更新过时的配置
prometheus:
metrics:
export:
enabled: true
metrics:
tags:
application: ${spring.application.name}
# 自定义配置
ainovel:
security:
jwt:
secret-key: test_secret_key_for_performance_testing
expiration-time: 86400000 # 24小时单位毫秒
refresh-token-expiration: 604800000 # 7天单位毫秒
performance:
virtual-threads:
enabled: true
monitoring:
enabled: true
testing:
security-disabled: true # 禁用安全验证,方便测试
# 添加MongoDB查询日志配置
mongodb:
logging:
enabled: true
query-level: DEBUG
result-count: true
# 禁用JWT验证方便测试
security:
jwt:
disabled: true

View File

@@ -0,0 +1,350 @@
server:
port: 18080
shutdown: graceful
netty:
connection-timeout: 5s
spring:
main:
allow-bean-definition-overriding: true
allow-circular-references: true
application:
name: ai-novel-server
data:
mongodb:
uri: mongodb://mongo:123456@localhost:27017/ainoval?authSource=admin&authMechanism=SCRAM-SHA-1
auto-index-creation: true
password: 123456
username: mongo
database: ainovel
authentication-database: admin
map-key-dot-replacement: "#DOT#"
webflux:
base-path: /
lifecycle:
timeout-per-shutdown-phase: 30s
rabbitmq:
enabled: false
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
publisher-confirm-type: correlated
publisher-returns: true
dynamic: false
listener:
simple:
acknowledge-mode: manual
prefetch: 1
concurrency: 4
max-concurrency: 8
auto-startup: false
missingQueuesFatal: false
retry:
enabled: false
direct:
auto-startup: false
missingQueuesFatal: false
connection-timeout: 5000
template:
mandatory: true
receive-timeout: 30000
reply-timeout: 30000
mail:
# 阿里云邮件推送服务配置 - SSL模式推荐
host: smtpdm.aliyun.com
port: 465 # SSL端口465非SSL端口25
username: ${ALIYUN_MAIL_USERNAME:111}
password: ${ALIYUN_MAIL_PASSWORD:111}
protocol: smtp
default-encoding: UTF-8
properties:
mail:
smtp:
auth: true
starttls:
enable: false # SSL模式不需要STARTTLS
ssl:
enable: true # 启用SSLtrue(465端口)禁用SSLfalse(25端口)
trust: smtpdm.aliyun.com
socketFactory:
class: javax.net.ssl.SSLSocketFactory
fallback: false
port: 465 # 必须与上面的port一致
connectiontimeout: 10000
timeout: 10000
writetimeout: 10000
# 阿里云短信配置
aliyun:
sms:
access-key-id: ${ALIYUN_SMS_ACCESS_KEY_ID:your-access-key}
access-key-secret: ${ALIYUN_SMS_ACCESS_KEY_SECRET:your-secret-key}
sign-name: ${ALIYUN_SMS_SIGN_NAME:AINoval}
template-code: ${ALIYUN_SMS_TEMPLATE_CODE:SMS_123456}
# 应用配置
app:
name: AINoval
logging:
level:
root: INFO
com.ainovel: DEBUG
org.springframework.data.mongodb: INFO
org.springframework.web: INFO
reactor.netty: INFO
com.ainovel.server: DEBUG
com.ainovel.server.task: INFO
org.springframework.amqp: INFO
request: true
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) [%X{traceId:-}] [%X{userId:-}] [%thread] %logger{36} - %msg%n"
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus,jmx
jmx:
exposure:
include: "*"
endpoint:
health:
show-details: when-authorized
jmx:
enabled: true
health:
mail:
enabled: false
jmx:
enabled: true
prometheus:
metrics:
export:
enabled: true
otlp:
metrics:
export:
enabled: false # 开发环境禁用OTLP只在生产环境启用
metrics:
tags:
application: ${spring.application.name}
distribution:
percentiles-histogram:
"[tasks.execution.time]": true
percentiles:
"[tasks.execution.time]": [0.5, 0.95, 0.99]
slo:
"[tasks.execution.time]": [1000ms, 5000ms, 10000ms]
# 自定义配置
ainovel:
security:
jwt:
# 统一引用 JWT_SECRET 环境变量,保持与 dev/prod 一致
secret-key: ${JWT_SECRET:changeme_in_production_environment}
expiration-time: 86400000 # 24小时单位毫秒
refresh-token-expiration: 604800000 # 7天单位毫秒
performance:
virtual-threads:
enabled: true
monitoring:
enabled: true
version-control:
enabled: true
auto-save-history: true
max-history-count: 20
mail:
# 邮件服务测试配置
test-on-startup: ${MAIL_TEST_ON_STARTUP:true} # 是否在启动时测试邮件配置
test-email: ${MAIL_TEST_EMAIL:} # 默认测试邮箱,启动时会发送测试邮件
storage:
default-provider: alioss
covers-path: covers
# 是否在启动时测试OSS连接
test-on-startup: true
# 阿里云OSS配置
aliyun:
endpoint: https://oss-cn-shanghai.aliyuncs.com
access-key-id: ${ALIYUN_OSS_ACCESS_KEY_ID}
access-key-secret: ${ALIYUN_OSS_ACCESS_KEY_SECRET}
bucket-name: ${ALIYUN_OSS_BUCKET_NAME}
base-url: ${ALIYUN_OSS_BASE_URL}
region: cn-shanghai
ai:
default-prompts:
scene-to-summary: "请根据以下小说场景内容,生成一段简洁的摘要。\n场景内容:\n{input}\n参考信息:\n{context}"
summary-to-scene: "请根据以下摘要/大纲,结合参考信息,生成一段详细的小说场景。\n摘要/大纲:\n{input}\n参考信息:\n{context}"
rag:
# RAG检索相关配置
retrieval-k: 5
# Resilience配置
resilience:
timeout:
duration: 30s
retry:
max-attempts: 3
backoff:
initial-delay: 1s
multiplier: 2
# 各AI功能开关
features:
setting-tree-generation:
# 启动时是否初始化 SETTING_TREE_GENERATION 的提示词/策略模板
init-on-startup: false
registration:
quick-enabled: ${REGISTRATION_QUICK_ENABLED:true}
email-enabled: ${REGISTRATION_EMAIL_ENABLED:false}
phone-enabled: ${REGISTRATION_PHONE_ENABLED:false}
# 向量存储配置
vectorstore:
chroma:
enabled: false
url: http://localhost:18000
collection: ainovel
use-random-collection: false
reuse-collection: true
# 代理配置
proxy:
enabled: true
host: localhost
port: 6888
jwt:
# 统一使用环境变量 JWT_SECRET提供开发默认值
secret: ${JWT_SECRET:aiNovelSecretKey12345678901234567890}
expiration: 86400000
refresh-expiration: 604800000
payment:
wechat:
mch-id: ${WECHAT_MCH_ID:}
app-id: ${WECHAT_APP_ID:}
api-v3-key: ${WECHAT_API_V3_KEY:}
merchant-serial-no: ${WECHAT_MCH_SERIAL_NO:}
merchant-private-key-pem: ${WECHAT_MCH_PRIVATE_KEY_PEM:}
platform-public-key-pem: ${WECHAT_PLATFORM_PUBLIC_KEY_PEM:}
notify-url: ${WECHAT_NOTIFY_URL:http://localhost:18080/api/v1/payments/notify/WECHAT}
sandbox: ${WECHAT_SANDBOX:false}
alipay:
app-id: ${ALIPAY_APP_ID:}
merchant-private-key-pem: ${ALIPAY_MERCHANT_PRIVATE_KEY_PEM:}
merchant-public-key-pem: ${ALIPAY_MERCHANT_PUBLIC_KEY_PEM:}
alipay-public-key-pem: ${ALIPAY_PUBLIC_KEY_PEM:}
notify-url: ${ALIPAY_NOTIFY_URL:http://localhost:18080/api/v1/payments/notify/ALIPAY}
sandbox: ${ALIPAY_SANDBOX:false}
ai:
model:
default: gpt-3.5-turbo
temperature: 0.7
max-tokens: 8192
jasypt:
encryptor:
password: AINoval_Secure_Encryption_Key_2025
algorithm: PBEWITHHMACSHA512ANDAES_256
iv-generator-classname: org.jasypt.iv.RandomIvGenerator
# 限流器配置
task:
transport: local
local:
concurrency: 4
# 限流配置
ratelimiter:
# 限流器类型: memory (基于内存) 或 redis (分布式)
type: memory
# 限流维度配置
dimensions:
default: USER_PROVIDER_MODEL # 默认:用户+供应商+模型
gemini: GLOBAL # Gemini全局限流免费层共享配额
sensitive_tasks: HYBRID # 敏感任务:最细粒度限流
high_performance: PROVIDER_MODEL # 高性能:供应商+模型
# 默认限流配置
default:
# 每秒许可数量 (QPS)
rate: 10.0
# 突发容量 (令牌桶最大容量)
burstCapacity: 20
# 获取许可的默认超时时间(毫秒)
defaultTimeoutMillis: 5000
# 默认限流维度
dimension: USER_PROVIDER_MODEL
# 各AI提供商或模型的特定限流配置
providers:
# Gemini特殊配置 - 应对200次/天限制
gemini:
strategy: CONSERVATIVE # 保守策略
dimension: GLOBAL # 全局维度限流(免费层共享配额)
rate: 2 # 每秒0.2个请求 (约180次/天)
burstCapacity: 2 # 突发容量1
dailyLimit: 2000000 # 日限额200次
safetyBuffer: 20 # 安全缓冲20次
retryStrategy: EXPONENTIAL_BACKOFF # 4倍指数退避
maxRetryAttempts: 3 # 最大重试3次降低以避免长时间等待
defaultTimeoutMillis: 20000 # 20秒超时降低以更快失败并重试
# OpenAI标准配置
openai:
strategy: STANDARD # 标准策略
dimension: USER_PROVIDER_MODEL # 用户+供应商+模型维度
rate: 5.0 # 每秒5个请求
burstCapacity: 10 # 突发容量10
retryStrategy: LINEAR_BACKOFF
maxRetryAttempts: 3
defaultTimeoutMillis: 3000
# Anthropic激进配置
anthropic:
strategy: AGGRESSIVE # 激进策略
dimension: USER_PROVIDER_MODEL # 用户+供应商+模型维度
rate: 3.0 # 每秒3个请求
burstCapacity: 6 # 突发容量6
retryStrategy: LINEAR_BACKOFF
maxRetryAttempts: 2
defaultTimeoutMillis: 3000
# GPT-4特殊限制
gpt-4:
strategy: CONSERVATIVE # 保守策略GPT-4通常限制更严格
rate: 2.0 # 每秒2个请求
burstCapacity: 4 # 突发容量4
retryStrategy: EXPONENTIAL_BACKOFF
maxRetryAttempts: 4
defaultTimeoutMillis: 10000
# 其他供应商自适应配置
openrouter:
strategy: ADAPTIVE # 自适应策略
rate: 8.0
burstCapacity: 15
retryStrategy: INTELLIGENT_BACKOFF
maxRetryAttempts: 4
defaultTimeoutMillis: 5000
siliconflow:
strategy: STANDARD
rate: 10.0
burstCapacity: 20
retryStrategy: LINEAR_BACKOFF
maxRetryAttempts: 3
defaultTimeoutMillis: 5000
# 重试配置
retry:
maxAttempts: 5
initialDelayMillis: 5000
maxDelayMillis: 3600000 # 1 hour
backoffFactor: 2.0
jitterFactor: 0.1
# 添加缺失的 delays 配置,使用默认值
delays: 15000,60000,300000
# 关闭配置
shutdown:
awaitTerminationTimeout: PT30S # ISO-8601 duration

View File

@@ -0,0 +1,34 @@
你是一位专业的小说创作顾问,擅长为作者提供多样化的剧情发展选项。请根据以下信息,为作者生成 {{numOptions}} 个不同的剧情大纲选项,每个选项应该是对当前故事的合理延续。
小说当前进展:{{targetChapter}}
{{#if authorGuidance}}
作者的创作引导:{{authorGuidance}}
{{/if}}
请为每个选项提供以下内容:
1. 一个简短但吸引人的标题
2. 剧情概要200-300字
3. 主要事件3-5个关键点
4. 涉及的角色
5. 冲突或悬念
格式要求:
选项1[标题]
[剧情概要]
主要事件:
- [事件1]
- [事件2]
- [事件3]
涉及角色:[角色列表]
冲突/悬念:[冲突或悬念描述]
选项2[标题]
...
注意事项:
- 每个选项应该有明显的差异,提供真正不同的故事发展方向
- 保持与已有故事的连贯性和一致性
- 考虑角色动机和故事内在逻辑
- 提供有创意但合理的发展方向
- 确保每个选项都有足够的戏剧冲突和情感张力

View File

@@ -0,0 +1,142 @@
{
"chatMemoryModes": [
{
"code": "history",
"displayName": "历史模式",
"description": "保留完整的对话历史记录,不进行任何修改或删除",
"icon": "📝",
"features": [
"保留所有消息",
"完整的对话上下文",
"适合短期对话"
],
"limitations": [
"可能超出模型上下文限制",
"增加API调用成本",
"处理速度可能较慢"
],
"defaultConfig": {
"mode": "history",
"preserveSystemMessages": true,
"enablePersistence": false
}
},
{
"code": "message_window",
"displayName": "消息窗口记忆",
"description": "保留最近的N条消息自动淘汰旧消息",
"icon": "🔄",
"features": [
"控制消息数量",
"保持对话连贯性",
"适合长期对话"
],
"limitations": [
"可能丢失早期对话信息",
"需要合理设置窗口大小"
],
"defaultConfig": {
"mode": "message_window",
"maxMessages": 50,
"preserveSystemMessages": true,
"enablePersistence": false
},
"configOptions": [
{
"key": "maxMessages",
"type": "number",
"min": 10,
"max": 200,
"default": 50,
"description": "保留的最大消息数量"
}
]
},
{
"code": "token_window",
"displayName": "令牌窗口记忆",
"description": "基于令牌数量保留最近的对话内容",
"icon": "🎯",
"features": [
"精确控制上下文长度",
"优化成本效益",
"兼容不同模型的限制"
],
"limitations": [
"需要估算令牌数量",
"对中文支持可能不够精确"
],
"defaultConfig": {
"mode": "token_window",
"maxTokens": 4000,
"preserveSystemMessages": true,
"enablePersistence": false
},
"configOptions": [
{
"key": "maxTokens",
"type": "number",
"min": 1000,
"max": 32000,
"default": 4000,
"description": "保留的最大令牌数量"
}
]
},
{
"code": "summary",
"displayName": "总结记忆",
"description": "对历史消息进行智能总结,保留关键信息",
"icon": "📋",
"features": [
"智能信息压缩",
"保留对话要点",
"适合超长对话"
],
"limitations": [
"需要额外的AI调用",
"可能丢失细节信息",
"总结质量依赖模型能力"
],
"defaultConfig": {
"mode": "summary",
"summaryThreshold": 20,
"summaryRetainCount": 5,
"preserveSystemMessages": true,
"enablePersistence": false
},
"configOptions": [
{
"key": "summaryThreshold",
"type": "number",
"min": 10,
"max": 100,
"default": 20,
"description": "触发总结的消息数量阈值"
},
{
"key": "summaryRetainCount",
"type": "number",
"min": 3,
"max": 20,
"default": 5,
"description": "总结后保留的最近消息数量"
}
]
}
],
"recommendations": {
"shortConversation": "history",
"mediumConversation": "message_window",
"longConversation": "token_window",
"veryLongConversation": "summary"
},
"apiEndpoints": {
"sendWithMemory": "/api/v1/ai-chat/messages/send-with-memory",
"streamWithMemory": "/api/v1/ai-chat/messages/stream-with-memory",
"getMemoryHistory": "/api/v1/ai-chat/messages/memory-history",
"updateMemoryConfig": "/api/v1/ai-chat/sessions/update-memory-config",
"clearMemory": "/api/v1/ai-chat/sessions/clear-memory",
"getSupportedModes": "/api/v1/ai-chat/memory/supported-modes"
}
}

View File

@@ -0,0 +1,380 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>谷歌Gemini API测试</title>
<style>
body {
font-family: 'Arial', sans-serif;
line-height: 1.6;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
color: #333;
}
.container {
max-width: 800px;
margin: 0 auto;
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
h1 {
color: #4285f4;
text-align: center;
margin-bottom: 30px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="text"],
input[type="number"],
textarea,
select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
font-size: 14px;
}
button {
background-color: #4285f4;
color: white;
border: none;
padding: 10px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
margin-right: 10px;
}
button:hover {
background-color: #3367d6;
}
.response {
margin-top: 20px;
padding: 15px;
background-color: #f9f9f9;
border-radius: 4px;
border-left: 4px solid #4285f4;
white-space: pre-wrap;
max-height: 400px;
overflow-y: auto;
}
.messages-container {
margin-top: 15px;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
max-height: 200px;
overflow-y: auto;
}
.message {
padding: 10px;
margin-bottom: 10px;
border-radius: 4px;
}
.message.user {
background-color: #e8f0fe;
}
.message.assistant {
background-color: #f1f3f4;
}
.message.system {
background-color: #fce8e6;
}
.message-controls {
display: flex;
margin-bottom: 10px;
}
.message-controls select {
width: 120px;
margin-right: 10px;
}
.message-controls button {
padding: 5px 10px;
font-size: 14px;
}
.loading {
text-align: center;
margin-top: 20px;
display: none;
}
.loading:after {
content: " .";
animation: dots 1s steps(5, end) infinite;
}
@keyframes dots {
0%, 20% { content: " ."; }
40% { content: " .."; }
60% { content: " ..."; }
80%, 100% { content: " ...."; }
}
</style>
</head>
<body>
<div class="container">
<h1>谷歌Gemini API测试</h1>
<div class="form-group">
<label for="apiKey">API密钥</label>
<input type="text" id="apiKey" placeholder="输入您的Gemini API密钥">
</div>
<div class="form-group">
<label for="model">模型</label>
<select id="model">
<option value="gemini-2.0-flash">gemini-2.0-flash</option>
<option value="gemini-2.0-pro">gemini-2.0-pro</option>
<option value="gemini-2.0-flash-lite">gemini-2.0-flash-lite</option>
</select>
</div>
<div class="form-group">
<label for="prompt">系统提示词</label>
<textarea id="prompt" rows="3" placeholder="输入系统提示词(可选)"></textarea>
</div>
<div class="form-group">
<label for="temperature">温度 (0-2)</label>
<input type="number" id="temperature" min="0" max="2" step="0.1" value="0.7">
</div>
<div class="form-group">
<label for="maxTokens">最大令牌数</label>
<input type="number" id="maxTokens" min="1" max="8192" value="1000">
</div>
<div class="form-group">
<label>对话消息</label>
<div class="messages-container" id="messagesContainer"></div>
<div class="message-controls">
<select id="messageRole" aria-label="消息角色">
<option value="user">用户</option>
<option value="assistant">助手</option>
<option value="system">系统</option>
</select>
<input type="text" id="messageContent" placeholder="输入消息内容">
<button onclick="addMessage()">添加消息</button>
</div>
</div>
<div>
<button onclick="validateApiKey()">验证API密钥</button>
<button onclick="generateContent()">生成内容</button>
<button onclick="generateContentStream()">流式生成</button>
<button onclick="clearResponse()">清除响应</button>
</div>
<div class="loading" id="loading">正在生成内容</div>
<div class="response" id="response"></div>
</div>
<script>
// 消息列表
let messages = [];
// 添加消息
function addMessage() {
const role = document.getElementById('messageRole').value;
const content = document.getElementById('messageContent').value;
if (!content.trim()) {
alert('消息内容不能为空');
return;
}
messages.push({ role, content });
renderMessages();
document.getElementById('messageContent').value = '';
}
// 渲染消息列表
function renderMessages() {
const container = document.getElementById('messagesContainer');
container.innerHTML = '';
messages.forEach((message, index) => {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${message.role}`;
messageDiv.innerHTML = `
<strong>${getRoleName(message.role)}:</strong> ${message.content}
<button onclick="removeMessage(${index})" style="float: right; padding: 2px 5px; font-size: 12px;">删除</button>
`;
container.appendChild(messageDiv);
});
}
// 获取角色名称
function getRoleName(role) {
switch (role) {
case 'user': return '用户';
case 'assistant': return '助手';
case 'system': return '系统';
default: return role;
}
}
// 删除消息
function removeMessage(index) {
messages.splice(index, 1);
renderMessages();
}
// 验证API密钥
function validateApiKey() {
const apiKey = document.getElementById('apiKey').value;
const model = document.getElementById('model').value;
if (!apiKey) {
alert('请输入API密钥');
return;
}
setLoading(true);
fetch('/api/test/gemini/validate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
apiKey,
model
})
})
.then(response => response.json())
.then(data => {
setLoading(false);
document.getElementById('response').textContent =
data.valid ? 'API密钥有效 ✓' : 'API密钥无效 ✗';
})
.catch(error => {
setLoading(false);
document.getElementById('response').textContent = '验证失败: ' + error.message;
});
}
// 生成内容
function generateContent() {
const apiKey = document.getElementById('apiKey').value;
const model = document.getElementById('model').value;
const prompt = document.getElementById('prompt').value;
const temperature = parseFloat(document.getElementById('temperature').value);
const maxTokens = parseInt(document.getElementById('maxTokens').value);
if (!apiKey) {
alert('请输入API密钥');
return;
}
if (messages.length === 0) {
alert('请添加至少一条消息');
return;
}
setLoading(true);
fetch('/api/test/gemini', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
apiKey,
model,
prompt,
temperature,
maxTokens,
messages
})
})
.then(response => response.json())
.then(data => {
setLoading(false);
document.getElementById('response').textContent = JSON.stringify(data, null, 2);
})
.catch(error => {
setLoading(false);
document.getElementById('response').textContent = '生成失败: ' + error.message;
});
}
// 流式生成内容
function generateContentStream() {
const apiKey = document.getElementById('apiKey').value;
const model = document.getElementById('model').value;
const prompt = document.getElementById('prompt').value;
const temperature = parseFloat(document.getElementById('temperature').value);
const maxTokens = parseInt(document.getElementById('maxTokens').value);
if (!apiKey) {
alert('请输入API密钥');
return;
}
if (messages.length === 0) {
alert('请添加至少一条消息');
return;
}
setLoading(true);
document.getElementById('response').textContent = '';
const eventSource = new EventSource(`/api/test/gemini/stream?dummy=${Date.now()}`);
// 发送请求数据
fetch('/api/test/gemini/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
apiKey,
model,
prompt,
temperature,
maxTokens,
messages
})
});
eventSource.onmessage = function(event) {
const responseDiv = document.getElementById('response');
responseDiv.textContent += event.data;
responseDiv.scrollTop = responseDiv.scrollHeight;
};
eventSource.onerror = function() {
setLoading(false);
eventSource.close();
};
// 5分钟后自动关闭连接
setTimeout(() => {
setLoading(false);
eventSource.close();
}, 5 * 60 * 1000);
}
// 清除响应
function clearResponse() {
document.getElementById('response').textContent = '';
}
// 设置加载状态
function setLoading(isLoading) {
document.getElementById('loading').style.display = isLoading ? 'block' : 'none';
}
</script>
</body>
</html>

View File

@@ -0,0 +1 @@
"This is a test image file"