|
|
@@ -0,0 +1,896 @@
|
|
|
+<template>
|
|
|
+ <div ref="wrapRef" class="resource-capsule">
|
|
|
+ <!-- 胶囊按钮 -->
|
|
|
+ <button type="button" :class="['capsule-btn', { open: isOpen }]" title="会话资源" :aria-expanded="isOpen" @click="isOpen = !isOpen">
|
|
|
+ <div class="capsule-icon"></div>
|
|
|
+ <span class="capsule-text"> </span>
|
|
|
+ <span v-if="totalCount > 0" class="capsule-count">{{ totalCount }}</span>
|
|
|
+ <div class="capsule-arrow"></div>
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <!-- 聚合下拉面板 -->
|
|
|
+ <Transition name="capsule-fade">
|
|
|
+ <div v-if="isOpen" class="capsule-popup" :style="{ width: `${popupWidth}px` }" @click.stop>
|
|
|
+ <div class="popup-header">
|
|
|
+ <div class="popup-title">
|
|
|
+ <div class="popup-title-icon"></div>
|
|
|
+ <span>会话资源</span>
|
|
|
+ </div>
|
|
|
+ <CloseOutlined class="close-icon" @click="isOpen = false" />
|
|
|
+ </div>
|
|
|
+ <div class="popup-body">
|
|
|
+ <template v-if="files.length || links.length || agents.length">
|
|
|
+ <div v-if="files.length" class="popup-section">
|
|
|
+ <div
|
|
|
+ :class="['section-title', { collapsed: collapsedFiles }]"
|
|
|
+ role="button"
|
|
|
+ tabindex="0"
|
|
|
+ :aria-expanded="!collapsedFiles"
|
|
|
+ @click="collapsedFiles = !collapsedFiles"
|
|
|
+ @keydown.enter.prevent="collapsedFiles = !collapsedFiles"
|
|
|
+ @keydown.space.prevent="collapsedFiles = !collapsedFiles"
|
|
|
+ >
|
|
|
+ <span class="section-title-text">文件</span>
|
|
|
+ <span class="section-count">{{ files.length }}</span>
|
|
|
+ <div class="section-arrow"></div>
|
|
|
+ </div>
|
|
|
+ <Transition name="collapse">
|
|
|
+ <div v-show="!collapsedFiles" class="section-body">
|
|
|
+ <div
|
|
|
+ v-for="item in files"
|
|
|
+ :key="fileKey(item)"
|
|
|
+ class="popup-item file-item"
|
|
|
+ :title="`预览 ${item.name}`"
|
|
|
+ @click="handleFileClick(item)"
|
|
|
+ >
|
|
|
+ <div v-if="previewingKey === fileKey(item)" class="row-loading"></div>
|
|
|
+ <div v-else class="item-icon" :style="{ backgroundImage: getFileIconVar(item.name) }"></div>
|
|
|
+ <div class="item-info">
|
|
|
+ <div class="item-name" :title="item.name">{{ item.name }}</div>
|
|
|
+ <div class="item-meta">
|
|
|
+ <span class="item-tag" :class="item.kind">{{ item.kind === 'upload' ? '上传' : 'AI生成' }}</span>
|
|
|
+ <span v-if="item.size != null">{{ formatFileSize(item.size) }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <button class="download-btn" :title="`下载 ${item.name}`" @click.stop="downloadFile(item)">
|
|
|
+ <div class="download-btn-icon"></div>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Transition>
|
|
|
+ </div>
|
|
|
+ <div v-if="links.length" class="popup-section">
|
|
|
+ <div
|
|
|
+ :class="['section-title', { collapsed: collapsedLinks }]"
|
|
|
+ role="button"
|
|
|
+ tabindex="0"
|
|
|
+ :aria-expanded="!collapsedLinks"
|
|
|
+ @click="collapsedLinks = !collapsedLinks"
|
|
|
+ @keydown.enter.prevent="collapsedLinks = !collapsedLinks"
|
|
|
+ @keydown.space.prevent="collapsedLinks = !collapsedLinks"
|
|
|
+ >
|
|
|
+ <span class="section-title-text">链接</span>
|
|
|
+ <span class="section-count">{{ links.length }}</span>
|
|
|
+ <div class="section-arrow"></div>
|
|
|
+ </div>
|
|
|
+ <Transition name="collapse">
|
|
|
+ <div v-show="!collapsedLinks" class="section-body">
|
|
|
+ <div v-for="link in links" :key="link.href" class="popup-item link-item" @click="openLink(link)">
|
|
|
+ <div class="link-glyph"></div>
|
|
|
+ <div class="item-info">
|
|
|
+ <div class="item-name" :title="link.title">{{ link.title }}</div>
|
|
|
+ <div class="item-meta link-href" :title="link.href">{{ link.href }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="link-arrow"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Transition>
|
|
|
+ </div>
|
|
|
+ <div v-if="agents.length" class="popup-section">
|
|
|
+ <div
|
|
|
+ :class="['section-title', { collapsed: collapsedAgents }]"
|
|
|
+ role="button"
|
|
|
+ tabindex="0"
|
|
|
+ :aria-expanded="!collapsedAgents"
|
|
|
+ @click="collapsedAgents = !collapsedAgents"
|
|
|
+ @keydown.enter.prevent="collapsedAgents = !collapsedAgents"
|
|
|
+ @keydown.space.prevent="collapsedAgents = !collapsedAgents"
|
|
|
+ >
|
|
|
+ <span class="section-title-text">智能体</span>
|
|
|
+ <span class="section-count">{{ agents.length }}</span>
|
|
|
+ <div class="section-arrow"></div>
|
|
|
+ </div>
|
|
|
+ <Transition name="collapse">
|
|
|
+ <div v-show="!collapsedAgents" class="section-body">
|
|
|
+ <div v-for="item in agents" :key="agentKey(item)" class="popup-item agent-item" @click="handleAgentClick(item)">
|
|
|
+ <div class="agent-icon"></div>
|
|
|
+ <div class="item-info">
|
|
|
+ <div class="item-name" :title="item.agent">{{ item.agent }}</div>
|
|
|
+ <div class="item-meta">
|
|
|
+ <span class="agent-status" :class="item.status">
|
|
|
+ <span class="status-dot"></span>
|
|
|
+ {{ agentStatusText(item.status) }}
|
|
|
+ </span>
|
|
|
+ <span>第{{ item.round }}轮</span>
|
|
|
+ <span v-if="item.toolCount > 0">{{ item.toolCount }} 次工具调用</span>
|
|
|
+ <span v-if="item.hiddenAnswer" class="item-tag folded">折叠回答</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <button class="locate-btn" :title="`定位到第 ${item.round} 轮消息`" @click.stop="handleLocateClick(item)">
|
|
|
+ <div class="locate-btn-icon"></div>
|
|
|
+ </button>
|
|
|
+ <div class="link-arrow"></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Transition>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div v-else class="popup-empty">
|
|
|
+ <div class="popup-empty-title">暂无内容</div>
|
|
|
+ <div class="popup-empty-desc">AI 生成的文件、可跳转链接与智能体调用记录会汇总到这里</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Transition>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+ import { ref, computed, watch, nextTick } from 'vue';
|
|
|
+ import { CloseOutlined } from '@ant-design/icons-vue';
|
|
|
+ import { onClickOutside, onKeyStroke } from '@vueuse/core';
|
|
|
+ import { message } from 'ant-design-vue';
|
|
|
+ import { formatFileSize, getFileIconVar, fetchPreviewFile } from './utils';
|
|
|
+ import type { AttachedFile, CapsuleFileItem, CapsuleLinkItem, CapsuleAgentItem } from './types';
|
|
|
+
|
|
|
+ const props = defineProps<{
|
|
|
+ files: CapsuleFileItem[];
|
|
|
+ links: CapsuleLinkItem[];
|
|
|
+ agents: CapsuleAgentItem[];
|
|
|
+ // 当前任务 id:切换任务时收起弹层,避免用户误操作上一个任务的数据
|
|
|
+ taskId?: string;
|
|
|
+ }>();
|
|
|
+
|
|
|
+ const emit = defineEmits<{
|
|
|
+ (e: 'file-preview', file: AttachedFile): void;
|
|
|
+ (e: 'agent-detail-click', data: { messageIndex: number; agent: string; agentId?: string }): void;
|
|
|
+ (e: 'locate-message', messageIndex: number): void;
|
|
|
+ }>();
|
|
|
+
|
|
|
+ const wrapRef = ref<HTMLElement>();
|
|
|
+ const isOpen = ref(false);
|
|
|
+ // 分区默认折叠,点击标题后才展开
|
|
|
+ const collapsedFiles = ref(true);
|
|
|
+ const collapsedLinks = ref(true);
|
|
|
+ const collapsedAgents = ref(true);
|
|
|
+ // 正在拉取预览内容的文件行(显示加载态、防重复点击)
|
|
|
+ const previewingKey = ref<string | null>(null);
|
|
|
+ const totalCount = computed(() => props.files.length + props.links.length + props.agents.length);
|
|
|
+
|
|
|
+ onClickOutside(wrapRef, () => {
|
|
|
+ isOpen.value = false;
|
|
|
+ });
|
|
|
+
|
|
|
+ // Esc 收起弹层(键盘可达性)
|
|
|
+ onKeyStroke('Escape', () => {
|
|
|
+ isOpen.value = false;
|
|
|
+ });
|
|
|
+
|
|
|
+ watch(
|
|
|
+ () => props.taskId,
|
|
|
+ () => {
|
|
|
+ isOpen.value = false;
|
|
|
+ // 切换任务后分区回到默认折叠状态
|
|
|
+ collapsedFiles.value = true;
|
|
|
+ collapsedLinks.value = true;
|
|
|
+ collapsedAgents.value = true;
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ // 弹层宽度自适应:窄窗口/拖拽缩放后避免超出聊天面板左边界被裁切。
|
|
|
+ // 容器带 scale 缩放,getBoundingClientRect 是缩放后的视口坐标,需换算回布局坐标
|
|
|
+ const popupWidth = ref(300);
|
|
|
+ const fitPopupWidth = () => {
|
|
|
+ const btn = wrapRef.value;
|
|
|
+ const panel = btn?.closest('.chat-panel') as HTMLElement | null;
|
|
|
+ if (!btn || !panel) return;
|
|
|
+ const btnRect = btn.getBoundingClientRect();
|
|
|
+ const panelRect = panel.getBoundingClientRect();
|
|
|
+ const scale = panel.offsetHeight ? panelRect.height / panel.offsetHeight : 1;
|
|
|
+ const available = (btnRect.right - panelRect.left - 8) / scale;
|
|
|
+ popupWidth.value = Math.max(200, Math.min(300, available));
|
|
|
+ };
|
|
|
+
|
|
|
+ watch(isOpen, (open) => {
|
|
|
+ if (open) nextTick(fitPopupWidth);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 稳定列表 key:文件按来源唯一标识(上传附件 id / 下载地址),智能体按消息×身份标识
|
|
|
+ const fileKey = (item: CapsuleFileItem): string =>
|
|
|
+ item.kind === 'upload' ? `upload-${item.file?.id || item.name}` : `download-${item.url || item.name}`;
|
|
|
+ const agentKey = (item: CapsuleAgentItem): string => `agent-${item.messageIndex}-${item.agentId || item.agent}`;
|
|
|
+
|
|
|
+ // 下载锚点挂在胶囊根节点内而非 body:
|
|
|
+ // 程序化 a.click() 的合成点击(detail === 0)会被 onClickOutside 当作"外部点击"而关闭弹层,
|
|
|
+ // 挂载到组件内部后合成点击的路径包含组件根节点,不会被误判
|
|
|
+ const triggerDownload = (href: string, filename: string) => {
|
|
|
+ const a = document.createElement('a');
|
|
|
+ a.href = href;
|
|
|
+ a.download = filename;
|
|
|
+ const mountEl = wrapRef.value || document.body;
|
|
|
+ mountEl.appendChild(a);
|
|
|
+ a.click();
|
|
|
+ mountEl.removeChild(a);
|
|
|
+ };
|
|
|
+
|
|
|
+ const downloadFile = async (item: CapsuleFileItem) => {
|
|
|
+ // AI 下载链接直接走地址下载
|
|
|
+ if (item.kind === 'download') {
|
|
|
+ if (!item.url) return;
|
|
|
+ triggerDownload(item.url, item.name);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 上传附件:优先用原始文件对象下载(字节保真),否则由本地缓存内容构造 Blob
|
|
|
+ const file = item.file;
|
|
|
+ if (!file) return;
|
|
|
+ if (file.originalFile) {
|
|
|
+ const url = URL.createObjectURL(file.originalFile);
|
|
|
+ triggerDownload(url, file.originalFile.name || file.name);
|
|
|
+ URL.revokeObjectURL(url);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ let blob: Blob | null = null;
|
|
|
+ if (file.content != null) {
|
|
|
+ blob = new Blob([file.content], { type: file.type || 'text/plain' });
|
|
|
+ } else if (file.arrayBuffer) {
|
|
|
+ blob = new Blob([file.arrayBuffer], { type: file.type || 'application/octet-stream' });
|
|
|
+ } else if (file.preview) {
|
|
|
+ try {
|
|
|
+ blob = await (await fetch(file.preview)).blob();
|
|
|
+ } catch {
|
|
|
+ blob = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!blob) {
|
|
|
+ emit('file-preview', file);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const url = URL.createObjectURL(blob);
|
|
|
+ triggerDownload(url, file.name);
|
|
|
+ URL.revokeObjectURL(url);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 点击文件行:统一在右侧面板打开预览(上传附件直接用本地数据,下载链接先拉取内容)
|
|
|
+ const handleFileClick = async (item: CapsuleFileItem) => {
|
|
|
+ const key = fileKey(item);
|
|
|
+ // 该行正在拉取预览内容时忽略重复点击
|
|
|
+ if (previewingKey.value === key) return;
|
|
|
+ if (item.kind === 'upload') {
|
|
|
+ if (item.file) emit('file-preview', item.file);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!item.url) return;
|
|
|
+ previewingKey.value = key;
|
|
|
+ try {
|
|
|
+ const previewFile = await fetchPreviewFile(item.url, item.name);
|
|
|
+ if (!previewFile) {
|
|
|
+ downloadFile(item);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ emit('file-preview', previewFile);
|
|
|
+ } catch (err) {
|
|
|
+ console.error('获取文件内容失败:', err);
|
|
|
+ message.error('文件预览失败,请尝试下载查看');
|
|
|
+ } finally {
|
|
|
+ previewingKey.value = null;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 与消息正文中 markdown 链接的渲染行为一致:新标签页打开
|
|
|
+ const openLink = (link: CapsuleLinkItem) => {
|
|
|
+ if (!link.href) return;
|
|
|
+ window.open(link.href, '_blank', 'noopener,noreferrer');
|
|
|
+ };
|
|
|
+
|
|
|
+ const agentStatusText = (status: CapsuleAgentItem['status']): string => {
|
|
|
+ if (status === 'done') return '已完成';
|
|
|
+ if (status === 'running') return '运行中';
|
|
|
+ if (status === 'waiting') return '等待确认';
|
|
|
+ return '已中断';
|
|
|
+ };
|
|
|
+
|
|
|
+ // 点击智能体条目:交给父组件打开右侧智能体详情面板(身份与思考卡片分组一致)
|
|
|
+ const handleAgentClick = (item: CapsuleAgentItem) => {
|
|
|
+ emit('agent-detail-click', { messageIndex: item.messageIndex, agent: item.agent, agentId: item.agentId });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 定位到该智能体所在的消息(收起弹层后由父组件滚动消息列表)
|
|
|
+ const handleLocateClick = (item: CapsuleAgentItem) => {
|
|
|
+ isOpen.value = false;
|
|
|
+ emit('locate-message', item.messageIndex);
|
|
|
+ };
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="less">
|
|
|
+ .resource-capsule {
|
|
|
+ position: relative;
|
|
|
+ flex-shrink: 0;
|
|
|
+ margin-left: 8px;
|
|
|
+ z-index: 10;
|
|
|
+
|
|
|
+ .capsule-btn {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ height: 36px;
|
|
|
+ padding: 0 12px;
|
|
|
+ border-radius: 18px;
|
|
|
+ background: linear-gradient(135deg, rgba(10, 132, 255, 0.22) 0%, rgba(10, 132, 255, 0.07) 100%);
|
|
|
+ border: 1px solid rgba(10, 132, 255, 0.45);
|
|
|
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
|
|
|
+ cursor: pointer;
|
|
|
+ user-select: none;
|
|
|
+ font-family: inherit;
|
|
|
+ transition:
|
|
|
+ background 0.2s,
|
|
|
+ border-color 0.2s,
|
|
|
+ box-shadow 0.2s,
|
|
|
+ transform 0.1s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: linear-gradient(135deg, rgba(10, 132, 255, 0.32) 0%, rgba(10, 132, 255, 0.14) 100%);
|
|
|
+ border-color: rgba(10, 132, 255, 0.65);
|
|
|
+ box-shadow:
|
|
|
+ 0 0 10px rgba(10, 132, 255, 0.25),
|
|
|
+ inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ background: linear-gradient(135deg, rgba(10, 132, 255, 0.4) 0%, rgba(10, 132, 255, 0.2) 100%);
|
|
|
+ transform: scale(0.97);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:focus-visible {
|
|
|
+ outline: 1px solid rgba(10, 132, 255, 0.8);
|
|
|
+ outline-offset: 1px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.open {
|
|
|
+ background: linear-gradient(135deg, rgba(10, 132, 255, 0.32) 0%, rgba(10, 132, 255, 0.14) 100%);
|
|
|
+ border-color: rgba(10, 132, 255, 0.65);
|
|
|
+ box-shadow:
|
|
|
+ 0 0 10px rgba(10, 132, 255, 0.25),
|
|
|
+ inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
|
|
+ }
|
|
|
+
|
|
|
+ .capsule-icon {
|
|
|
+ width: 14px;
|
|
|
+ height: 14px;
|
|
|
+ background-image: var(--img-chat-attach-icon);
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ flex-shrink: 0;
|
|
|
+ filter: drop-shadow(0 0 3px rgba(10, 132, 255, 0.5));
|
|
|
+ }
|
|
|
+
|
|
|
+ .capsule-text {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #e0e6ed;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .capsule-count {
|
|
|
+ min-width: 18px;
|
|
|
+ height: 18px;
|
|
|
+ line-height: 18px;
|
|
|
+ padding: 0 5px;
|
|
|
+ border-radius: 9px;
|
|
|
+ background: linear-gradient(135deg, #2f9bff 0%, #0a84ff 100%);
|
|
|
+ box-shadow: 0 1px 4px rgba(10, 132, 255, 0.45);
|
|
|
+ color: #fff;
|
|
|
+ font-size: 11px;
|
|
|
+ text-align: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .capsule-arrow {
|
|
|
+ width: 10px;
|
|
|
+ height: 10px;
|
|
|
+ background-image: var(--img-chat-arrow-icon);
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ flex-shrink: 0;
|
|
|
+ transition: transform 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.open .capsule-arrow {
|
|
|
+ transform: rotate(180deg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .capsule-popup {
|
|
|
+ position: absolute;
|
|
|
+ top: calc(100% + 8px);
|
|
|
+ right: 0;
|
|
|
+ width: 300px;
|
|
|
+ max-height: 400px;
|
|
|
+ background: linear-gradient(135deg, #0a1628 0%, #0d2137 50%, #0a1e35 100%);
|
|
|
+ border: 1px solid #1a4a6f;
|
|
|
+ border-radius: 10px;
|
|
|
+ box-shadow:
|
|
|
+ 0 8px 24px rgba(0, 0, 0, 0.5),
|
|
|
+ 0 0 14px rgba(10, 132, 255, 0.08);
|
|
|
+ z-index: 1000;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+
|
|
|
+ // 指向胶囊按钮的小箭头(与会话提示条箭头同一画法)
|
|
|
+ &::before {
|
|
|
+ content: '';
|
|
|
+ position: absolute;
|
|
|
+ top: -6px;
|
|
|
+ right: 26px;
|
|
|
+ width: 10px;
|
|
|
+ height: 10px;
|
|
|
+ transform: rotate(45deg);
|
|
|
+ background: #0d2137;
|
|
|
+ border-left: 1px solid #1a4a6f;
|
|
|
+ border-top: 1px solid #1a4a6f;
|
|
|
+ }
|
|
|
+
|
|
|
+ .popup-header {
|
|
|
+ padding: 12px 14px;
|
|
|
+ border-bottom: 1px solid rgba(26, 74, 111, 0.55);
|
|
|
+ background: linear-gradient(180deg, rgba(10, 132, 255, 0.1) 0%, rgba(10, 132, 255, 0.02) 100%);
|
|
|
+ border-radius: 10px 10px 0 0;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+
|
|
|
+ .popup-title {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ font-size: 13px;
|
|
|
+ font-weight: 500;
|
|
|
+ letter-spacing: 0.3px;
|
|
|
+ color: #e6edf3;
|
|
|
+
|
|
|
+ .popup-title-icon {
|
|
|
+ width: 15px;
|
|
|
+ height: 15px;
|
|
|
+ background-image: var(--img-chat-attach-icon);
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ filter: drop-shadow(0 0 3px rgba(10, 132, 255, 0.5));
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .close-icon {
|
|
|
+ cursor: pointer;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #8b949e;
|
|
|
+ width: 22px;
|
|
|
+ height: 22px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ border-radius: 50%;
|
|
|
+ transition:
|
|
|
+ color 0.2s,
|
|
|
+ background 0.2s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ color: #e0e6ed;
|
|
|
+ background: rgba(10, 132, 255, 0.2);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .popup-body {
|
|
|
+ flex: 1;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 6px;
|
|
|
+ scrollbar-width: thin;
|
|
|
+ scrollbar-color: rgba(139, 148, 158, 0.4) transparent;
|
|
|
+
|
|
|
+ &::-webkit-scrollbar {
|
|
|
+ width: 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::-webkit-scrollbar-track {
|
|
|
+ background: transparent;
|
|
|
+ }
|
|
|
+
|
|
|
+ &::-webkit-scrollbar-thumb {
|
|
|
+ background: rgba(139, 148, 158, 0.35);
|
|
|
+ border-radius: 3px;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: rgba(139, 148, 158, 0.6);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .popup-section {
|
|
|
+ & + .popup-section {
|
|
|
+ margin-top: 4px;
|
|
|
+ padding-top: 6px;
|
|
|
+ border-top: 1px solid rgba(63, 80, 106, 0.35);
|
|
|
+ }
|
|
|
+
|
|
|
+ .section-title {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ padding: 6px 10px;
|
|
|
+ border-radius: 6px;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 500;
|
|
|
+ letter-spacing: 0.4px;
|
|
|
+ color: #a9b9c9;
|
|
|
+ cursor: pointer;
|
|
|
+ user-select: none;
|
|
|
+ transition:
|
|
|
+ background 0.2s,
|
|
|
+ color 0.2s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: rgba(10, 132, 255, 0.12);
|
|
|
+ color: #e0e6ed;
|
|
|
+ }
|
|
|
+
|
|
|
+ .section-title-text {
|
|
|
+ font-size: inherit;
|
|
|
+ font-weight: inherit;
|
|
|
+ letter-spacing: inherit;
|
|
|
+ color: inherit;
|
|
|
+ }
|
|
|
+
|
|
|
+ .section-count {
|
|
|
+ min-width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ line-height: 16px;
|
|
|
+ padding: 0 5px;
|
|
|
+ border-radius: 8px;
|
|
|
+ background: rgba(10, 132, 255, 0.14);
|
|
|
+ color: #79c0ff;
|
|
|
+ font-size: 11px;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .section-arrow {
|
|
|
+ width: 10px;
|
|
|
+ height: 10px;
|
|
|
+ margin-left: auto;
|
|
|
+ background-image: var(--img-chat-arrow-icon);
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ opacity: 0.7;
|
|
|
+ transition:
|
|
|
+ transform 0.2s,
|
|
|
+ opacity 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover .section-arrow {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.collapsed .section-arrow {
|
|
|
+ transform: rotate(180deg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .popup-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+ padding: 9px 10px;
|
|
|
+ cursor: pointer;
|
|
|
+ border-radius: 6px;
|
|
|
+ transition: background 0.2s;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: rgba(10, 132, 255, 0.14);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ background: rgba(10, 132, 255, 0.22);
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-icon {
|
|
|
+ width: 26px;
|
|
|
+ height: 26px;
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .row-loading {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ margin: 5px;
|
|
|
+ border: 2px solid rgba(10, 132, 255, 0.3);
|
|
|
+ border-top-color: #0a84ff;
|
|
|
+ border-radius: 50%;
|
|
|
+ flex-shrink: 0;
|
|
|
+ animation: row-spin 0.8s linear infinite;
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-info {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+
|
|
|
+ .item-name {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #e0e6ed;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-meta {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ margin-top: 2px;
|
|
|
+ font-size: 11px;
|
|
|
+ color: #8b949e;
|
|
|
+
|
|
|
+ .item-tag {
|
|
|
+ padding: 0 5px;
|
|
|
+ border-radius: 3px;
|
|
|
+ line-height: 16px;
|
|
|
+
|
|
|
+ &.upload {
|
|
|
+ color: #79c0ff;
|
|
|
+ background: rgba(10, 132, 255, 0.15);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.download {
|
|
|
+ color: #8b949e;
|
|
|
+ background: rgba(139, 148, 158, 0.15);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.folded {
|
|
|
+ color: #8b949e;
|
|
|
+ background: rgba(139, 148, 158, 0.15);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .link-href {
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .agent-icon {
|
|
|
+ width: 26px;
|
|
|
+ height: 26px;
|
|
|
+ background-image: var(--img-chat-agent-icon);
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .agent-status {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+ padding: 0 5px;
|
|
|
+ border-radius: 3px;
|
|
|
+ line-height: 16px;
|
|
|
+
|
|
|
+ .status-dot {
|
|
|
+ width: 6px;
|
|
|
+ height: 6px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: currentColor;
|
|
|
+ }
|
|
|
+
|
|
|
+ &.running {
|
|
|
+ color: #0a84ff;
|
|
|
+ background: rgba(10, 132, 255, 0.15);
|
|
|
+ box-shadow: 0 0 6px rgba(10, 132, 255, 0.25);
|
|
|
+
|
|
|
+ .status-dot {
|
|
|
+ animation: status-pulse 1.2s ease-in-out infinite;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &.waiting {
|
|
|
+ color: #a371f7;
|
|
|
+ background: rgba(163, 113, 247, 0.12);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.done {
|
|
|
+ color: #3fb950;
|
|
|
+ background: rgba(63, 185, 80, 0.12);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.interrupted {
|
|
|
+ color: #d29922;
|
|
|
+ background: rgba(210, 153, 34, 0.12);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .download-btn {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 0;
|
|
|
+ border: none;
|
|
|
+ border-radius: 4px;
|
|
|
+ background: transparent;
|
|
|
+ cursor: pointer;
|
|
|
+ flex-shrink: 0;
|
|
|
+ transition:
|
|
|
+ background 0.2s,
|
|
|
+ transform 0.1s;
|
|
|
+
|
|
|
+ .download-btn-icon {
|
|
|
+ width: 14px;
|
|
|
+ height: 14px;
|
|
|
+ background-image: var(--img-chat-download-icon-btn);
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: rgba(10, 132, 255, 0.25);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ background: rgba(10, 132, 255, 0.4);
|
|
|
+ transform: scale(0.88);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:focus-visible {
|
|
|
+ outline: 1px solid rgba(10, 132, 255, 0.8);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .locate-btn {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 0;
|
|
|
+ border: none;
|
|
|
+ border-radius: 4px;
|
|
|
+ background: transparent;
|
|
|
+ cursor: pointer;
|
|
|
+ flex-shrink: 0;
|
|
|
+ transition:
|
|
|
+ background 0.2s,
|
|
|
+ transform 0.1s;
|
|
|
+
|
|
|
+ .locate-btn-icon {
|
|
|
+ width: 14px;
|
|
|
+ height: 14px;
|
|
|
+ background-image: var(--img-chat-locate-icon);
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ opacity: 0.8;
|
|
|
+ transition: opacity 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ background: rgba(10, 132, 255, 0.25);
|
|
|
+
|
|
|
+ .locate-btn-icon {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ background: rgba(10, 132, 255, 0.4);
|
|
|
+ transform: scale(0.88);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:focus-visible {
|
|
|
+ outline: 1px solid rgba(10, 132, 255, 0.8);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .link-glyph {
|
|
|
+ width: 26px;
|
|
|
+ height: 26px;
|
|
|
+ background-image: var(--img-chat-link-icon);
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-size: 14px 14px;
|
|
|
+ background-position: center;
|
|
|
+ flex-shrink: 0;
|
|
|
+ opacity: 0.8;
|
|
|
+ transition: opacity 0.2s;
|
|
|
+ }
|
|
|
+
|
|
|
+ &:hover .link-glyph {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ .link-arrow {
|
|
|
+ width: 10px;
|
|
|
+ height: 10px;
|
|
|
+ background-image: var(--img-chat-next-icon);
|
|
|
+ background-repeat: no-repeat;
|
|
|
+ background-size: 100% 100%;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .popup-empty {
|
|
|
+ padding: 20px 12px;
|
|
|
+ text-align: center;
|
|
|
+
|
|
|
+ .popup-empty-title {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #c9d1d9;
|
|
|
+ }
|
|
|
+
|
|
|
+ .popup-empty-desc {
|
|
|
+ margin-top: 4px;
|
|
|
+ font-size: 11px;
|
|
|
+ line-height: 1.5;
|
|
|
+ color: #8b949e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .capsule-fade-enter-active,
|
|
|
+ .capsule-fade-leave-active {
|
|
|
+ transition:
|
|
|
+ opacity 0.15s ease,
|
|
|
+ transform 0.15s ease;
|
|
|
+ }
|
|
|
+
|
|
|
+ .capsule-fade-enter-from,
|
|
|
+ .capsule-fade-leave-to {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(-4px);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 分区折叠过渡(与思考卡片折叠动画同一模式)
|
|
|
+ .collapse-enter-active,
|
|
|
+ .collapse-leave-active {
|
|
|
+ transition: all 0.25s ease;
|
|
|
+ overflow: hidden;
|
|
|
+ }
|
|
|
+
|
|
|
+ .collapse-enter-from,
|
|
|
+ .collapse-leave-to {
|
|
|
+ opacity: 0;
|
|
|
+ max-height: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .collapse-enter-to,
|
|
|
+ .collapse-leave-from {
|
|
|
+ opacity: 1;
|
|
|
+ max-height: 600px;
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes status-pulse {
|
|
|
+ 0%,
|
|
|
+ 100% {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ opacity: 0.3;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes row-spin {
|
|
|
+ to {
|
|
|
+ transform: rotate(360deg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+</style>
|