新增项目库向量化及问答模块
parent
194928e0e1
commit
ae09cf358e
|
|
@ -268,7 +268,9 @@ def _compact_cited_refs(answer: str, retrieved_docs):
|
||||||
"citation_id": new_id,
|
"citation_id": new_id,
|
||||||
"file_path": file_path,
|
"file_path": file_path,
|
||||||
"anchor_text": doc.get("anchor_text") or "",
|
"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
|
old_to_new[old_id] = new_id
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -410,7 +410,8 @@
|
||||||
vertical-align: super;
|
vertical-align: super;
|
||||||
color: #1677ff;
|
color: #1677ff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin: 0 1px;
|
margin: 0 3px;
|
||||||
|
padding: 0 1px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -578,7 +579,7 @@
|
||||||
.chat-citation-preview {
|
.chat-citation-preview {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 10px;
|
||||||
max-width: 460px;
|
max-width: 460px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -592,7 +593,7 @@
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
line-height: 1.65;
|
line-height: 1.65;
|
||||||
padding: 10px;
|
padding: 12px 14px;
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: color-mix(in srgb, var(--border-color) 10%, transparent);
|
background: color-mix(in srgb, var(--border-color) 10%, transparent);
|
||||||
|
|
@ -646,13 +647,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-shell .chat-composer-shell {
|
.chat-shell .chat-composer-shell {
|
||||||
margin: 0 24px 24px;
|
margin: 0 24px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-new-composer {
|
.chat-new-composer {
|
||||||
min-height: 150px;
|
margin-bottom: 12px;
|
||||||
padding-top: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-composer-input {
|
.chat-composer-input {
|
||||||
|
|
@ -697,18 +696,18 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-composer-plus {
|
.chat-composer-plus {
|
||||||
width: 44px;
|
width: 32px;
|
||||||
height: 44px;
|
height: 32px;
|
||||||
min-width: 44px;
|
min-width: 32px;
|
||||||
border: 1px solid #f0f0f0;
|
border: 1px solid #f0f0f0;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #fff;
|
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);
|
color: var(--text-color-secondary);
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 24px;
|
font-size: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
@ -778,21 +777,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-send-button {
|
.chat-send-button {
|
||||||
width: 40px !important;
|
width: 32px !important;
|
||||||
height: 40px !important;
|
height: 32px !important;
|
||||||
min-width: 40px !important;
|
min-width: 32px !important;
|
||||||
border: 1px solid #bebebe !important;
|
border: 1px solid #bebebe !important;
|
||||||
background: #d1d1d1 !important;
|
background: #d1d1d1 !important;
|
||||||
color: #fff !important;
|
color: #fff !important;
|
||||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.28);
|
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;
|
line-height: 1 !important;
|
||||||
transform: scale(1.08);
|
|
||||||
transform-origin: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-send-button .anticon {
|
.chat-send-button .anticon {
|
||||||
font-size: 22px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-send-button:not(:disabled):hover {
|
.chat-send-button:not(:disabled):hover {
|
||||||
|
|
@ -885,10 +882,9 @@ body.dark .chat-search-highlight {
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-send-button {
|
.chat-send-button {
|
||||||
width: 48px !important;
|
width: 40px !important;
|
||||||
height: 48px !important;
|
height: 40px !important;
|
||||||
min-width: 48px !important;
|
min-width: 40px !important;
|
||||||
font-size: 24px !important;
|
font-size: 18px !important;
|
||||||
transform: scale(1.08);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ import 'highlight.js/styles/github.css'
|
||||||
import { createChatSession, deleteChatMessage, deleteChatSession, getChatMessages, getChatSessions, searchChatMessages, sendChatMessageStream, updateChatSessionTitle } from '@/api/chat'
|
import { createChatSession, deleteChatMessage, deleteChatSession, getChatMessages, getChatSessions, searchChatMessages, sendChatMessageStream, updateChatSessionTitle } from '@/api/chat'
|
||||||
import { getMyProjects } from '@/api/project'
|
import { getMyProjects } from '@/api/project'
|
||||||
import { getLLMModelConfigs } from '@/api/llmModelConfigs'
|
import { getLLMModelConfigs } from '@/api/llmModelConfigs'
|
||||||
|
import useUserStore from '@/stores/userStore'
|
||||||
import './Chat.css'
|
import './Chat.css'
|
||||||
|
|
||||||
const { TextArea } = Input
|
const { TextArea } = Input
|
||||||
|
|
@ -208,6 +209,7 @@ function MessageActions({ content, onCopy, onDelete }) {
|
||||||
function Chat() {
|
function Chat() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const { user: currentUser } = useUserStore()
|
||||||
const [searchParams, setSearchParams] = useSearchParams()
|
const [searchParams, setSearchParams] = useSearchParams()
|
||||||
const [sessions, setSessions] = useState([])
|
const [sessions, setSessions] = useState([])
|
||||||
const [projects, setProjects] = useState([])
|
const [projects, setProjects] = useState([])
|
||||||
|
|
@ -232,6 +234,7 @@ function Chat() {
|
||||||
const [renameSession, setRenameSession] = useState(null)
|
const [renameSession, setRenameSession] = useState(null)
|
||||||
const [renameTitle, setRenameTitle] = useState('')
|
const [renameTitle, setRenameTitle] = useState('')
|
||||||
const messageRefs = useRef(new Map())
|
const messageRefs = useRef(new Map())
|
||||||
|
const messagesEndRef = useRef(null)
|
||||||
const skipNextOpenSessionRef = useRef(null)
|
const skipNextOpenSessionRef = useRef(null)
|
||||||
const searchRequestIdRef = useRef(0)
|
const searchRequestIdRef = useRef(0)
|
||||||
const newMode = location.pathname === '/chat/new'
|
const newMode = location.pathname === '/chat/new'
|
||||||
|
|
@ -252,6 +255,19 @@ function Chat() {
|
||||||
const canCreateSession = Boolean(newQuestion.trim() && currentProject && currentModel)
|
const canCreateSession = Boolean(newQuestion.trim() && currentProject && currentModel)
|
||||||
const canSendMessage = Boolean(inputValue.trim() && currentSession)
|
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 groupedSessions = useMemo(() => {
|
||||||
const groups = []
|
const groups = []
|
||||||
const groupMap = new Map()
|
const groupMap = new Map()
|
||||||
|
|
@ -353,6 +369,11 @@ function Chat() {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [sessionId, sessions.length, newMode])
|
}, [sessionId, sessions.length, newMode])
|
||||||
|
|
||||||
|
// 新会话打开或消息生成/更新时,自动滚动到对话底部
|
||||||
|
useEffect(() => {
|
||||||
|
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth', block: 'end' })
|
||||||
|
}, [messages, currentSession])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const target = searchParams.get('message_id')
|
const target = searchParams.get('message_id')
|
||||||
if (!target) return
|
if (!target) return
|
||||||
|
|
@ -874,11 +895,16 @@ function Chat() {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
|
<div ref={messagesEndRef} />
|
||||||
</div>
|
</div>
|
||||||
<div className="chat-composer-shell">
|
<div className="chat-composer-shell">
|
||||||
<TextArea
|
<TextArea
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue