|
@@ -180,7 +180,19 @@ interface ConversationRow {
|
|
|
-->
|
|
-->
|
|
|
|
|
|
|
|
<!-- ===== 聊天区 ===== -->
|
|
<!-- ===== 聊天区 ===== -->
|
|
|
- <section class="col main-col" aria-label="消息">
|
|
|
|
|
|
|
+ <!--
|
|
|
|
|
+ 2026-11-14 用户要求:文件拖到**整个聊天区域**即可上传,不必精确拖到输入框。
|
|
|
|
|
+ 原实现只把 dragover/drop 绑在 composer 上,用户拖到消息列表就被浏览器
|
|
|
|
|
+ 当成「打开文件」跳走(或什么都不发生),体验很差。
|
|
|
|
|
+ -->
|
|
|
|
|
+ <section
|
|
|
|
|
+ class="col main-col"
|
|
|
|
|
+ aria-label="消息"
|
|
|
|
|
+ [class.drag-over]="dragging()"
|
|
|
|
|
+ (dragover)="onAreaDragOver($event)"
|
|
|
|
|
+ (dragleave)="onAreaDragLeave($event)"
|
|
|
|
|
+ (drop)="onAreaDrop($event)"
|
|
|
|
|
+ >
|
|
|
@if (chat.state() === 'loading') {
|
|
@if (chat.state() === 'loading') {
|
|
|
<!--
|
|
<!--
|
|
|
🔴 会话加载中 → 骨架屏(2026-11-14 用户要求「加载完成之后才显示」)。
|
|
🔴 会话加载中 → 骨架屏(2026-11-14 用户要求「加载完成之后才显示」)。
|
|
@@ -850,6 +862,27 @@ interface ConversationRow {
|
|
|
.main-col {
|
|
.main-col {
|
|
|
background: var(--surface);
|
|
background: var(--surface);
|
|
|
border-right: 1px solid var(--edge);
|
|
border-right: 1px solid var(--edge);
|
|
|
|
|
+ /* 拖拽高亮需要定位上下文(::after 铺满整块) */
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ }
|
|
|
|
|
+ /*
|
|
|
|
|
+ * 拖文件进入聊天区 → 整块蒙一层提示(2026-11-14 用户要求)。
|
|
|
|
|
+ * 用 ::after 而不是额外 DOM:不干扰内部布局与滚动。
|
|
|
|
|
+ */
|
|
|
|
|
+ .main-col.drag-over::after {
|
|
|
|
|
+ content: '松开即可添加到本条消息';
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ inset: var(--sp-3);
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ place-items: center;
|
|
|
|
|
+ border: 2px dashed var(--primary);
|
|
|
|
|
+ border-radius: var(--r-lg);
|
|
|
|
|
+ background: color-mix(in srgb, var(--primary-subtle) 88%, transparent);
|
|
|
|
|
+ color: var(--primary);
|
|
|
|
|
+ font-size: var(--fs-14);
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ pointer-events: none;
|
|
|
|
|
+ z-index: var(--z-float);
|
|
|
}
|
|
}
|
|
|
.rail-col {
|
|
.rail-col {
|
|
|
background: var(--rail-surface);
|
|
background: var(--rail-surface);
|
|
@@ -3470,6 +3503,50 @@ export class DkChatPage implements OnDestroy {
|
|
|
this.chat.sessionBusy() ? '发送将打断当前任务,容器会立即开始处理这条' : '',
|
|
this.chat.sessionBusy() ? '发送将打断当前任务,容器会立即开始处理这条' : '',
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 整块聊天区域的文件拖放(2026-11-14 用户要求)。
|
|
|
|
|
+ *
|
|
|
|
|
+ * ## 为什么不在 composer 上做
|
|
|
|
|
+ *
|
|
|
|
|
+ * 原实现把 `dragover`/`drop` 只绑在输入框上。用户拖文件到**消息列表**
|
|
|
|
|
+ * 这种更自然的位置时,浏览器会把 drop 当成「用浏览器打开这个文件」,
|
|
|
|
|
+ * 整个 SPA 直接跳走 —— 丢状态、看起来像崩溃。
|
|
|
|
|
+ *
|
|
|
|
|
+ * ## 现在的行为
|
|
|
|
|
+ *
|
|
|
|
|
+ * 拖进**整个聊天区**(`.main-col`)就高亮并接受,落到哪都算数。
|
|
|
|
|
+ * 输入框那条路径保留(两侧都能用,不会重复触发 —— 冒泡到 section 时
|
|
|
|
|
+ * composer 已经 preventDefault,这里再兜一次也无害)。
|
|
|
|
|
+ */
|
|
|
|
|
+ protected readonly dragging = signal(false);
|
|
|
|
|
+
|
|
|
|
|
+ protected onAreaDragOver(e: DragEvent): void {
|
|
|
|
|
+ /* 只接管「拖文件」;拖文本/链接保持浏览器默认 */
|
|
|
|
|
+ if (!e.dataTransfer?.types.includes('Files')) return;
|
|
|
|
|
+ /* 🔴 必须 preventDefault:否则浏览器把 drop 当「打开文件」,页面直接跳走 */
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ e.dataTransfer.dropEffect = 'copy';
|
|
|
|
|
+ if (!this.dragging()) this.dragging.set(true);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ protected onAreaDragLeave(e: DragEvent): void {
|
|
|
|
|
+ /*
|
|
|
|
|
+ * 只在**真正离开整个区域**时取消高亮。
|
|
|
|
|
+ * 子元素之间移动也会触发 dragleave,用 relatedTarget 判断可避免闪烁。
|
|
|
|
|
+ */
|
|
|
|
|
+ const to = e.relatedTarget as Node | null;
|
|
|
|
|
+ if (to && (e.currentTarget as HTMLElement).contains(to)) return;
|
|
|
|
|
+ this.dragging.set(false);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ protected onAreaDrop(e: DragEvent): void {
|
|
|
|
|
+ if (!e.dataTransfer?.types.includes('Files')) return;
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ this.dragging.set(false);
|
|
|
|
|
+ const files = e.dataTransfer.files;
|
|
|
|
|
+ if (files && files.length > 0) this.chat.stageFiles(files);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
retryOpen(): void {
|
|
retryOpen(): void {
|
|
|
const id = this.chat.agentId();
|
|
const id = this.chat.agentId();
|
|
|
if (id) void this.chat.open(id, this.chat.sessionId() || undefined, true);
|
|
if (id) void this.chat.open(id, this.chat.sessionId() || undefined, true);
|