fix:1.修复章节规划预览窗口UI显示问题
2.修复部分端点删除章节后字数没有更新的问题
This commit is contained in:
@@ -279,9 +279,20 @@ async def delete_outline(
|
|||||||
project_id = outline.project_id
|
project_id = outline.project_id
|
||||||
deleted_order = outline.order_index
|
deleted_order = outline.order_index
|
||||||
|
|
||||||
# 根据项目模式删除对应的章节
|
# 获取要删除的章节并计算总字数
|
||||||
|
deleted_word_count = 0
|
||||||
if project.outline_mode == 'one-to-one':
|
if project.outline_mode == 'one-to-one':
|
||||||
# one-to-one模式:通过chapter_number删除对应章节
|
# one-to-one模式:通过chapter_number获取对应章节
|
||||||
|
chapters_result = await db.execute(
|
||||||
|
select(Chapter).where(
|
||||||
|
Chapter.project_id == project_id,
|
||||||
|
Chapter.chapter_number == outline.order_index
|
||||||
|
)
|
||||||
|
)
|
||||||
|
chapters_to_delete = chapters_result.scalars().all()
|
||||||
|
deleted_word_count = sum(ch.word_count or 0 for ch in chapters_to_delete)
|
||||||
|
|
||||||
|
# 删除章节
|
||||||
delete_result = await db.execute(
|
delete_result = await db.execute(
|
||||||
delete(Chapter).where(
|
delete(Chapter).where(
|
||||||
Chapter.project_id == project_id,
|
Chapter.project_id == project_id,
|
||||||
@@ -289,14 +300,26 @@ async def delete_outline(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
deleted_chapters_count = delete_result.rowcount
|
deleted_chapters_count = delete_result.rowcount
|
||||||
logger.info(f"一对一模式:删除大纲 {outline_id}(序号{outline.order_index}),同时删除了第{outline.order_index}章({deleted_chapters_count}个章节)")
|
logger.info(f"一对一模式:删除大纲 {outline_id}(序号{outline.order_index}),同时删除了第{outline.order_index}章({deleted_chapters_count}个章节,{deleted_word_count}字)")
|
||||||
else:
|
else:
|
||||||
# one-to-many模式:通过outline_id删除关联章节
|
# one-to-many模式:通过outline_id获取关联章节
|
||||||
|
chapters_result = await db.execute(
|
||||||
|
select(Chapter).where(Chapter.outline_id == outline_id)
|
||||||
|
)
|
||||||
|
chapters_to_delete = chapters_result.scalars().all()
|
||||||
|
deleted_word_count = sum(ch.word_count or 0 for ch in chapters_to_delete)
|
||||||
|
|
||||||
|
# 删除章节
|
||||||
delete_result = await db.execute(
|
delete_result = await db.execute(
|
||||||
delete(Chapter).where(Chapter.outline_id == outline_id)
|
delete(Chapter).where(Chapter.outline_id == outline_id)
|
||||||
)
|
)
|
||||||
deleted_chapters_count = delete_result.rowcount
|
deleted_chapters_count = delete_result.rowcount
|
||||||
logger.info(f"一对多模式:删除大纲 {outline_id},同时删除了 {deleted_chapters_count} 个关联章节")
|
logger.info(f"一对多模式:删除大纲 {outline_id},同时删除了 {deleted_chapters_count} 个关联章节({deleted_word_count}字)")
|
||||||
|
|
||||||
|
# 更新项目字数
|
||||||
|
if deleted_word_count > 0:
|
||||||
|
project.current_words = max(0, project.current_words - deleted_word_count)
|
||||||
|
logger.info(f"更新项目字数:减少 {deleted_word_count} 字")
|
||||||
|
|
||||||
# 删除大纲
|
# 删除大纲
|
||||||
await db.delete(outline)
|
await db.delete(outline)
|
||||||
@@ -530,12 +553,24 @@ async def _generate_new_outline(
|
|||||||
|
|
||||||
from sqlalchemy import delete as sql_delete
|
from sqlalchemy import delete as sql_delete
|
||||||
|
|
||||||
# 先删除所有旧章节(无论是一对一还是一对多模式)
|
# 先获取所有旧章节并计算总字数
|
||||||
|
old_chapters_result = await db.execute(
|
||||||
|
select(Chapter).where(Chapter.project_id == project.id)
|
||||||
|
)
|
||||||
|
old_chapters = old_chapters_result.scalars().all()
|
||||||
|
deleted_word_count = sum(ch.word_count or 0 for ch in old_chapters)
|
||||||
|
|
||||||
|
# 删除所有旧章节(无论是一对一还是一对多模式)
|
||||||
delete_result = await db.execute(
|
delete_result = await db.execute(
|
||||||
sql_delete(Chapter).where(Chapter.project_id == project.id)
|
sql_delete(Chapter).where(Chapter.project_id == project.id)
|
||||||
)
|
)
|
||||||
deleted_chapters_count = delete_result.rowcount
|
deleted_chapters_count = delete_result.rowcount
|
||||||
logger.info(f"✅ 全新生成:删除了 {deleted_chapters_count} 个旧章节")
|
logger.info(f"✅ 全新生成:删除了 {deleted_chapters_count} 个旧章节({deleted_word_count}字)")
|
||||||
|
|
||||||
|
# 更新项目字数
|
||||||
|
if deleted_word_count > 0:
|
||||||
|
project.current_words = max(0, project.current_words - deleted_word_count)
|
||||||
|
logger.info(f"更新项目字数:减少 {deleted_word_count} 字")
|
||||||
|
|
||||||
# 再删除所有旧大纲
|
# 再删除所有旧大纲
|
||||||
delete_outline_result = await db.execute(
|
delete_outline_result = await db.execute(
|
||||||
@@ -1156,12 +1191,24 @@ async def new_outline_generator(
|
|||||||
|
|
||||||
from sqlalchemy import delete as sql_delete
|
from sqlalchemy import delete as sql_delete
|
||||||
|
|
||||||
# 先删除所有旧章节
|
# 先获取所有旧章节并计算总字数
|
||||||
|
old_chapters_result = await db.execute(
|
||||||
|
select(Chapter).where(Chapter.project_id == project_id)
|
||||||
|
)
|
||||||
|
old_chapters = old_chapters_result.scalars().all()
|
||||||
|
deleted_word_count = sum(ch.word_count or 0 for ch in old_chapters)
|
||||||
|
|
||||||
|
# 删除所有旧章节
|
||||||
delete_chapters_result = await db.execute(
|
delete_chapters_result = await db.execute(
|
||||||
sql_delete(Chapter).where(Chapter.project_id == project_id)
|
sql_delete(Chapter).where(Chapter.project_id == project_id)
|
||||||
)
|
)
|
||||||
deleted_chapters_count = delete_chapters_result.rowcount
|
deleted_chapters_count = delete_chapters_result.rowcount
|
||||||
logger.info(f"✅ 全新生成:删除了 {deleted_chapters_count} 个旧章节")
|
logger.info(f"✅ 全新生成:删除了 {deleted_chapters_count} 个旧章节({deleted_word_count}字)")
|
||||||
|
|
||||||
|
# 更新项目字数
|
||||||
|
if deleted_word_count > 0:
|
||||||
|
project.current_words = max(0, project.current_words - deleted_word_count)
|
||||||
|
logger.info(f"更新项目字数:减少 {deleted_word_count} 字")
|
||||||
|
|
||||||
# 再删除所有旧大纲
|
# 再删除所有旧大纲
|
||||||
delete_outlines_result = await db.execute(
|
delete_outlines_result = await db.execute(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.11",
|
"version": "1.0.12",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -127,3 +127,7 @@ body {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-tabs-dropdown {
|
||||||
|
z-index: 2000 !important;
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default function Chapters() {
|
|||||||
const [writingStyles, setWritingStyles] = useState<WritingStyle[]>([]);
|
const [writingStyles, setWritingStyles] = useState<WritingStyle[]>([]);
|
||||||
const [selectedStyleId, setSelectedStyleId] = useState<number | undefined>();
|
const [selectedStyleId, setSelectedStyleId] = useState<number | undefined>();
|
||||||
const [targetWordCount, setTargetWordCount] = useState<number>(3000);
|
const [targetWordCount, setTargetWordCount] = useState<number>(3000);
|
||||||
const [availableModels, setAvailableModels] = useState<Array<{value: string, label: string}>>([]);
|
const [availableModels, setAvailableModels] = useState<Array<{ value: string, label: string }>>([]);
|
||||||
const [selectedModel, setSelectedModel] = useState<string | undefined>();
|
const [selectedModel, setSelectedModel] = useState<string | undefined>();
|
||||||
const [batchSelectedModel, setBatchSelectedModel] = useState<string | undefined>(); // 批量生成的模型选择
|
const [batchSelectedModel, setBatchSelectedModel] = useState<string | undefined>(); // 批量生成的模型选择
|
||||||
const [temporaryNarrativePerspective, setTemporaryNarrativePerspective] = useState<string | undefined>(); // 临时人称选择
|
const [temporaryNarrativePerspective, setTemporaryNarrativePerspective] = useState<string | undefined>(); // 临时人称选择
|
||||||
@@ -710,6 +710,12 @@ export default function Chapters() {
|
|||||||
if (status.completed > 0) {
|
if (status.completed > 0) {
|
||||||
refreshChapters();
|
refreshChapters();
|
||||||
loadAnalysisTasks();
|
loadAnalysisTasks();
|
||||||
|
|
||||||
|
// 刷新项目信息以实时更新总字数统计
|
||||||
|
if (currentProject?.id) {
|
||||||
|
const updatedProject = await projectApi.getProject(currentProject.id);
|
||||||
|
setCurrentProject(updatedProject);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 任务完成或失败,停止轮询
|
// 任务完成或失败,停止轮询
|
||||||
@@ -725,6 +731,12 @@ export default function Chapters() {
|
|||||||
await refreshChapters();
|
await refreshChapters();
|
||||||
await loadAnalysisTasks();
|
await loadAnalysisTasks();
|
||||||
|
|
||||||
|
// 刷新项目信息以更新总字数统计
|
||||||
|
if (currentProject?.id) {
|
||||||
|
const updatedProject = await projectApi.getProject(currentProject.id);
|
||||||
|
setCurrentProject(updatedProject);
|
||||||
|
}
|
||||||
|
|
||||||
if (status.status === 'completed') {
|
if (status.status === 'completed') {
|
||||||
message.success(`批量生成完成!成功生成 ${status.completed} 章`);
|
message.success(`批量生成完成!成功生成 ${status.completed} 章`);
|
||||||
} else if (status.status === 'failed') {
|
} else if (status.status === 'failed') {
|
||||||
@@ -770,6 +782,12 @@ export default function Chapters() {
|
|||||||
// 取消后立即刷新章节列表和分析任务,显示已生成的章节
|
// 取消后立即刷新章节列表和分析任务,显示已生成的章节
|
||||||
await refreshChapters();
|
await refreshChapters();
|
||||||
await loadAnalysisTasks();
|
await loadAnalysisTasks();
|
||||||
|
|
||||||
|
// 刷新项目信息以更新总字数统计
|
||||||
|
if (currentProject?.id) {
|
||||||
|
const updatedProject = await projectApi.getProject(currentProject.id);
|
||||||
|
setCurrentProject(updatedProject);
|
||||||
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error('取消失败:' + (error.message || '未知错误'));
|
message.error('取消失败:' + (error.message || '未知错误'));
|
||||||
}
|
}
|
||||||
@@ -1333,21 +1351,21 @@ export default function Chapters() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChapterSelect = (chapterId: string) => {
|
const handleChapterSelect = (chapterId: string) => {
|
||||||
const element = document.getElementById(`chapter-item-${chapterId}`);
|
const element = document.getElementById(`chapter-item-${chapterId}`);
|
||||||
if (element) {
|
if (element) {
|
||||||
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
// Optional: add a visual highlight effect
|
// Optional: add a visual highlight effect
|
||||||
element.style.transition = 'background-color 0.5s ease';
|
element.style.transition = 'background-color 0.5s ease';
|
||||||
element.style.backgroundColor = '#e6f7ff';
|
element.style.backgroundColor = '#e6f7ff';
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
element.style.backgroundColor = '';
|
element.style.backgroundColor = '';
|
||||||
}, 1500);
|
}, 1500);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||||
<div style={{
|
<div style={{
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
top: 0,
|
top: 0,
|
||||||
@@ -1441,8 +1459,8 @@ export default function Chapters() {
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
!hasContent ? '请先生成章节内容' :
|
!hasContent ? '请先生成章节内容' :
|
||||||
isAnalyzing ? '分析进行中,请稍候...' :
|
isAnalyzing ? '分析进行中,请稍候...' :
|
||||||
''
|
''
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
@@ -1524,8 +1542,8 @@ export default function Chapters() {
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
!hasContent ? '请先生成章节内容' :
|
!hasContent ? '请先生成章节内容' :
|
||||||
isAnalyzing ? '分析中' :
|
isAnalyzing ? '分析中' :
|
||||||
'查看分析'
|
'查看分析'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
@@ -1601,64 +1619,64 @@ export default function Chapters() {
|
|||||||
alignItems: isMobile ? 'flex-start' : 'center',
|
alignItems: isMobile ? 'flex-start' : 'center',
|
||||||
}}
|
}}
|
||||||
actions={isMobile ? undefined : [
|
actions={isMobile ? undefined : [
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
icon={<EditOutlined />}
|
|
||||||
onClick={() => handleOpenEditor(item.id)}
|
|
||||||
>
|
|
||||||
编辑内容
|
|
||||||
</Button>,
|
|
||||||
(() => {
|
|
||||||
const task = analysisTasksMap[item.id];
|
|
||||||
const isAnalyzing = task && (task.status === 'pending' || task.status === 'running');
|
|
||||||
const hasContent = item.content && item.content.trim() !== '';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip
|
|
||||||
title={
|
|
||||||
!hasContent ? '请先生成章节内容' :
|
|
||||||
isAnalyzing ? '分析进行中,请稍候...' :
|
|
||||||
''
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
icon={isAnalyzing ? <SyncOutlined spin /> : <FundOutlined />}
|
icon={<EditOutlined />}
|
||||||
onClick={() => handleShowAnalysis(item.id)}
|
onClick={() => handleOpenEditor(item.id)}
|
||||||
disabled={!hasContent || isAnalyzing}
|
|
||||||
loading={isAnalyzing}
|
|
||||||
>
|
>
|
||||||
{isAnalyzing ? '分析中' : '查看分析'}
|
编辑内容
|
||||||
</Button>
|
</Button>,
|
||||||
</Tooltip>
|
(() => {
|
||||||
);
|
const task = analysisTasksMap[item.id];
|
||||||
})(),
|
const isAnalyzing = task && (task.status === 'pending' || task.status === 'running');
|
||||||
<Button
|
const hasContent = item.content && item.content.trim() !== '';
|
||||||
type="text"
|
|
||||||
icon={<SettingOutlined />}
|
return (
|
||||||
onClick={() => handleOpenModal(item.id)}
|
<Tooltip
|
||||||
>
|
title={
|
||||||
修改信息
|
!hasContent ? '请先生成章节内容' :
|
||||||
</Button>,
|
isAnalyzing ? '分析进行中,请稍候...' :
|
||||||
// 只在 one-to-many 模式下显示删除按钮
|
''
|
||||||
...(currentProject.outline_mode === 'one-to-many' ? [
|
}
|
||||||
<Popconfirm
|
>
|
||||||
title="确定删除这个章节吗?"
|
<Button
|
||||||
description="删除后将无法恢复,章节内容和分析结果都将被删除。"
|
type="text"
|
||||||
onConfirm={() => handleDeleteChapter(item.id)}
|
icon={isAnalyzing ? <SyncOutlined spin /> : <FundOutlined />}
|
||||||
okText="确定删除"
|
onClick={() => handleShowAnalysis(item.id)}
|
||||||
cancelText="取消"
|
disabled={!hasContent || isAnalyzing}
|
||||||
okButtonProps={{ danger: true }}
|
loading={isAnalyzing}
|
||||||
>
|
>
|
||||||
<Button
|
{isAnalyzing ? '分析中' : '查看分析'}
|
||||||
type="text"
|
</Button>
|
||||||
danger
|
</Tooltip>
|
||||||
icon={<DeleteOutlined />}
|
);
|
||||||
>
|
})(),
|
||||||
删除
|
<Button
|
||||||
</Button>
|
type="text"
|
||||||
</Popconfirm>
|
icon={<SettingOutlined />}
|
||||||
] : []),
|
onClick={() => handleOpenModal(item.id)}
|
||||||
|
>
|
||||||
|
修改信息
|
||||||
|
</Button>,
|
||||||
|
// 只在 one-to-many 模式下显示删除按钮
|
||||||
|
...(currentProject.outline_mode === 'one-to-many' ? [
|
||||||
|
<Popconfirm
|
||||||
|
title="确定删除这个章节吗?"
|
||||||
|
description="删除后将无法恢复,章节内容和分析结果都将被删除。"
|
||||||
|
onConfirm={() => handleDeleteChapter(item.id)}
|
||||||
|
okText="确定删除"
|
||||||
|
cancelText="取消"
|
||||||
|
okButtonProps={{ danger: true }}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
danger
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
] : []),
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<div style={{ width: '100%' }}>
|
<div style={{ width: '100%' }}>
|
||||||
@@ -1741,8 +1759,8 @@ export default function Chapters() {
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
!hasContent ? '请先生成章节内容' :
|
!hasContent ? '请先生成章节内容' :
|
||||||
isAnalyzing ? '分析中' :
|
isAnalyzing ? '分析中' :
|
||||||
'查看分析'
|
'查看分析'
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ import { useOutlineSync } from '../store/hooks';
|
|||||||
import { cardStyles } from '../components/CardStyles';
|
import { cardStyles } from '../components/CardStyles';
|
||||||
import { SSEPostClient } from '../utils/sseClient';
|
import { SSEPostClient } from '../utils/sseClient';
|
||||||
import { SSEProgressModal } from '../components/SSEProgressModal';
|
import { SSEProgressModal } from '../components/SSEProgressModal';
|
||||||
import { outlineApi, chapterApi } from '../services/api';
|
import { outlineApi, chapterApi, projectApi } from '../services/api';
|
||||||
import type { OutlineExpansionResponse, BatchOutlineExpansionResponse } from '../types';
|
import type { OutlineExpansionResponse, BatchOutlineExpansionResponse } from '../types';
|
||||||
|
|
||||||
const { TextArea } = Input;
|
const { TextArea } = Input;
|
||||||
|
|
||||||
export default function Outline() {
|
export default function Outline() {
|
||||||
const { currentProject, outlines } = useStore();
|
const { currentProject, outlines, setCurrentProject } = useStore();
|
||||||
const [isGenerating, setIsGenerating] = useState(false);
|
const [isGenerating, setIsGenerating] = useState(false);
|
||||||
const [editForm] = Form.useForm();
|
const [editForm] = Form.useForm();
|
||||||
const [generateForm] = Form.useForm();
|
const [generateForm] = Form.useForm();
|
||||||
@@ -142,8 +142,12 @@ export default function Outline() {
|
|||||||
try {
|
try {
|
||||||
await deleteOutline(id);
|
await deleteOutline(id);
|
||||||
message.success('删除成功');
|
message.success('删除成功');
|
||||||
// 删除后刷新大纲列表,确保显示最新的顺序
|
// 删除后刷新大纲列表和项目信息,更新字数显示
|
||||||
await refreshOutlines();
|
await refreshOutlines();
|
||||||
|
if (currentProject?.id) {
|
||||||
|
const updatedProject = await projectApi.getProject(currentProject.id);
|
||||||
|
setCurrentProject(updatedProject);
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
message.error('删除失败');
|
message.error('删除失败');
|
||||||
}
|
}
|
||||||
@@ -745,7 +749,12 @@ export default function Outline() {
|
|||||||
await Promise.all(deletePromises);
|
await Promise.all(deletePromises);
|
||||||
|
|
||||||
message.success(`已删除《${outlineTitle}》展开的所有 ${chapters.length} 个章节`);
|
message.success(`已删除《${outlineTitle}》展开的所有 ${chapters.length} 个章节`);
|
||||||
refreshOutlines();
|
await refreshOutlines();
|
||||||
|
// 刷新项目信息以更新字数显示
|
||||||
|
if (currentProject?.id) {
|
||||||
|
const updatedProject = await projectApi.getProject(currentProject.id);
|
||||||
|
setCurrentProject(updatedProject);
|
||||||
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error(error.response?.data?.detail || '删除章节失败');
|
message.error(error.response?.data?.detail || '删除章节失败');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
MuMuAINovel 服务启动脚本
|
||||||
|
用于快速启动后端服务
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# 将backend目录添加到Python路径
|
||||||
|
backend_dir = Path(__file__).parent / "backend"
|
||||||
|
sys.path.insert(0, str(backend_dir))
|
||||||
|
|
||||||
|
import uvicorn
|
||||||
|
from app.config import settings
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"🚀 启动 {settings.app_name} v{settings.app_version}")
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"📍 服务地址: http://{settings.app_host}:{settings.app_port}")
|
||||||
|
print(f"📚 API文档: http://{settings.app_host}:{settings.app_port}/docs")
|
||||||
|
print(f"🔧 调试模式: {'启用' if settings.debug else '禁用'}")
|
||||||
|
print(f"🗄️ 数据库: PostgreSQL")
|
||||||
|
print("=" * 60)
|
||||||
|
print()
|
||||||
|
|
||||||
|
uvicorn.run(
|
||||||
|
"app.main:app",
|
||||||
|
host=settings.app_host,
|
||||||
|
port=settings.app_port,
|
||||||
|
reload=settings.debug,
|
||||||
|
log_level="info"
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user