新增项目库向量化及问答模块

main
mula.liu 2026-08-04 09:31:48 +08:00
parent 194928e0e1
commit ae09cf358e
3 changed files with 50 additions and 26 deletions

View File

@ -268,7 +268,9 @@ def _compact_cited_refs(answer: str, retrieved_docs):
"citation_id": new_id,
"file_path": file_path,
"anchor_text": doc.get("anchor_text") or "",
"excerpt": doc.get("excerpt") or doc.get("content") or doc.get("anchor_text") or "",
# 优先使用命中文档的上下文片段(命中点前后扩展),避免分块边界
# 的短文本(如文档末尾残余块)导致引用预览内容过少
"excerpt": doc.get("content") or doc.get("excerpt") or doc.get("anchor_text") or "",
})
old_to_new[old_id] = new_id

View File

@ -410,7 +410,8 @@
vertical-align: super;
color: #1677ff;
font-weight: 600;
margin: 0 1px;
margin: 0 3px;
padding: 0 1px;
cursor: pointer;
}
@ -578,7 +579,7 @@
.chat-citation-preview {
display: flex;
flex-direction: column;
gap: 8px;
gap: 10px;
max-width: 460px;
}
@ -592,7 +593,7 @@
overflow: auto;
word-break: break-word;
line-height: 1.65;
padding: 10px;
padding: 12px 14px;
border: 1px solid var(--border-color);
border-radius: 8px;
background: color-mix(in srgb, var(--border-color) 10%, transparent);
@ -646,13 +647,11 @@
}
.chat-shell .chat-composer-shell {
margin: 0 24px 24px;
margin: 0 24px 12px;
}
.chat-new-composer {
min-height: 150px;
padding-top: 10px;
padding-bottom: 10px;
margin-bottom: 12px;
}
.chat-composer-input {
@ -697,18 +696,18 @@
}
.chat-composer-plus {
width: 44px;
height: 44px;
min-width: 44px;
width: 32px;
height: 32px;
min-width: 32px;
border: 1px solid #f0f0f0;
border-radius: 50%;
background: #fff;
box-shadow: 0 6px 18px rgba(15, 23, 42, 0.08);
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.06);
color: var(--text-color-secondary);
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-size: 16px;
cursor: pointer;
flex-shrink: 0;
}
@ -778,21 +777,19 @@
}
.chat-send-button {
width: 40px !important;
height: 40px !important;
min-width: 40px !important;
width: 32px !important;
height: 32px !important;
min-width: 32px !important;
border: 1px solid #bebebe !important;
background: #d1d1d1 !important;
color: #fff !important;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.28);
font-size: 24px !important;
font-size: 14px !important;
line-height: 1 !important;
transform: scale(1.08);
transform-origin: center;
}
.chat-send-button .anticon {
font-size: 22px;
font-size: 15px;
}
.chat-send-button:not(:disabled):hover {
@ -885,10 +882,9 @@ body.dark .chat-search-highlight {
}
.chat-send-button {
width: 48px !important;
height: 48px !important;
min-width: 48px !important;
font-size: 24px !important;
transform: scale(1.08);
width: 40px !important;
height: 40px !important;
min-width: 40px !important;
font-size: 18px !important;
}
}

View File

@ -38,6 +38,7 @@ import 'highlight.js/styles/github.css'
import { createChatSession, deleteChatMessage, deleteChatSession, getChatMessages, getChatSessions, searchChatMessages, sendChatMessageStream, updateChatSessionTitle } from '@/api/chat'
import { getMyProjects } from '@/api/project'
import { getLLMModelConfigs } from '@/api/llmModelConfigs'
import useUserStore from '@/stores/userStore'
import './Chat.css'
const { TextArea } = Input
@ -208,6 +209,7 @@ function MessageActions({ content, onCopy, onDelete }) {
function Chat() {
const navigate = useNavigate()
const location = useLocation()
const { user: currentUser } = useUserStore()
const [searchParams, setSearchParams] = useSearchParams()
const [sessions, setSessions] = useState([])
const [projects, setProjects] = useState([])
@ -232,6 +234,7 @@ function Chat() {
const [renameSession, setRenameSession] = useState(null)
const [renameTitle, setRenameTitle] = useState('')
const messageRefs = useRef(new Map())
const messagesEndRef = useRef(null)
const skipNextOpenSessionRef = useRef(null)
const searchRequestIdRef = useRef(0)
const newMode = location.pathname === '/chat/new'
@ -252,6 +255,19 @@ function Chat() {
const canCreateSession = Boolean(newQuestion.trim() && currentProject && currentModel)
const canSendMessage = Boolean(inputValue.trim() && currentSession)
// /avatar API
const userAvatarUrl = useMemo(() => {
const avatar = currentUser?.avatar
if (!avatar) return null
if (avatar.startsWith('http')) return avatar
const parts = avatar.split('/')
if (parts.length >= 3) {
return `/api/v1/auth/avatar/${parts[0]}/${parts[2]}`
}
return null
}, [currentUser])
const userDisplayName = currentUser?.nickname || currentUser?.username
const groupedSessions = useMemo(() => {
const groups = []
const groupMap = new Map()
@ -353,6 +369,11 @@ function Chat() {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [sessionId, sessions.length, newMode])
// /
useEffect(() => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' })
}, [messages, currentSession])
useEffect(() => {
const target = searchParams.get('message_id')
if (!target) return
@ -874,11 +895,16 @@ function Chat() {
/>
)}
</div>
{isUser && <Avatar className="chat-avatar user" icon={<UserOutlined />} />}
{isUser && (
<Avatar className="chat-avatar user" src={userAvatarUrl} style={{ backgroundColor: '#1677ff' }}>
{userDisplayName?.[0]?.toUpperCase() || 'U'}
</Avatar>
)}
</div>
)
})
)}
<div ref={messagesEndRef} />
</div>
<div className="chat-composer-shell">
<TextArea