diff --git a/backend/.env.example b/backend/.env.example index 9d6a4f7..f43cb6e 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -8,7 +8,7 @@ # 应用配置 # ========================================== APP_NAME=MuMuAINovel -APP_VERSION=1.1.4 +APP_VERSION=1.2.2 APP_HOST=0.0.0.0 APP_PORT=8000 DEBUG=false @@ -25,7 +25,7 @@ POSTGRES_PASSWORD=123456 POSTGRES_PORT=5432 # 数据库连接 URL(Docker 部署时自动生成) -DATABASE_URL=postgresql+asyncpg://mumuai:123456@localhost:5432/mumuai_novel +# DATABASE_URL=postgresql+asyncpg://mumuai:123456@localhost:5432/mumuai_novel # ========================================== # SQLite 数据库配置 @@ -33,13 +33,6 @@ DATABASE_URL=postgresql+asyncpg://mumuai:123456@localhost:5432/mumuai_novel # DATABASE_URL=sqlite+aiosqlite:///data/ai_story.db -# ========================================== -# 代理配置(可选) -# ========================================== -# HTTP_PROXY=http://your-proxy:port -# HTTPS_PROXY=http://your-proxy:port -# NO_PROXY=localhost,127.0.0.1 - # ========================================== # 日志配置 # ========================================== @@ -54,6 +47,13 @@ LOG_BACKUP_COUNT=30 # ========================================== CORS_ORIGINS=["http://localhost:8000","http://127.0.0.1:8000"] +# ========================================== +# 代理配置(可选) +# ========================================== +# HTTP_PROXY=http://your-proxy:port +# HTTPS_PROXY=http://your-proxy:port +# NO_PROXY=localhost,127.0.0.1 + # ========================================== # AI 服务配置(至少配置一个) # ========================================== @@ -71,11 +71,9 @@ DEFAULT_MAX_TOKENS=32000 # ========================================== # LinuxDO OAuth 配置(可选) # ========================================== -# LINUXDO_CLIENT_ID=your_client_id_here -# LINUXDO_CLIENT_SECRET=your_client_secret_here -# LINUXDO_REDIRECT_URI=http://localhost:8000/api/auth/callback - -# 前端 URL(OAuth 回调后重定向) +LINUXDO_CLIENT_ID=11111 +LINUXDO_CLIENT_SECRET=11111 +LINUXDO_REDIRECT_URI=http://localhost:8000/api/auth/callback FRONTEND_URL=http://localhost:8000 # 初始管理员(LinuxDO user_id) diff --git a/backend/app/api/organizations.py b/backend/app/api/organizations.py index 02c4cfc..af0cb0b 100644 --- a/backend/app/api/organizations.py +++ b/backend/app/api/organizations.py @@ -497,7 +497,7 @@ async def generate_organization_stream( - 其他要求:{gen_request.requirements or '无'} """ - yield await SSEResponse.send_progress("构建AI提示词...", 20) + yield await SSEResponse.send_progress("构建AI提示词...", 5) # 获取自定义提示词模板 template = await PromptService.get_template("SINGLE_ORGANIZATION_GENERATION", user_id, db) @@ -508,7 +508,7 @@ async def generate_organization_stream( user_input=user_input ) - yield await SSEResponse.send_progress("调用AI服务生成组织...", 30) + yield await SSEResponse.send_progress("调用AI服务生成组织...", 10) logger.info(f"🎯 开始为项目 {gen_request.project_id} 生成组织(SSE流式)") try: @@ -525,7 +525,7 @@ async def generate_organization_stream( # 定期更新字数(5-95%,AI生成占90%) if chunk_count % 5 == 0: - progress = min(5 + (chunk_count // 5), 95) + progress = min(10 + (chunk_count // 5), 95) yield await SSEResponse.send_progress( f"AI生成组织中... ({len(ai_content)}字符)", progress @@ -544,7 +544,7 @@ async def generate_organization_stream( yield await SSEResponse.send_error("AI服务返回空响应") return - yield await SSEResponse.send_progress("解析AI响应...", 96) + yield await SSEResponse.send_progress("解析AI响应...", 90) # ✅ 使用统一的 JSON 清洗方法 try: @@ -557,7 +557,7 @@ async def generate_organization_stream( yield await SSEResponse.send_error(f"AI返回的内容无法解析为JSON:{str(e)}") return - yield await SSEResponse.send_progress("创建组织记录...", 97) + yield await SSEResponse.send_progress("创建组织记录...", 95) # 创建角色记录(组织也是角色的一种) character = Character( diff --git a/backend/app/api/wizard_stream.py b/backend/app/api/wizard_stream.py index ea1277d..ce11231 100644 --- a/backend/app/api/wizard_stream.py +++ b/backend/app/api/wizard_stream.py @@ -134,10 +134,10 @@ async def world_building_generator( 请结合上述资料,生成符合历史/现实的世界观设定。""" final_prompt = enhanced_prompt - yield await SSEResponse.send_progress("💡 已整合参考资料,开始生成世界观...", 30) + yield await SSEResponse.send_progress("💡 已整合参考资料,开始生成世界观...", 10) else: final_prompt = base_prompt - yield await SSEResponse.send_progress("正在调用AI生成...", 30) + yield await SSEResponse.send_progress("正在调用AI生成...", 10) # ===== 流式生成世界观(带重试机制) ===== MAX_WORLD_RETRIES = 3 # 最多重试3次 @@ -148,7 +148,7 @@ async def world_building_generator( while world_retry_count < MAX_WORLD_RETRIES and not world_generation_success: try: retry_suffix = f" (重试{world_retry_count}/{MAX_WORLD_RETRIES})" if world_retry_count > 0 else "" - yield await SSEResponse.send_progress(f"生成世界观{retry_suffix}...", 30 + world_retry_count * 5) + yield await SSEResponse.send_progress(f"生成世界观{retry_suffix}...", 10 + world_retry_count * 5) # 流式生成世界观 accumulated_text = "" @@ -181,7 +181,7 @@ async def world_building_generator( if world_retry_count < MAX_WORLD_RETRIES: yield await SSEResponse.send_progress( f"⚠️ AI返回为空,准备重试...", - 30 + world_retry_count * 5, + 10 + world_retry_count * 5, "warning" ) continue @@ -221,7 +221,7 @@ async def world_building_generator( if world_retry_count < MAX_WORLD_RETRIES: yield await SSEResponse.send_progress( f"⚠️ JSON解析失败,准备重试...", - 30 + world_retry_count * 5, + 10 + world_retry_count * 5, "warning" ) continue @@ -241,7 +241,7 @@ async def world_building_generator( if world_retry_count < MAX_WORLD_RETRIES: yield await SSEResponse.send_progress( f"⚠️ 生成异常,准备重试...", - 30 + world_retry_count * 5, + 10 + world_retry_count * 5, "warning" ) continue @@ -1599,10 +1599,10 @@ async def world_building_regenerate_generator( 请结合上述资料,生成符合历史/现实的世界观设定。""" final_prompt = enhanced_prompt - yield await SSEResponse.send_progress("💡 已整合参考资料,开始生成世界观...", 30) + yield await SSEResponse.send_progress("💡 已整合参考资料,开始生成世界观...", 10) else: final_prompt = base_prompt - yield await SSEResponse.send_progress("正在调用AI生成...", 30) + yield await SSEResponse.send_progress("正在调用AI生成...", 10) # ===== 流式生成世界观(带重试机制) ===== MAX_WORLD_RETRIES = 3 # 最多重试3次 @@ -1613,7 +1613,7 @@ async def world_building_regenerate_generator( while world_retry_count < MAX_WORLD_RETRIES and not world_generation_success: try: retry_suffix = f" (重试{world_retry_count}/{MAX_WORLD_RETRIES})" if world_retry_count > 0 else "" - yield await SSEResponse.send_progress(f"重新生成世界观{retry_suffix}...", 30 + world_retry_count * 5) + yield await SSEResponse.send_progress(f"重新生成世界观{retry_suffix}...", 10 + world_retry_count * 5) # 流式生成世界观 accumulated_text = "" @@ -1630,7 +1630,7 @@ async def world_building_regenerate_generator( yield await SSEResponse.send_chunk(chunk) if chunk_count % 5 == 0: - progress = min(30 + (chunk_count // 5), 85) + progress = min(10 + (chunk_count // 5), 85) yield await SSEResponse.send_progress(f"生成中... ({len(accumulated_text)}字符)", progress) if chunk_count % 20 == 0: @@ -1643,7 +1643,7 @@ async def world_building_regenerate_generator( if world_retry_count < MAX_WORLD_RETRIES: yield await SSEResponse.send_progress( f"⚠️ AI返回为空,准备重试...", - 30 + world_retry_count * 5, + 10 + world_retry_count * 5, "warning" ) continue @@ -1679,7 +1679,7 @@ async def world_building_regenerate_generator( if world_retry_count < MAX_WORLD_RETRIES: yield await SSEResponse.send_progress( f"⚠️ JSON解析失败,准备重试...", - 30 + world_retry_count * 5, + 10 + world_retry_count * 5, "warning" ) continue @@ -1699,7 +1699,7 @@ async def world_building_regenerate_generator( if world_retry_count < MAX_WORLD_RETRIES: yield await SSEResponse.send_progress( f"⚠️ 生成异常,准备重试...", - 30 + world_retry_count * 5, + 10 + world_retry_count * 5, "warning" ) continue diff --git a/backend/scripts/entrypoint.sh b/backend/scripts/entrypoint.sh index 6d80f8a..30bdb0a 100644 --- a/backend/scripts/entrypoint.sh +++ b/backend/scripts/entrypoint.sh @@ -4,8 +4,14 @@ set -e # 遇到错误立即退出 +# 获取版本信息(从config.py中提取) +APP_VERSION=$(grep -oP "app_version:\s*str\s*=\s*\"\K[^\"]*" /app/app/config.py || echo "unknown") +BUILD_TIME=$(date '+%Y-%m-%d %H:%M:%S') + echo "================================================" echo "🚀 MuMuAINovel 启动中..." +echo "📦 版本: v${APP_VERSION}" +echo "🕐 启动时间: ${BUILD_TIME}" echo "================================================" # 数据库配置(从环境变量读取) diff --git a/docker-compose.yml b/docker-compose.yml index aab90e0..76629f6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -102,7 +102,7 @@ services: - DEFAULT_AI_PROVIDER=${DEFAULT_AI_PROVIDER:-openai} - DEFAULT_MODEL=${DEFAULT_MODEL:-gpt-4o-mini} - DEFAULT_TEMPERATURE=${DEFAULT_TEMPERATURE:-0.7} - - DEFAULT_MAX_TOKENS=${DEFAULT_MAX_TOKENS:-2000} + - DEFAULT_MAX_TOKENS=${DEFAULT_MAX_TOKENS:-32000} # LinuxDO OAuth 配置 - LINUXDO_CLIENT_ID=${LINUXDO_CLIENT_ID:-11111} diff --git a/frontend/package.json b/frontend/package.json index 075106b..958b0aa 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "1.2.1", + "version": "1.2.2", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/public/WX.png b/frontend/public/WX.png index 5adfc91..81de823 100644 Binary files a/frontend/public/WX.png and b/frontend/public/WX.png differ diff --git a/frontend/src/App.css b/frontend/src/App.css index 45a791e..da807c8 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -3,3 +3,17 @@ margin: 0; padding: 0; } + +/* 移动端按钮文本优化 */ +@media (max-width: 576px) { + .button-text-mobile { + font-size: 14px; + } + + /* 小屏幕下按钮文字可以隐藏,只保留图标 */ + @media (max-width: 480px) { + .button-text-mobile { + display: none; + } + } +} diff --git a/frontend/src/components/ChangelogModal.tsx b/frontend/src/components/ChangelogModal.tsx index 9f1e7c6..acafb7e 100644 --- a/frontend/src/components/ChangelogModal.tsx +++ b/frontend/src/components/ChangelogModal.tsx @@ -12,6 +12,7 @@ import { GithubOutlined, ReloadOutlined, ClockCircleOutlined, + SyncOutlined, } from '@ant-design/icons'; import { fetchChangelog, @@ -29,6 +30,7 @@ interface ChangelogModalProps { // 提交类型图标和颜色配置 const typeConfig: Record = { feature: { icon: , color: 'green', label: '新功能' }, + update: { icon: , color: 'geekblue', label: '更新' }, fix: { icon: , color: 'red', label: '修复' }, docs: { icon: , color: 'blue', label: '文档' }, style: { icon: , color: 'purple', label: '样式' }, diff --git a/frontend/src/pages/Careers.tsx b/frontend/src/pages/Careers.tsx index 6a38b40..af0ea61 100644 --- a/frontend/src/pages/Careers.tsx +++ b/frontend/src/pages/Careers.tsx @@ -283,7 +283,10 @@ export default function Careers() { flexWrap: 'wrap', gap: '12px' }}> - 职业管理 + + <TrophyOutlined style={{ marginRight: 8 }} /> + 职业管理 + + )} +
请选择一个组织查看详情
+ + + ) : ( + <> + {/* 工具栏 - 移动端显示项目标题和组织列表按钮 */} + {isMobile && ( + +
+ + + + 组织管理 + + {currentProject?.title} + + +
+
+ )} + + {/* 内容区域 */} +
+ + - - ) : ( - -
- 请从左侧选择一个组织查看详情 -
+
- )} -
+ + + )} - + {/* 添加成员模态框 */}
-

故事大纲

+

+ + 故事大纲 +

{currentProject?.outline_mode && ( {currentProject.outline_mode === 'one-to-one' ? '传统模式 (1→1)' : '细化模式 (1→N)'} diff --git a/frontend/src/pages/Relationships.tsx b/frontend/src/pages/Relationships.tsx index 57f1b49..ea09f9b 100644 --- a/frontend/src/pages/Relationships.tsx +++ b/frontend/src/pages/Relationships.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react'; import { useParams } from 'react-router-dom'; import { Card, Table, Tag, Button, Space, message, Modal, Form, Select, Slider, Input, Tabs, AutoComplete } from 'antd'; -import { PlusOutlined, TeamOutlined, UserOutlined, EditOutlined } from '@ant-design/icons'; +import { PlusOutlined, ApartmentOutlined, UserOutlined, EditOutlined } from '@ant-design/icons'; import { useStore } from '../store'; import axios from 'axios'; @@ -308,7 +308,7 @@ export default function Relationships() { - + 关系管理 {!isMobile && {currentProject?.title}} diff --git a/frontend/src/pages/WorldSetting.tsx b/frontend/src/pages/WorldSetting.tsx index f388986..0a507f6 100644 --- a/frontend/src/pages/WorldSetting.tsx +++ b/frontend/src/pages/WorldSetting.tsx @@ -1,4 +1,4 @@ -import { Card, Descriptions, Empty, Typography, Button, Modal, Form, Input, message, Space } from 'antd'; +import { Card, Descriptions, Empty, Typography, Button, Modal, Form, Input, message, Flex } from 'antd'; import { GlobalOutlined, EditOutlined, SyncOutlined } from '@ant-design/icons'; import { useState } from 'react'; import { useStore } from '../store'; @@ -174,39 +174,51 @@ export default function WorldSetting() { backgroundColor: '#fff', padding: '16px 0', marginBottom: 24, - borderBottom: '1px solid #f0f0f0', - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between' + borderBottom: '1px solid #f0f0f0' }}> -
- -

世界设定

-
- - - - + +
+ +

世界设定

+
+ + + + +
{/* 可滚动内容区域 */} diff --git a/frontend/src/pages/WritingStyles.tsx b/frontend/src/pages/WritingStyles.tsx index 1ff4af6..1372ef5 100644 --- a/frontend/src/pages/WritingStyles.tsx +++ b/frontend/src/pages/WritingStyles.tsx @@ -180,7 +180,10 @@ export default function WritingStyles() { justifyContent: 'space-between', alignItems: isMobile ? 'stretch' : 'center' }}> -

写作风格管理

+

+ + 写作风格管理 +