马良AI写作初始化仓库
This commit is contained in:
142
AINovalServer/src/main/resources/application-dev.yml
Normal file
142
AINovalServer/src/main/resources/application-dev.yml
Normal 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
|
||||
@@ -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 # 禁用安全验证,方便测试
|
||||
272
AINovalServer/src/main/resources/application-prod.yml
Normal file
272
AINovalServer/src/main/resources/application-prod.yml
Normal 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
|
||||
80
AINovalServer/src/main/resources/application-test.yml
Normal file
80
AINovalServer/src/main/resources/application-test.yml
Normal 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
|
||||
350
AINovalServer/src/main/resources/application.yml
Normal file
350
AINovalServer/src/main/resources/application.yml
Normal 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 # 启用SSL:true(465端口),禁用SSL:false(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
|
||||
@@ -0,0 +1,34 @@
|
||||
你是一位专业的小说创作顾问,擅长为作者提供多样化的剧情发展选项。请根据以下信息,为作者生成 {{numOptions}} 个不同的剧情大纲选项,每个选项应该是对当前故事的合理延续。
|
||||
|
||||
小说当前进展:{{targetChapter}}
|
||||
|
||||
{{#if authorGuidance}}
|
||||
作者的创作引导:{{authorGuidance}}
|
||||
{{/if}}
|
||||
|
||||
请为每个选项提供以下内容:
|
||||
1. 一个简短但吸引人的标题
|
||||
2. 剧情概要(200-300字)
|
||||
3. 主要事件(3-5个关键点)
|
||||
4. 涉及的角色
|
||||
5. 冲突或悬念
|
||||
|
||||
格式要求:
|
||||
选项1:[标题]
|
||||
[剧情概要]
|
||||
主要事件:
|
||||
- [事件1]
|
||||
- [事件2]
|
||||
- [事件3]
|
||||
涉及角色:[角色列表]
|
||||
冲突/悬念:[冲突或悬念描述]
|
||||
|
||||
选项2:[标题]
|
||||
...
|
||||
|
||||
注意事项:
|
||||
- 每个选项应该有明显的差异,提供真正不同的故事发展方向
|
||||
- 保持与已有故事的连贯性和一致性
|
||||
- 考虑角色动机和故事内在逻辑
|
||||
- 提供有创意但合理的发展方向
|
||||
- 确保每个选项都有足够的戏剧冲突和情感张力
|
||||
142
AINovalServer/src/main/resources/static/chat-memory-modes.json
Normal file
142
AINovalServer/src/main/resources/static/chat-memory-modes.json
Normal 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"
|
||||
}
|
||||
}
|
||||
380
AINovalServer/src/main/resources/static/gemini-test.html
Normal file
380
AINovalServer/src/main/resources/static/gemini-test.html
Normal 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>
|
||||
1
AINovalServer/src/main/resources/static/test-image.jpg
Normal file
1
AINovalServer/src/main/resources/static/test-image.jpg
Normal file
@@ -0,0 +1 @@
|
||||
"This is a test image file"
|
||||
Reference in New Issue
Block a user