This commit is contained in:
ViperEkura 2026-04-29 16:35:37 +08:00
parent f948dfc45f
commit b07e3411e3
7 changed files with 88 additions and 34 deletions

View File

@ -1,8 +1,13 @@
<script setup>
import { useAuth } from './utils/useAuth.js'
import AppHeader from './components/AppHeader.vue'
import { useRouter } from 'vue-router'
const { isLoggedIn } = useAuth()
const router = useRouter()
// router api.js 使
window.__VUE_ROUTER__ = router
</script>
<template>

View File

@ -157,12 +157,11 @@ const regenerateIcon = `<svg viewBox="0 0 24 24" width="14" height="14" fill="no
display: flex;
align-items: center;
gap: 10px;
padding: 8px 0 0;
margin-top: 8px;
padding-top: 10px;
font-size: 12px;
color: var(--text-tertiary);
border-top: 1px solid transparent;
margin-top: 8px;
padding-top: 10px;
}

View File

@ -194,8 +194,9 @@ function handleBack() {
<div v-if="sidebarTab === 'roomAgents' && room" class="sidebar-tab-content">
<div class="sidebar-header sidebar-header-row">
<button class="btn-back" @click="handleBack">
<svg width="18" height="18">
<use href="#arrow-left-icon"/>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="19" y1="12" x2="5" y2="12"></line>
<polyline points="12 19 5 12 12 5"></polyline>
</svg>
</button>
<span class="sidebar-title">{{ room.title }}</span>

View File

@ -30,8 +30,13 @@ api.interceptors.response.use(
if (error.response?.status === 401) {
localStorage.removeItem('access_token')
localStorage.removeItem('user')
// 使用 Vue Router 跳转,避免 SPA 路由丢失
if (window.__VUE_ROUTER__) {
window.__VUE_ROUTER__.push('/auth')
} else {
window.location.href = '/auth'
}
}
return Promise.reject(error.response?.data || error.message)
}
)
@ -106,6 +111,7 @@ export const chatRoomsAPI = {
delete: (id) => api.delete(`/chat-rooms/${id}`),
getMessages: (id) => api.get(`/chat-rooms/${id}/messages`),
start: (id) => `/api/chat-rooms/${id}/start`,
// 注意: start 返回路径字符串,由调用方使用 fetch 处理 SSE 流
stop: (id) => api.post(`/chat-rooms/${id}/stop`),
reset: (id) => api.post(`/chat-rooms/${id}/reset`),
addAgent: (roomId, data) => api.post(`/chat-rooms/${roomId}/agents`, data),

View File

@ -108,24 +108,24 @@ class ParallelStreamManager {
break
case 'message_start':
store.startAgentStream(roomId, data.agent_id || data.agentId, data)
store.startAgentStream(roomId, data.agent_id, data)
break
case 'message_chunk':
store.updateAgentContent(roomId, data.agent_id || data.agentId, {
store.updateAgentContent(roomId, data.agent_id, {
content: data.content || '',
progress: data.progress || 0
})
break
case 'message_end':
store.completeAgentStream(roomId, data.agent_id || data.agentId, data)
store.completeAgentStream(roomId, data.agent_id, data)
break
case 'agent_error':
store.errorAgentStream(roomId, data.agent_id || data.agentId, {
store.errorAgentStream(roomId, data.agent_id, {
message: data.error,
agentName: data.agent_name || data.agentName
agentName: data.agent_name
})
break

View File

@ -102,8 +102,8 @@ export function useConversations() {
convMessages.value = res.data?.messages || []
// 加载完成后强制滚动到底部(初始加载总是显示最新消息)
nextTick(() => {
if (typeof onInitialScroll === 'function') {
onInitialScroll()
if (typeof initialScrollCallback === 'function') {
initialScrollCallback()
}
})
}
@ -278,10 +278,10 @@ export function useConversations() {
}
// 初始滚动回调(由外部设置)
let onInitialScroll = null
let initialScrollCallback = null
const setOnInitialScroll = (callback) => {
onInitialScroll = callback
initialScrollCallback = callback
}
return {

View File

@ -94,14 +94,15 @@
</div>
</div>
<div class="chat-input-area">
<input
<div class="input-wrapper">
<textarea
v-model="newMessage"
@keyup.enter="handleSend"
type="text"
placeholder="输入消息..."
@keydown.enter.exact.prevent="handleSend"
placeholder="输入消息... (Shift+Enter 换行)"
class="chat-input"
:disabled="sending"
/>
rows="3"
></textarea>
<button @click="handleSend" class="btn-send" :disabled="sending || !newMessage.trim()" title="发送">
<svg v-if="!sending" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="22" y1="2" x2="11" y2="13"></line>
@ -111,6 +112,7 @@
</button>
</div>
</div>
</div>
</main>
</div>
@ -424,10 +426,51 @@ onUnmounted(() => {
.loading-messages .spinner-small { margin-bottom: 0.5rem; }
/* 聊天输入区 */
.chat-input-area { padding: 1rem; border-top: 1px solid var(--border-light); display: flex; gap: 0.75rem; }
.chat-input { flex: 1; padding: 0.65rem 0.9rem; border: 1px solid var(--border-input); border-radius: 8px; background: var(--bg-input); color: var(--text-primary); font-size: 0.9rem; }
.chat-input:focus { outline: none; border-color: var(--accent-primary); }
.btn-send { width: 40px; height: 40px; background: var(--accent-primary); color: white; border: none; border-radius: 8px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.15s ease; }
.chat-input-area { padding: 1rem; border-top: 1px solid var(--border-light); }
.input-wrapper {
position: relative;
display: flex;
background: var(--bg-input);
border: 1px solid var(--border-input);
border-radius: 10px;
transition: border-color 0.2s ease;
}
.input-wrapper:focus-within { border-color: var(--accent-primary); }
.chat-input {
flex: 1;
padding: 0.75rem 3rem 0.75rem 1rem;
border: none;
background: transparent;
color: var(--text-primary);
font-size: 0.9rem;
font-family: inherit;
resize: none;
min-height: 72px;
max-height: 200px;
overflow-y: auto;
line-height: 1.5;
white-space: pre-wrap;
word-break: break-word;
box-sizing: border-box;
}
.chat-input:focus { outline: none; }
.chat-input:disabled { opacity: 0.6; }
.btn-send {
position: absolute;
right: 8px;
bottom: 8px;
width: 36px;
height: 36px;
background: var(--accent-primary);
color: white;
border: none;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.15s ease;
}
.btn-send:hover:not(:disabled) { background: var(--accent-primary-hover); transform: translateY(-1px); box-shadow: 0 2px 8px rgba(37, 99, 235, 0.3); }
.btn-send:disabled { opacity: 0.5; cursor: not-allowed; }