fix:问答时间和样式修正
This commit is contained in:
parent
f6aa6d9a97
commit
8fb76a9cb6
|
|
@ -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")
|
||||||
|
]
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue