diff --git a/ai_knowledge_base_v2/apps/backend/app/services/model_service.py b/ai_knowledge_base_v2/apps/backend/app/services/model_service.py index 0530a58..d18e608 100644 --- a/ai_knowledge_base_v2/apps/backend/app/services/model_service.py +++ b/ai_knowledge_base_v2/apps/backend/app/services/model_service.py @@ -10,7 +10,7 @@ from sqlalchemy import select from sqlalchemy.orm import Session from app.core.config import get_settings -from app.models.ai_config import ModelConfig +from app.models.ai_config import ModelConfig, SystemConfig from app.services.external_errors import ExternalServiceError from app.services.rag_service import NO_HIT_ANSWER, RagResult @@ -27,17 +27,19 @@ class ModelCompletion: class ModelClientService: @staticmethod def complete(db: Session, rag_result: RagResult) -> ModelCompletion: + mock_model_enabled = _system_config_bool(db, "mock_model_enabled", get_settings().mock_model_enabled) model = db.scalar( select(ModelConfig) .where(ModelConfig.enabled == 1) .order_by(ModelConfig.id.desc()) .limit(1) ) - settings = get_settings() - if settings.mock_model_enabled or model is None: + if mock_model_enabled: model_name = model.model_name if model is not None else "mock-model" answer = _mock_answer(rag_result) else: + if model is None: + raise ExternalServiceError("未启用可用模型,请先在模型管理中启用一个模型。", provider="model") model_name = model.model_name answer = _call_configured_model(model, rag_result) @@ -92,6 +94,13 @@ def _mock_answer(rag_result: RagResult) -> str: ) +def _system_config_bool(db: Session, key: str, default: bool) -> bool: + config = db.scalar(select(SystemConfig).where(SystemConfig.config_key == key)) + if config is None or not config.config_value.strip(): + return default + return config.config_value.strip().lower() in {"1", "true", "yes", "on", "启用"} + + def _copy_model_with_overrides(model: ModelConfig, overrides: dict[str, Any]) -> ModelConfig: debug_model = ModelConfig( provider=model.provider, diff --git a/ai_knowledge_base_v2/apps/user-client/src/components/ChatMessage.vue b/ai_knowledge_base_v2/apps/user-client/src/components/ChatMessage.vue index ce3bb27..fa63177 100644 --- a/ai_knowledge_base_v2/apps/user-client/src/components/ChatMessage.vue +++ b/ai_knowledge_base_v2/apps/user-client/src/components/ChatMessage.vue @@ -14,9 +14,32 @@ const markdown = new MarkdownIt({ linkify: true, }); +const assistantParts = computed(() => { + if (props.role === "user") { + return { reasoning: "", answer: props.content }; + } + const matched = props.content.match(/([\s\S]*?)<\/think>/i); + if (matched) { + return { + reasoning: matched[1].trim(), + answer: props.content.replace(matched[0], "").trim(), + }; + } + const opening = props.content.match(/([\s\S]*)$/i); + if (opening) { + return { + reasoning: opening[1].trim(), + answer: props.content.slice(0, opening.index).trim(), + }; + } + return { reasoning: "", answer: props.content }; +}); + +const renderedReasoning = computed(() => markdown.render(assistantParts.value.reasoning || "")); + const renderedContent = computed(() => { if (props.role === "user") return props.content; - return markdown.render(props.content || ""); + return markdown.render(assistantParts.value.answer || ""); }); @@ -24,7 +47,13 @@ const renderedContent = computed(() => {
{{ role === "assistant" ? "AI" : "我" }}
-
+
{{ renderedContent }}
正在生成
diff --git a/ai_knowledge_base_v2/apps/user-client/src/styles.css b/ai_knowledge_base_v2/apps/user-client/src/styles.css index be52015..5d1e907 100644 --- a/ai_knowledge_base_v2/apps/user-client/src/styles.css +++ b/ai_knowledge_base_v2/apps/user-client/src/styles.css @@ -291,6 +291,33 @@ button:disabled { white-space: normal; } +.reasoning-panel { + margin-bottom: 10px; + border: 1px solid #dbe7e3; + border-radius: 12px; + background: #f2f7f5; + color: #496259; +} + +.reasoning-panel summary { + min-height: 36px; + display: flex; + align-items: center; + padding: 0 10px; + color: #315d52; + font-size: 13px; + font-weight: 700; + cursor: pointer; +} + +.reasoning-content { + max-height: 220px; + overflow-y: auto; + padding: 0 10px 10px; + color: #5d7169; + font-size: 13px; +} + .markdown-content > :first-child { margin-top: 0; }