Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 24 additions & 21 deletions dashboard/src/components/chat/Chat.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<v-card-text class="chat-page-container">
<!-- 遮罩层 (手机端) -->
<div class="mobile-overlay" v-if="isMobile && mobileMenuOpen" @click="closeMobileSidebar"></div>

<div class="chat-layout">
<ConversationSidebar
:sessions="sessions"
Expand Down Expand Up @@ -50,7 +50,7 @@

<div class="message-list-wrapper" v-if="currSessionId && !selectedProjectId">
<MessageList :messages="messages" :isDark="isDark"
:isStreaming="isStreaming || isConvRunning"
:isStreaming="isStreaming || isConvRunning"
:isLoadingMessages="isLoadingMessages"
@openImagePreview="openImagePreview"
@replyMessage="handleReplyMessage"
Expand All @@ -59,7 +59,7 @@
ref="messageList" />
<div class="message-list-fade" :class="{ 'fade-dark': isDark }"></div>
</div>
<ProjectView
<ProjectView
v-else-if="selectedProjectId"
:project="currentProject"
:sessions="projectSessions"
Expand Down Expand Up @@ -91,7 +91,7 @@
ref="chatInputRef"
/>
</ProjectView>
<WelcomeView
<WelcomeView
v-else
:isLoading="isLoadingMessages"
>
Expand Down Expand Up @@ -301,7 +301,7 @@ const prompt = ref('');
const projectDialog = ref(false);
const editingProject = ref<Project | null>(null);
const projectSessions = ref<any[]>([]);
const currentProject = computed(() =>
const currentProject = computed(() =>
projects.value.find(p => p.project_id === selectedProjectId.value)
);

Expand Down Expand Up @@ -352,7 +352,7 @@ function openImagePreview(imageUrl: string) {

async function handleSaveTitle() {
await saveTitle();

// 如果在项目视图中,刷新项目会话列表
if (selectedProjectId.value) {
const sessions = await getProjectSessions(selectedProjectId.value);
Expand All @@ -367,7 +367,7 @@ function handleReplyMessage(msg: any, index: number) {
console.warn('Message does not have an id');
return;
}

// 获取消息内容用于显示
let messageContent = '';
if (typeof msg.content.message === 'string') {
Expand All @@ -379,12 +379,12 @@ function handleReplyMessage(msg: any, index: number) {
.map((part: any) => part.text);
messageContent = textParts.join('');
}

// 截断过长的内容
if (messageContent.length > 100) {
messageContent = messageContent.substring(0, 100) + '...';
}

replyTo.value = {
messageId,
selectedText: messageContent || '[媒体内容]'
Expand All @@ -398,12 +398,12 @@ function clearReply() {
function handleReplyWithText(replyData: any) {
// 处理选中文本的引用
const { messageId, selectedText, messageIndex } = replyData;

if (!messageId) {
console.warn('Message does not have an id');
return;
}

replyTo.value = {
messageId,
selectedText: selectedText // 保存原始的选中文本
Expand Down Expand Up @@ -449,16 +449,16 @@ async function handleSelectConversation(sessionIds: string[]) {

// 清除引用状态
clearReply();

// 开始加载消息
isLoadingMessages.value = true;

try {
await getSessionMsg(sessionIds[0]);
} finally {
isLoadingMessages.value = false;
}

nextTick(() => {
messageList.value?.scrollToBottom();
});
Expand All @@ -476,7 +476,7 @@ function handleNewChat() {
async function handleDeleteConversation(sessionId: string) {
await deleteSessionFn(sessionId);
messages.value = [];

// 如果在项目视图中,刷新项目会话列表
if (selectedProjectId.value) {
const sessions = await getProjectSessions(selectedProjectId.value);
Expand All @@ -489,11 +489,11 @@ async function handleSelectProject(projectId: string) {
const sessions = await getProjectSessions(projectId);
projectSessions.value = sessions;
messages.value = [];

// 清空当前会话ID,准备在项目中创建新对话
currSessionId.value = '';
selectedSessions.value = [];

// 手机端关闭侧边栏
if (isMobile.value) {
closeMobileSidebar();
Expand Down Expand Up @@ -542,7 +542,10 @@ async function handleStopRecording() {

async function handleFileSelect(files: FileList) {
const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
for (const file of files) {
// 将 FileList 转换为数组,避免异步处理时 FileList 被清空
const fileArray = Array.from(files);
for (let i = 0; i < fileArray.length; i++) {
const file = fileArray[i];
if (imageTypes.includes(file.type)) {
await processAndUploadImage(file);
} else {
Expand All @@ -559,10 +562,10 @@ async function handleSendMessage() {

const isCreatingNewSession = !currSessionId.value;
const currentProjectId = selectedProjectId.value; // 保存当前项目ID

if (isCreatingNewSession) {
await newSession();

// 如果在项目视图中创建新会话,立即退出项目视图
if (currentProjectId) {
selectedProjectId.value = null;
Expand Down Expand Up @@ -821,7 +824,7 @@ onBeforeUnmount(() => {
.chat-content-panel {
width: 100%;
}

.chat-page-container {
padding: 0 !important;
}
Expand Down
89 changes: 87 additions & 2 deletions dashboard/src/components/chat/ChatInput.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<template>
<div class="input-area fade-in">
<div class="input-area fade-in"
@dragover.prevent="handleDragOver"
@dragleave.prevent="handleDragLeave"
@drop.prevent="handleDrop">
<div class="input-container"
:style="{
width: '85%',
Expand All @@ -8,8 +11,18 @@
border: isDark ? 'none' : '1px solid #e0e0e0',
borderRadius: '24px',
boxShadow: isDark ? 'none' : '0px 2px 2px rgba(0, 0, 0, 0.1)',
backgroundColor: isDark ? '#2d2d2d' : 'transparent'
backgroundColor: isDark ? '#2d2d2d' : 'transparent',
position: 'relative'
}">
<!-- 拖拽上传遮罩 -->
<transition name="fade">
<div v-if="isDragging" class="drop-overlay">
<div class="drop-overlay-content">
<v-icon size="48" color="deep-purple">mdi-cloud-upload</v-icon>
<span class="drop-text">{{ tm('input.dropToUpload') }}</span>
</div>
</div>
</transition>
<!-- 引用预览区 -->
<transition name="slideReply" @after-leave="handleReplyAfterLeave">
<div class="reply-preview" v-if="props.replyTo && !isReplyClosing">
Expand Down Expand Up @@ -189,6 +202,8 @@ const imageInputRef = ref<HTMLInputElement | null>(null);
const providerModelMenuRef = ref<InstanceType<typeof ProviderModelMenu> | null>(null);
const showProviderSelector = ref(true);
const isReplyClosing = ref(false);
const isDragging = ref(false);
let dragLeaveTimeout: number | null = null;

const localPrompt = computed({
get: () => props.prompt,
Expand Down Expand Up @@ -260,6 +275,35 @@ function handlePaste(e: ClipboardEvent) {
emit('pasteImage', e);
}

function handleDragOver(e: DragEvent) {
// 清除之前的 leave timeout
if (dragLeaveTimeout) {
clearTimeout(dragLeaveTimeout);
dragLeaveTimeout = null;
}

// 检查是否有文件
if (e.dataTransfer?.types.includes('Files')) {
isDragging.value = true;
}
}

function handleDragLeave(e: DragEvent) {
// 使用 timeout 避免在子元素间移动时闪烁
dragLeaveTimeout = window.setTimeout(() => {
isDragging.value = false;
}, 50);
}

function handleDrop(e: DragEvent) {
isDragging.value = false;

const files = e.dataTransfer?.files;
if (files && files.length > 0) {
emit('fileSelect', files);
}
}

function triggerImageInput() {
imageInputRef.value?.click();
}
Expand Down Expand Up @@ -322,6 +366,47 @@ defineExpose({
flex-shrink: 0;
}

/* 拖拽上传遮罩 */
.drop-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(103, 58, 183, 0.15);
border: 2px dashed rgba(103, 58, 183, 0.5);
border-radius: 24px;
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
pointer-events: none;
}

.drop-overlay-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}

.drop-text {
font-size: 16px;
font-weight: 500;
color: #673ab7;
}

/* Fade transition for drop overlay */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.2s ease;
}

.fade-enter-from,
.fade-leave-to {
opacity: 0;
}

.reply-preview {
display: flex;
align-items: center;
Expand Down
3 changes: 2 additions & 1 deletion dashboard/src/i18n/locales/en-US/features/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"upload": "Upload File",
"voice": "Voice Input",
"recordingPrompt": "Recording, please speak...",
"chatPrompt": "Let's chat!"
"chatPrompt": "Let's chat!",
"dropToUpload": "Drop files to upload"
},
"message": {
"user": "User",
Expand Down
3 changes: 2 additions & 1 deletion dashboard/src/i18n/locales/zh-CN/features/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"upload": "上传文件",
"voice": "语音输入",
"recordingPrompt": "录音中,请说话...",
"chatPrompt": "聊天吧!"
"chatPrompt": "聊天吧!",
"dropToUpload": "松开鼠标上传文件"
},
"message": {
"user": "用户",
Expand Down