|
|
@@ -27,6 +27,7 @@
|
|
|
@switch-task="switchTask"
|
|
|
@create-task="createNewTask"
|
|
|
@delete-task="handleDeleteTask"
|
|
|
+ @pin-task="handlePinTask"
|
|
|
/>
|
|
|
</div>
|
|
|
|
|
|
@@ -45,7 +46,15 @@
|
|
|
<div class="nav-icon add-icon"></div>
|
|
|
</div>
|
|
|
</template>
|
|
|
- <span v-if="currentTask?.sessionId" class="chat-header-title">{{ currentTask.name }}</span>
|
|
|
+ <div v-if="currentTask?.sessionId" class="chat-header-title-wrapper">
|
|
|
+ <template v-if="editingTitle">
|
|
|
+ <input v-model="editTitleValue" class="title-edit-input" @keyup.enter="saveTitle" @keyup.escape="cancelEditTitle" @blur="saveTitle" />
|
|
|
+ </template>
|
|
|
+ <template v-else>
|
|
|
+ <div class="title-edit-btn" @click="startEditTitle" title="编辑标题"></div>
|
|
|
+ <span class="chat-header-title">{{ currentTask.name }}</span>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
<div v-if="currentWordUrl" class="upload-bg" @click="downloadWordReport">
|
|
|
<div class="download-icon"></div>
|
|
|
</div>
|
|
|
@@ -55,18 +64,35 @@
|
|
|
<FileListPanel :visible="showFileList" :files="getCurrentTaskFiles()" @close="showFileList = false" @file-click="openFilePreview" />
|
|
|
|
|
|
<!-- 消息列表 -->
|
|
|
- <ChatMessages ref="chatMessagesRef" :messages="messages" @file-preview="openFilePreview" @download-word="downloadWordFile" />
|
|
|
+ <ChatMessages
|
|
|
+ ref="chatMessagesRef"
|
|
|
+ :messages="messages"
|
|
|
+ @file-preview="openFilePreview"
|
|
|
+ @download-word="downloadWordFile"
|
|
|
+ @approve="handleApprove"
|
|
|
+ @reject="handleReject"
|
|
|
+ />
|
|
|
|
|
|
<!-- 输入区域 -->
|
|
|
<ChatInputArea
|
|
|
ref="chatInputRef"
|
|
|
v-model="inputMessage"
|
|
|
:pendingFile="pendingFile"
|
|
|
- :loading="loading"
|
|
|
+ :loading="loading || !!pendingApprovalSessionId"
|
|
|
+ :contextUsed="contextUsed"
|
|
|
+ :contextMax="contextMax"
|
|
|
+ :contextPercent="contextPercent"
|
|
|
+ :contextBreakdown="contextBreakdown"
|
|
|
+ :initialThinkLevel="thinkLevel"
|
|
|
+ :initialEditMode="editMode"
|
|
|
+ :taskList="taskList"
|
|
|
@send="handleSendMessage"
|
|
|
@file-upload="triggerFileUpload"
|
|
|
@file-change="handleFileUpload"
|
|
|
@remove-pending-file="removePendingFile"
|
|
|
+ @think-level-change="handleThinkLevelChange"
|
|
|
+ @edit-mode-change="handleEditModeChange"
|
|
|
+ @insert-session="handleInsertSession"
|
|
|
/>
|
|
|
</div>
|
|
|
|
|
|
@@ -81,9 +107,22 @@
|
|
|
<script setup lang="ts">
|
|
|
import { ref, nextTick, watch, computed, onMounted, onBeforeUnmount } from 'vue';
|
|
|
import dayjs from 'dayjs';
|
|
|
- import { unifiedStream, reviewPdfStream, getHistoryList, getDetail, deleteSession } from '../api';
|
|
|
+ import {
|
|
|
+ unifiedStream,
|
|
|
+ reviewPdfStream,
|
|
|
+ chatResumeStream,
|
|
|
+ getHistoryList,
|
|
|
+ getDetail,
|
|
|
+ deleteSession,
|
|
|
+ pinSession,
|
|
|
+ editSessionTitle,
|
|
|
+ getContext,
|
|
|
+ switchThinking,
|
|
|
+ getModelMsg,
|
|
|
+ getSessionMode,
|
|
|
+ putSessionMode,
|
|
|
+ } from '../api';
|
|
|
import { message, Modal } from 'ant-design-vue';
|
|
|
- import { isPdfFile } from './chatModal/utils';
|
|
|
import TaskListPanel from './chatModal/TaskListPanel.vue';
|
|
|
import FileListPanel from './chatModal/FileListPanel.vue';
|
|
|
import ChatMessages from './chatModal/ChatMessages.vue';
|
|
|
@@ -110,13 +149,14 @@
|
|
|
const res = await getHistoryList({});
|
|
|
const sessions = res?.sessions || res?.data || [];
|
|
|
if (Array.isArray(sessions)) {
|
|
|
- const apiTasks: Task[] = sessions.map((item: { session_id: string; title: string; updated_at: string }) => ({
|
|
|
+ const apiTasks: Task[] = sessions.map((item: { session_id: string; title: string; updated_at: string; sort_order?: number }) => ({
|
|
|
id: item.session_id,
|
|
|
name: item.title || '未命名会话',
|
|
|
sessionId: item.session_id,
|
|
|
messages: [],
|
|
|
attachedFiles: [],
|
|
|
updatedAt: item.updated_at,
|
|
|
+ sortOrder: item.sort_order,
|
|
|
}));
|
|
|
|
|
|
const mergedTasks = apiTasks.map((apiTask) => {
|
|
|
@@ -153,11 +193,54 @@
|
|
|
|
|
|
const messages = ref<Message[]>([]);
|
|
|
|
|
|
+ const contextUsed = ref(0);
|
|
|
+ const contextMax = ref(1000000);
|
|
|
+ const contextPercent = ref(0);
|
|
|
+ const thinkLevel = ref('off');
|
|
|
+ const editMode = ref('full');
|
|
|
+ const pendingApprovalSessionId = ref('');
|
|
|
+ const pendingApprovalThreadId = ref('');
|
|
|
+ const contextBreakdown = ref({ messages: 0, mcp: 0, skills: 0, system_prompt: 0, other: 0 });
|
|
|
+
|
|
|
const panelCollapsed = ref(false);
|
|
|
const togglePanel = () => {
|
|
|
panelCollapsed.value = !panelCollapsed.value;
|
|
|
};
|
|
|
|
|
|
+ const editingTitle = ref(false);
|
|
|
+ const editTitleValue = ref('');
|
|
|
+
|
|
|
+ const startEditTitle = () => {
|
|
|
+ if (!currentTask.value?.sessionId) return;
|
|
|
+ editTitleValue.value = currentTask.value.name;
|
|
|
+ editingTitle.value = true;
|
|
|
+ nextTick(() => {
|
|
|
+ const input = document.querySelector('.title-edit-input') as HTMLInputElement;
|
|
|
+ input?.focus();
|
|
|
+ input?.select();
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ const saveTitle = async () => {
|
|
|
+ const newTitle = editTitleValue.value.trim();
|
|
|
+ if (!currentTask.value?.sessionId || !newTitle || newTitle === currentTask.value.name) {
|
|
|
+ editingTitle.value = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ await editSessionTitle(currentTask.value.sessionId, newTitle);
|
|
|
+ currentTask.value.name = newTitle;
|
|
|
+ message.success('标题修改成功');
|
|
|
+ } catch {
|
|
|
+ message.error('标题修改失败');
|
|
|
+ }
|
|
|
+ editingTitle.value = false;
|
|
|
+ };
|
|
|
+
|
|
|
+ const cancelEditTitle = () => {
|
|
|
+ editingTitle.value = false;
|
|
|
+ };
|
|
|
+
|
|
|
const isFullscreen = ref(false);
|
|
|
const toggleFullscreen = () => {
|
|
|
isFullscreen.value = !isFullscreen.value;
|
|
|
@@ -251,6 +334,16 @@
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ if (newTask.sessionId) {
|
|
|
+ fetchContext(newTask.sessionId);
|
|
|
+ fetchSessionMode(newTask.sessionId);
|
|
|
+ } else {
|
|
|
+ contextUsed.value = 0;
|
|
|
+ contextPercent.value = 0;
|
|
|
+ contextBreakdown.value = { messages: 0, mcp: 0, skills: 0, system_prompt: 0, other: 0 };
|
|
|
+ editMode.value = 'full';
|
|
|
+ }
|
|
|
+
|
|
|
messages.value = newTask.messages && newTask.messages.length > 0 ? [...newTask.messages] : getDefaultMessages();
|
|
|
|
|
|
await nextTick();
|
|
|
@@ -272,10 +365,151 @@
|
|
|
attachedFiles: [],
|
|
|
};
|
|
|
|
|
|
+ contextUsed.value = 0;
|
|
|
+ contextPercent.value = 0;
|
|
|
+ contextBreakdown.value = { messages: 0, mcp: 0, skills: 0, system_prompt: 0, other: 0 };
|
|
|
+ editMode.value = 'full';
|
|
|
taskList.value.unshift(newTask);
|
|
|
switchTask(newTask.id);
|
|
|
};
|
|
|
|
|
|
+ const handlePinTask = async (task: Task) => {
|
|
|
+ try {
|
|
|
+ await pinSession(task.sessionId);
|
|
|
+ await fetchSessionList();
|
|
|
+ } catch {
|
|
|
+ message.error('置顶操作失败');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleThinkLevelChange = async (levelKey: string) => {
|
|
|
+ try {
|
|
|
+ await switchThinking(levelKey);
|
|
|
+ } catch {
|
|
|
+ message.error('切换思考级别失败');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleInsertSession = (_taskId: string, _taskName: string) => {
|
|
|
+ // 会话引用已由 ChatInputArea 内部以 chip 形式管理
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleEditModeChange = async (modeKey: string) => {
|
|
|
+ const sessionId = getCurrentSessionId();
|
|
|
+ if (!sessionId) return;
|
|
|
+ try {
|
|
|
+ await putSessionMode(sessionId, modeKey);
|
|
|
+ editMode.value = modeKey;
|
|
|
+ } catch {
|
|
|
+ message.error('切换模式失败');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleApprove = async () => {
|
|
|
+ const sessionId = pendingApprovalSessionId.value || getCurrentSessionId();
|
|
|
+ if (!sessionId) return;
|
|
|
+
|
|
|
+ // 清除当前消息的 pendingApproval 状态,获取中断前累计时长和思考内容
|
|
|
+ const lastMsg = messages.value[messages.value.length - 1];
|
|
|
+ const prevElapsedMs = lastMsg?.pendingApprovalData?.elapsedMs || 0;
|
|
|
+ const prevThinkingContent = lastMsg?.pendingApprovalData?.prevThinkingContent || lastMsg?.thinkingContent || '';
|
|
|
+ if (lastMsg && lastMsg.isPendingApproval) {
|
|
|
+ lastMsg.isPendingApproval = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const now = dayjs();
|
|
|
+ const aiMsgIndex = messages.value.length;
|
|
|
+ const aiMsg: Message = {
|
|
|
+ type: 'ai',
|
|
|
+ content: '',
|
|
|
+ thinkingContent: prevThinkingContent,
|
|
|
+ time: now.format('HH:mm'),
|
|
|
+ createdAt: now.toISOString(),
|
|
|
+ isLoading: true,
|
|
|
+ thinkingSteps: [],
|
|
|
+ generateStartTime: Date.now(),
|
|
|
+ baseDurationMs: prevElapsedMs,
|
|
|
+ };
|
|
|
+ messages.value.push(aiMsg);
|
|
|
+ await scrollToBottom();
|
|
|
+
|
|
|
+ loading.value = true;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const result = await chatResumeStream(
|
|
|
+ {
|
|
|
+ session_id: sessionId,
|
|
|
+ thread_id: pendingApprovalThreadId.value || undefined,
|
|
|
+ action: 'approve',
|
|
|
+ },
|
|
|
+ (chunk: string) => {
|
|
|
+ parseSSEData(chunk, aiMsgIndex);
|
|
|
+ scrollToBottom();
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ if (result.session_id) {
|
|
|
+ setCurrentSessionId(result.session_id);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('审批继续执行失败:', error);
|
|
|
+ message.error('审批失败,请重试');
|
|
|
+ const errorMsg = messages.value[aiMsgIndex];
|
|
|
+ if (errorMsg && errorMsg.isLoading) {
|
|
|
+ errorMsg.isLoading = false;
|
|
|
+ errorMsg.content = '抱歉,审批请求失败,请稍后重试。';
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ pendingApprovalSessionId.value = '';
|
|
|
+ pendingApprovalThreadId.value = '';
|
|
|
+ await scrollToBottom();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleReject = async () => {
|
|
|
+ const sessionId = pendingApprovalSessionId.value || getCurrentSessionId();
|
|
|
+ if (!sessionId) return;
|
|
|
+
|
|
|
+ // 清除当前消息的 pendingApproval 状态
|
|
|
+ const lastMsg = messages.value[messages.value.length - 1];
|
|
|
+ if (lastMsg && lastMsg.isPendingApproval) {
|
|
|
+ lastMsg.isPendingApproval = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ await chatResumeStream(
|
|
|
+ {
|
|
|
+ session_id: sessionId,
|
|
|
+ thread_id: pendingApprovalThreadId.value || undefined,
|
|
|
+ action: 'reject',
|
|
|
+ },
|
|
|
+ () => {}
|
|
|
+ );
|
|
|
+ } catch (error) {
|
|
|
+ console.error('拒绝操作失败:', error);
|
|
|
+ }
|
|
|
+
|
|
|
+ const now = dayjs();
|
|
|
+ const rejectMsg: Message = {
|
|
|
+ type: 'ai',
|
|
|
+ content: '已拒绝执行计划。',
|
|
|
+ time: now.format('HH:mm'),
|
|
|
+ createdAt: now.toISOString(),
|
|
|
+ };
|
|
|
+ messages.value.push(rejectMsg);
|
|
|
+
|
|
|
+ pendingApprovalSessionId.value = '';
|
|
|
+ pendingApprovalThreadId.value = '';
|
|
|
+
|
|
|
+ const currentTask = taskList.value.find((t) => t.id === currentTaskId.value);
|
|
|
+ if (currentTask) {
|
|
|
+ currentTask.messages = [...messages.value];
|
|
|
+ }
|
|
|
+
|
|
|
+ await scrollToBottom();
|
|
|
+ };
|
|
|
+
|
|
|
const handleDeleteTask = (task: Task, _index: number) => {
|
|
|
Modal.confirm({
|
|
|
title: '提示',
|
|
|
@@ -357,12 +591,6 @@
|
|
|
const file = target.files?.[0];
|
|
|
if (!file) return;
|
|
|
|
|
|
- if (!isPdfFile(file.name)) {
|
|
|
- message.error('仅支持 PDF 文件格式');
|
|
|
- target.value = '';
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
try {
|
|
|
const newFile: AttachedFile = {
|
|
|
id: `file-${Date.now()}`,
|
|
|
@@ -473,6 +701,10 @@
|
|
|
return;
|
|
|
}
|
|
|
if (loading.value) return;
|
|
|
+ if (pendingApprovalSessionId.value) {
|
|
|
+ message.warning('请先审批执行计划');
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
const userInput = inputMessage.value.trim();
|
|
|
const fileToSend = pendingFile.value;
|
|
|
@@ -482,6 +714,8 @@
|
|
|
|
|
|
try {
|
|
|
loading.value = true;
|
|
|
+ await nextTick();
|
|
|
+ chatMessagesRef.value?.forceScrollToBottom();
|
|
|
|
|
|
if (hasFile) {
|
|
|
await sendWithAttachment(fileToSend!, userInput);
|
|
|
@@ -544,6 +778,48 @@
|
|
|
steps.push(step);
|
|
|
};
|
|
|
|
|
|
+ const fetchModelMsg = async () => {
|
|
|
+ try {
|
|
|
+ const res = await getModelMsg();
|
|
|
+ if (res?.thinking_level) {
|
|
|
+ thinkLevel.value = res.thinking_level;
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('获取模型信息失败:', e);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const fetchSessionMode = async (sessionId: string) => {
|
|
|
+ try {
|
|
|
+ const res = await getSessionMode(sessionId);
|
|
|
+ if (res?.mode) {
|
|
|
+ editMode.value = res.mode;
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('获取会话模式失败:', e);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const fetchContext = async (sessionId: string) => {
|
|
|
+ try {
|
|
|
+ const res = await getContext(sessionId);
|
|
|
+ if (res?.current_usage != null) {
|
|
|
+ contextUsed.value = res.current_usage;
|
|
|
+ }
|
|
|
+ if (res?.total_limit != null) {
|
|
|
+ contextMax.value = res.total_limit;
|
|
|
+ }
|
|
|
+ if (res?.usage_pct != null) {
|
|
|
+ contextPercent.value = res.usage_pct;
|
|
|
+ }
|
|
|
+ if (res?.breakdown) {
|
|
|
+ contextBreakdown.value = res.breakdown;
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('获取上下文容量失败:', e);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
const handleSSEMessage = (data: any, aiMsgIndex: number) => {
|
|
|
const aiMsg = messages.value[aiMsgIndex];
|
|
|
const normalizedContent = normalizeLineBreaks(data.content || '');
|
|
|
@@ -561,6 +837,13 @@
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
+ // thinking_token - 模型思考过程内容
|
|
|
+ case 'thinking_token':
|
|
|
+ if (normalizedContent) {
|
|
|
+ aiMsg.thinkingContent = (aiMsg.thinkingContent || '') + normalizedContent;
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
// 系统进度消息 - 仅当 agent 不是 system 时才作为 thinking step
|
|
|
case 'progress': {
|
|
|
if (data.agent && data.agent !== 'system') {
|
|
|
@@ -639,6 +922,10 @@
|
|
|
message: `${(data.message || '审查完成').replace(/(\d+ms)/g, '')}${data.duration_ms ? `(${(data.duration_ms / 1000).toFixed(0)}s)` : ''}${data.progress ? ` [${data.progress}]` : ''}`,
|
|
|
timestamp: Date.now(),
|
|
|
});
|
|
|
+ // 用后端返回的时间替换前端计时,作为中断前的累计基准
|
|
|
+ if (data.duration_ms) {
|
|
|
+ aiMsg.baseDurationMs = data.duration_ms;
|
|
|
+ }
|
|
|
if (data.preview) {
|
|
|
aiMsg.content += data.preview;
|
|
|
}
|
|
|
@@ -685,9 +972,10 @@
|
|
|
case 'done':
|
|
|
if (data.session_id) {
|
|
|
aiMsg.sessionId = data.session_id;
|
|
|
+ fetchContext(data.session_id);
|
|
|
}
|
|
|
if (data.duration_ms) {
|
|
|
- aiMsg.durationMs = data.duration_ms;
|
|
|
+ aiMsg.durationMs = (aiMsg.baseDurationMs || 0) + data.duration_ms;
|
|
|
}
|
|
|
aiMsg.isLoading = false;
|
|
|
break;
|
|
|
@@ -697,6 +985,24 @@
|
|
|
aiMsg.content += normalizedContent.replace(/\n/g, ' \n');
|
|
|
}
|
|
|
break;
|
|
|
+
|
|
|
+ case 'interrupt': {
|
|
|
+ // 优先使用 agent_done 已设置的后端时间,否则用前端计时
|
|
|
+ const elapsedMs = aiMsg.baseDurationMs || (aiMsg.generateStartTime ? Date.now() - aiMsg.generateStartTime : 0);
|
|
|
+ aiMsg.isLoading = false;
|
|
|
+ aiMsg.isPendingApproval = true;
|
|
|
+ aiMsg.pendingApprovalData = {
|
|
|
+ message: data.message || '智能体制定了执行计划,等待您的审批...',
|
|
|
+ sessionId: data.session_id || getCurrentSessionId(),
|
|
|
+ threadId: data.thread_id,
|
|
|
+ interrupts: data.interrupts,
|
|
|
+ elapsedMs,
|
|
|
+ prevThinkingContent: aiMsg.thinkingContent,
|
|
|
+ };
|
|
|
+ pendingApprovalSessionId.value = data.session_id || getCurrentSessionId();
|
|
|
+ pendingApprovalThreadId.value = data.thread_id || '';
|
|
|
+ break;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
const currentTask = taskList.value.find((t) => t.id === currentTaskId.value);
|
|
|
@@ -763,6 +1069,7 @@
|
|
|
{
|
|
|
message: userInput,
|
|
|
session_id: getCurrentSessionId() || undefined,
|
|
|
+ mode: editMode.value,
|
|
|
},
|
|
|
(chunk: string) => {
|
|
|
parseSSEData(chunk, aiMsgIndex);
|
|
|
@@ -828,6 +1135,7 @@
|
|
|
file: file.originalFile!,
|
|
|
session_id: getCurrentSessionId() || undefined,
|
|
|
message: userInput || undefined,
|
|
|
+ mode: editMode.value,
|
|
|
},
|
|
|
(chunk: string) => {
|
|
|
parseSSEData(chunk, aiMsgIndex);
|
|
|
@@ -864,6 +1172,7 @@
|
|
|
}
|
|
|
} else {
|
|
|
await fetchSessionList();
|
|
|
+ fetchModelMsg();
|
|
|
|
|
|
// 去除重复的空白任务,只保留一个
|
|
|
taskList.value = taskList.value.filter((task, index) => {
|
|
|
@@ -908,6 +1217,16 @@
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ if (targetTask.sessionId) {
|
|
|
+ fetchContext(targetTask.sessionId);
|
|
|
+ fetchSessionMode(targetTask.sessionId);
|
|
|
+ } else {
|
|
|
+ contextUsed.value = 0;
|
|
|
+ contextPercent.value = 0;
|
|
|
+ contextBreakdown.value = { messages: 0, mcp: 0, skills: 0, system_prompt: 0, other: 0 };
|
|
|
+ editMode.value = 'full';
|
|
|
+ }
|
|
|
+
|
|
|
messages.value = targetTask.messages && targetTask.messages.length > 0 ? [...targetTask.messages] : getDefaultMessages();
|
|
|
|
|
|
previewFile.value = null;
|
|
|
@@ -1101,6 +1420,59 @@
|
|
|
margin-left: 10px;
|
|
|
}
|
|
|
|
|
|
+ .chat-header-title-wrapper {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+
|
|
|
+ &:hover .title-edit-btn {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .title-edit-btn {
|
|
|
+ width: 28px;
|
|
|
+ height: 28px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ background-image: var(--img-chat-edit-icon);
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-size: 16px 16px;
|
|
|
+ background-position: center;
|
|
|
+ cursor: pointer;
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity 0.2s;
|
|
|
+ border-radius: 4px;
|
|
|
+ margin-right: 4px;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background-color: rgba(10, 132, 255, 0.2);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .chat-header-title {
|
|
|
+ margin-left: 0;
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .title-edit-input {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ height: 28px;
|
|
|
+ padding: 0 8px;
|
|
|
+ font-size: 14px;
|
|
|
+ color: #e0e6ed;
|
|
|
+ background: rgba(10, 132, 255, 0.1);
|
|
|
+ border: 1px solid rgba(10, 132, 255, 0.5);
|
|
|
+ border-radius: 4px;
|
|
|
+ outline: none;
|
|
|
+
|
|
|
+ &:focus {
|
|
|
+ border-color: rgba(10, 132, 255, 0.8);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
.nav-btn {
|
|
|
width: 28px;
|
|
|
height: 28px;
|
|
|
@@ -1354,6 +1726,19 @@
|
|
|
background-size: 100% 100%;
|
|
|
}
|
|
|
}
|
|
|
+ .chat-header-title-wrapper {
|
|
|
+ .title-edit-input {
|
|
|
+ height: 36px;
|
|
|
+ font-size: 16px;
|
|
|
+ background: rgba(10, 132, 255, 0.15);
|
|
|
+ border-color: rgba(10, 132, 255, 0.6);
|
|
|
+ }
|
|
|
+ .title-edit-btn {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ background-size: 16px 16px;
|
|
|
+ }
|
|
|
+ }
|
|
|
.chat-input-area {
|
|
|
margin: 24px;
|
|
|
}
|