fix:问答时间和样式修正

This commit is contained in:
shijing 2026-07-23 09:46:22 +08:00
parent f6aa6d9a97
commit 8fb76a9cb6
7 changed files with 38 additions and 34 deletions

View File

@ -0,0 +1,22 @@
from datetime import datetime, timezone
from typing import Annotated
from pydantic import PlainSerializer
def _to_utc_iso(v: datetime | None) -> str | None:
if v is None:
return None
if v.tzinfo is None:
v = v.replace(tzinfo=timezone.utc)
else:
v = v.astimezone(timezone.utc)
return v.isoformat().replace("+00:00", "Z")
# 数据库里存的是 datetime.utcnow() 产出的 naive UTC。裸 ISO 字符串
# 会被 JS new Date(...) 当本地时间解析, 少 8 小时。统一序列化为带 Z 的
# UTC ISO, 前端/小程序拿到后 new Date(...) 会正确转成本地时区。
UtcDatetime = Annotated[
datetime, PlainSerializer(_to_utc_iso, return_type=str, when_used="json")
]

View File

@ -1,7 +1,7 @@
from datetime import datetime
from pydantic import BaseModel from pydantic import BaseModel
from ._types import UtcDatetime
class AuditLogResponse(BaseModel): class AuditLogResponse(BaseModel):
id: int id: int
@ -10,7 +10,7 @@ class AuditLogResponse(BaseModel):
target_type: str | None target_type: str | None
target_id: str | None target_id: str | None
detail_json: dict | None detail_json: dict | None
created_at: datetime created_at: UtcDatetime
class StatsOverviewResponse(BaseModel): class StatsOverviewResponse(BaseModel):

View File

@ -1,7 +1,7 @@
from datetime import datetime
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from ._types import UtcDatetime
class ConversationCreate(BaseModel): class ConversationCreate(BaseModel):
title: str = "新会话" title: str = "新会话"
@ -10,8 +10,8 @@ class ConversationCreate(BaseModel):
class ConversationResponse(BaseModel): class ConversationResponse(BaseModel):
id: int id: int
title: str title: str
created_at: datetime created_at: UtcDatetime
last_message_at: datetime | None last_message_at: UtcDatetime | None
class ChatStreamRequest(BaseModel): class ChatStreamRequest(BaseModel):
@ -42,7 +42,7 @@ class MessageResponse(BaseModel):
status: str status: str
token_usage: int token_usage: int
pu_usage: int pu_usage: int
created_at: datetime created_at: UtcDatetime
references: list[ReferenceResponse] references: list[ReferenceResponse]
attachments: list[AttachmentResponse] = Field(default_factory=list) attachments: list[AttachmentResponse] = Field(default_factory=list)

View File

@ -1,7 +1,7 @@
from datetime import datetime
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from ._types import UtcDatetime
class DocumentCreate(BaseModel): class DocumentCreate(BaseModel):
title: str = Field(min_length=1, max_length=255) title: str = Field(min_length=1, max_length=255)
@ -31,4 +31,4 @@ class DocumentResponse(BaseModel):
status: str status: str
owner_dept: str | None owner_dept: str | None
tags: list[str] tags: list[str]
created_at: datetime created_at: UtcDatetime

View File

@ -1,7 +1,7 @@
from datetime import datetime
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from ._types import UtcDatetime
class UserCreate(BaseModel): class UserCreate(BaseModel):
phone: str = Field(min_length=6, max_length=32) phone: str = Field(min_length=6, max_length=32)
@ -34,7 +34,7 @@ class UserResponse(BaseModel):
status: str status: str
role_codes: list[str] role_codes: list[str]
is_superuser: bool is_superuser: bool
created_at: datetime | None = None created_at: UtcDatetime | None = None
class BulkImportResult(BaseModel): class BulkImportResult(BaseModel):

View File

@ -835,16 +835,6 @@ button {
white-space: nowrap; white-space: nowrap;
} }
/* 共享文档 (ref.source_url 存在) 时, 标题变成蓝色外链, 悬停加下划线 */
.bubble-reference-item summary .ref-title-link {
color: #1a73e8;
text-decoration: none;
cursor: pointer;
}
.bubble-reference-item summary .ref-title-link:hover {
text-decoration: underline;
}
/* 展开区域内的链接地址行, 显示完整 URL, 蓝色字, 可换行 */ /* 展开区域内的链接地址行, 显示完整 URL, 蓝色字, 可换行 */
.bubble-reference-item .ref-url-line { .bubble-reference-item .ref-url-line {
display: block; display: block;

View File

@ -302,7 +302,7 @@ watch(messages, scrollToEnd, { deep: true })
@click="onBubbleClick" @click="onBubbleClick"
/> />
<div v-if="msg.references && msg.references.length" class="bubble-references"> <div v-if="msg.references && msg.references.length" class="bubble-references">
<div class="bubble-references-title">参考知识片段</div> <div class="bubble-references-title">参考来源</div>
<div class="bubble-references-body"> <div class="bubble-references-body">
<details <details
v-for="(ref, rIdx) in msg.references" v-for="(ref, rIdx) in msg.references"
@ -311,15 +311,7 @@ watch(messages, scrollToEnd, { deep: true })
> >
<summary> <summary>
<span class="ref-icon">📄</span> <span class="ref-icon">📄</span>
<a <span class="ref-title">{{ rIdx + 1 }}.{{ ref.source_title || '未知来源' }}</span>
v-if="ref.source_url"
:href="ref.source_url"
target="_blank"
rel="noopener noreferrer"
class="ref-title ref-title-link"
@click.stop
>{{ rIdx + 1 }}.{{ ref.source_title || '未知来源' }}</a>
<span v-else class="ref-title">{{ rIdx + 1 }}.{{ ref.source_title || '未知来源' }}</span>
<small v-if="ref.page_no" class="ref-page"> {{ ref.page_no }} </small> <small v-if="ref.page_no" class="ref-page"> {{ ref.page_no }} </small>
<span class="ref-arrow"></span> <span class="ref-arrow"></span>
</summary> </summary>