update
This commit is contained in:
parent
4faacb2a69
commit
2b7e900cd5
@ -3,7 +3,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
image: tradus-web:1.3.35
|
image: tradus-web:1.3.36
|
||||||
container_name: tradus-web
|
container_name: tradus-web
|
||||||
ports:
|
ports:
|
||||||
- '6000:80'
|
- '6000:80'
|
||||||
|
|||||||
@ -59,6 +59,10 @@ const conversationToRename = ref<Conversation | null>(null)
|
|||||||
const newConversationName = ref('')
|
const newConversationName = ref('')
|
||||||
const renameInputRef = ref<HTMLInputElement | null>(null)
|
const renameInputRef = ref<HTMLInputElement | null>(null)
|
||||||
|
|
||||||
|
// 流式输出控制
|
||||||
|
const currentTaskId = ref<string>('')
|
||||||
|
const streamingAbortController = ref<AbortController | null>(null)
|
||||||
|
|
||||||
// 根据环境选择API基础URL
|
// 根据环境选择API基础URL
|
||||||
const apiBaseUrl =
|
const apiBaseUrl =
|
||||||
import.meta.env.MODE === 'development' ? 'http://127.0.0.1:8000' : 'https://api.ibtc.work'
|
import.meta.env.MODE === 'development' ? 'http://127.0.0.1:8000' : 'https://api.ibtc.work'
|
||||||
@ -201,6 +205,9 @@ const sendMessage = async () => {
|
|||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
await scrollToBottom()
|
await scrollToBottom()
|
||||||
|
|
||||||
|
// 创建AbortController用于取消请求
|
||||||
|
streamingAbortController.value = new AbortController()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await http.post(`${apiBaseUrl}/analysis/chat-messages`, {
|
const response = await http.post(`${apiBaseUrl}/analysis/chat-messages`, {
|
||||||
message: message,
|
message: message,
|
||||||
@ -233,6 +240,11 @@ const sendMessage = async () => {
|
|||||||
let buffer = ''
|
let buffer = ''
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
// 检查是否被中止
|
||||||
|
if (streamingAbortController.value?.signal.aborted) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
const { done, value } = await reader.read()
|
const { done, value } = await reader.read()
|
||||||
if (done) break
|
if (done) break
|
||||||
|
|
||||||
@ -253,6 +265,11 @@ const sendMessage = async () => {
|
|||||||
|
|
||||||
switch (data.event) {
|
switch (data.event) {
|
||||||
case 'workflow_started':
|
case 'workflow_started':
|
||||||
|
// 提取task_id
|
||||||
|
if (data.task_id) {
|
||||||
|
currentTaskId.value = data.task_id
|
||||||
|
console.log('获取到task_id:', data.task_id)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
case 'node_started':
|
case 'node_started':
|
||||||
break
|
break
|
||||||
@ -292,12 +309,16 @@ const sendMessage = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
assistantMessage.isStreaming = false
|
assistantMessage.isStreaming = false
|
||||||
|
// 清理task_id
|
||||||
|
currentTaskId.value = ''
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'error':
|
case 'error':
|
||||||
const errorMessage = data.error || '未知错误'
|
const errorMessage = data.error || '未知错误'
|
||||||
assistantMessage.content = `错误: ${errorMessage}`
|
assistantMessage.content = `错误: ${errorMessage}`
|
||||||
assistantMessage.isStreaming = false
|
assistantMessage.isStreaming = false
|
||||||
|
// 清理task_id
|
||||||
|
currentTaskId.value = ''
|
||||||
await scrollToBottom()
|
await scrollToBottom()
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -309,6 +330,7 @@ const sendMessage = async () => {
|
|||||||
console.error('解析响应数据出错:', error, '原始数据:', line)
|
console.error('解析响应数据出错:', error, '原始数据:', line)
|
||||||
assistantMessage.content = '错误: 解析响应数据时出错,请稍后重试'
|
assistantMessage.content = '错误: 解析响应数据时出错,请稍后重试'
|
||||||
assistantMessage.isStreaming = false
|
assistantMessage.isStreaming = false
|
||||||
|
currentTaskId.value = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -323,6 +345,8 @@ const sendMessage = async () => {
|
|||||||
assistantMessage.isStreaming = false
|
assistantMessage.isStreaming = false
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
|
currentTaskId.value = ''
|
||||||
|
streamingAbortController.value = null
|
||||||
await scrollToBottom()
|
await scrollToBottom()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -504,6 +528,40 @@ const confirmDelete = async () => {
|
|||||||
|
|
||||||
cancelDelete()
|
cancelDelete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 停止流式输出
|
||||||
|
const stopStreaming = async () => {
|
||||||
|
if (!currentTaskId.value) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await http.post(`${apiBaseUrl}/analysis/stop_streaming`, {
|
||||||
|
task_id: currentTaskId.value,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
console.log('停止流式输出成功')
|
||||||
|
} else {
|
||||||
|
console.error('停止流式输出失败')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('停止流式输出失败:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 无论是否成功,都清理状态
|
||||||
|
currentTaskId.value = ''
|
||||||
|
if (streamingAbortController.value) {
|
||||||
|
streamingAbortController.value.abort()
|
||||||
|
streamingAbortController.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停止当前流式消息
|
||||||
|
const lastMessage = messages.value[messages.value.length - 1]
|
||||||
|
if (lastMessage && lastMessage.isStreaming) {
|
||||||
|
lastMessage.isStreaming = false
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -730,21 +788,20 @@ const confirmDelete = async () => {
|
|||||||
rows="1"
|
rows="1"
|
||||||
></textarea>
|
></textarea>
|
||||||
<button
|
<button
|
||||||
|
v-if="!isLoading"
|
||||||
class="send-button"
|
class="send-button"
|
||||||
@click="sendMessage"
|
@click="sendMessage"
|
||||||
:disabled="isLoading || !messageInput.trim()"
|
:disabled="!messageInput.trim()"
|
||||||
>
|
>
|
||||||
<svg
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
v-if="!isLoading"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="2"
|
|
||||||
>
|
|
||||||
<line x1="22" y1="2" x2="11" y2="13"></line>
|
<line x1="22" y1="2" x2="11" y2="13"></line>
|
||||||
<polygon points="22,2 15,22 11,13 2,9"></polygon>
|
<polygon points="22,2 15,22 11,13 2,9"></polygon>
|
||||||
</svg>
|
</svg>
|
||||||
<div v-else class="button-loader"></div>
|
</button>
|
||||||
|
<button v-else class="stop-button" @click="stopStreaming" title="停止生成">
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
||||||
|
<rect x="6" y="6" width="12" height="12" rx="2" ry="2"></rect>
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1414,6 +1471,36 @@ const confirmDelete = async () => {
|
|||||||
height: 20px;
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stop-button {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background-color: rgba(239, 68, 68, 0.1);
|
||||||
|
color: #ef4444;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop-button:hover:not(:disabled) {
|
||||||
|
background-color: rgba(239, 68, 68, 0.2);
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop-button:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop-button svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.button-loader {
|
.button-loader {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
@ -1470,39 +1557,6 @@ const confirmDelete = async () => {
|
|||||||
.welcome-content {
|
.welcome-content {
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.chat-agent-view {
|
|
||||||
height: 100vh;
|
|
||||||
height: 100dvh;
|
|
||||||
min-height: -webkit-fill-available;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-messages {
|
|
||||||
padding: 1.5rem 0.75rem 80px 0.75rem; /* 为固定输入框留出空间 */
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-header {
|
|
||||||
padding: 0.6rem 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-title-container {
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-title {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-icon {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.welcome-content {
|
|
||||||
padding: 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-grid {
|
.example-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
@ -1568,6 +1622,18 @@ const confirmDelete = async () => {
|
|||||||
height: 16px;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stop-button {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stop-button svg {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
@ -1613,171 +1679,37 @@ const confirmDelete = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
@media (max-width: 480px) {
|
||||||
position: fixed;
|
.chat-agent-view {
|
||||||
top: 0;
|
height: 100vh;
|
||||||
right: -350px;
|
height: 100dvh;
|
||||||
width: 350px;
|
min-height: -webkit-fill-available;
|
||||||
height: 100vh;
|
}
|
||||||
background-color: var(--color-bg-secondary);
|
|
||||||
border-left: 1px solid var(--color-border);
|
|
||||||
z-index: 120;
|
|
||||||
transition: right 0.3s ease;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar.open {
|
.chat-messages {
|
||||||
right: 0;
|
padding: 1.5rem 0.75rem 80px 0.75rem; /* 为固定输入框留出空间 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-header {
|
.conversation-header {
|
||||||
display: flex;
|
padding: 0.6rem 0.75rem;
|
||||||
justify-content: space-between;
|
}
|
||||||
align-items: center;
|
|
||||||
padding: 1rem 1.5rem;
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
background-color: var(--color-bg-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-header h3 {
|
.conversation-title-container {
|
||||||
margin: 0;
|
gap: 0.25rem;
|
||||||
font-size: 1.1rem;
|
}
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-sidebar-btn {
|
.conversation-title {
|
||||||
width: 32px;
|
font-size: 1rem;
|
||||||
height: 32px;
|
}
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
background-color: transparent;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-sidebar-btn:hover {
|
.dropdown-icon {
|
||||||
background-color: var(--color-bg-secondary);
|
width: 16px;
|
||||||
color: var(--color-text-primary);
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-sidebar-btn svg {
|
.welcome-content {
|
||||||
width: 16px;
|
padding: 0 1rem;
|
||||||
height: 16px;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-content {
|
|
||||||
flex: 1;
|
|
||||||
padding: 1rem;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new-conversation-item {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.75rem;
|
|
||||||
padding: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
border: 1px dashed var(--color-border);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
background-color: transparent;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new-conversation-item:hover:not(:disabled) {
|
|
||||||
border-color: var(--color-accent);
|
|
||||||
background-color: rgba(51, 85, 255, 0.05);
|
|
||||||
color: var(--color-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.new-conversation-item:disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new-conversation-item svg {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-item {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
background-color: var(--color-bg-primary);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-item:hover:not(:disabled) {
|
|
||||||
border-color: var(--color-border);
|
|
||||||
background-color: var(--color-bg-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-item.active {
|
|
||||||
border-color: var(--color-accent);
|
|
||||||
background-color: rgba(51, 85, 255, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-item:disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-info {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-name {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--color-text-primary);
|
|
||||||
display: block;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.conversation-item.active .conversation-name {
|
|
||||||
color: var(--color-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-conversations,
|
|
||||||
.loading-history {
|
|
||||||
text-align: center;
|
|
||||||
padding: 1rem;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: rgba(0, 0, 0, 0.3);
|
|
||||||
z-index: 110;
|
|
||||||
backdrop-filter: blur(2px);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user