|
@@ -503,6 +503,29 @@ export interface NewSessionRequest {
|
|
|
}
|
|
}
|
|
|
</nav>
|
|
</nav>
|
|
|
|
|
|
|
|
|
|
+ <!-- 删除工作区确认(2026-11-01) -->
|
|
|
|
|
+ <ui-modal
|
|
|
|
|
+ [open]="removingWorkspace() !== null"
|
|
|
|
|
+ title="删除这个工作区?"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ (openChange)="$event || removingWorkspace.set(null)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <p class="rm-note">
|
|
|
|
|
+ 将删除工作区「{{ removingWorkspace()?.name }}」。
|
|
|
|
|
+ @if (removingWorkspace(); as rw) {
|
|
|
|
|
+ @if (sessionCountIn(rw.id) > 0) {
|
|
|
|
|
+ 其中的 <strong>{{ sessionCountIn(rw.id) }} 条对话会被移回「日常对话」,不会被删除</strong>。
|
|
|
|
|
+ } @else {
|
|
|
|
|
+ 这个工作区里没有对话。
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ </p>
|
|
|
|
|
+ <div class="rm-acts">
|
|
|
|
|
+ <ui-button variant="ghost" (click)="removingWorkspace.set(null)">取消</ui-button>
|
|
|
|
|
+ <ui-button variant="danger" (click)="doDeleteWorkspace()">删除工作区</ui-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </ui-modal>
|
|
|
|
|
+
|
|
|
<!-- 归档确认(2026-11-01 用户要求:归档也要二次提示) -->
|
|
<!-- 归档确认(2026-11-01 用户要求:归档也要二次提示) -->
|
|
|
<ui-modal
|
|
<ui-modal
|
|
|
[open]="archiving() !== null"
|
|
[open]="archiving() !== null"
|
|
@@ -1014,16 +1037,23 @@ export interface NewSessionRequest {
|
|
|
font-weight: 500;
|
|
font-weight: 500;
|
|
|
color: var(--muted);
|
|
color: var(--muted);
|
|
|
letter-spacing: 0.2px;
|
|
letter-spacing: 0.2px;
|
|
|
|
|
+ /* 名字占满剩余空间,「+」被推到最右 */
|
|
|
|
|
+ flex: 1;
|
|
|
}
|
|
}
|
|
|
- /* 分组标题的「+」默认不显示,悬停分组头才出现 —— 常驻一排加号会抢注意力 */
|
|
|
|
|
|
|
+ /*
|
|
|
|
|
+ * 分组头的「+」**常驻显示**(2026-11-01 用户要求)。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 原先是 opacity: 0 悬停才出现 —— 用户根本不知道有「新建工作区」这个入口。
|
|
|
|
|
+ * 图标体积小,常驻不会抢注意力,反而让入口可发现。
|
|
|
|
|
+ */
|
|
|
.sect-head ui-button {
|
|
.sect-head ui-button {
|
|
|
- opacity: 0;
|
|
|
|
|
- transition: opacity var(--motion-fast) var(--ease-standard);
|
|
|
|
|
- }
|
|
|
|
|
- .sect-head:hover ui-button,
|
|
|
|
|
- .sect-head:focus-within ui-button {
|
|
|
|
|
opacity: 1;
|
|
opacity: 1;
|
|
|
}
|
|
}
|
|
|
|
|
+ /* 计数紧跟名字,不与「+」抢位置 */
|
|
|
|
|
+ .sect-count {
|
|
|
|
|
+ font-size: var(--fs-11);
|
|
|
|
|
+ color: var(--faint);
|
|
|
|
|
+ }
|
|
|
.sect-count {
|
|
.sect-count {
|
|
|
font-size: var(--fs-11);
|
|
font-size: var(--fs-11);
|
|
|
color: var(--faint);
|
|
color: var(--faint);
|
|
@@ -1151,15 +1181,18 @@ export interface NewSessionRequest {
|
|
|
flex: 1;
|
|
flex: 1;
|
|
|
min-width: 0;
|
|
min-width: 0;
|
|
|
}
|
|
}
|
|
|
|
|
+ /*
|
|
|
|
|
+ * 工作区操作图标**常驻显示**(2026-11-01 用户要求)。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 原先是 display:none + 悬停/聚焦才出现 —— 用户不知道文件夹上还有这些操作,
|
|
|
|
|
+ * 只能靠"碰运气"把鼠标移上去。侧栏本来就窄,三颗小图标常驻不会造成拥挤,
|
|
|
|
|
+ * 换来的是「看得见就能点」。
|
|
|
|
|
+ */
|
|
|
.ws-acts {
|
|
.ws-acts {
|
|
|
- display: none;
|
|
|
|
|
|
|
+ display: flex;
|
|
|
gap: 0;
|
|
gap: 0;
|
|
|
flex-shrink: 0;
|
|
flex-shrink: 0;
|
|
|
}
|
|
}
|
|
|
- .ws-head:hover .ws-acts,
|
|
|
|
|
- .ws-head:focus-within .ws-acts {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- }
|
|
|
|
|
.ws-body {
|
|
.ws-body {
|
|
|
padding-left: var(--sp-4);
|
|
padding-left: var(--sp-4);
|
|
|
}
|
|
}
|
|
@@ -1841,6 +1874,13 @@ export class SessionTreeComponent {
|
|
|
value: '',
|
|
value: '',
|
|
|
confirmText: '创建',
|
|
confirmText: '创建',
|
|
|
allowEmpty: false,
|
|
allowEmpty: false,
|
|
|
|
|
+ /*
|
|
|
|
|
+ * 🔴 重名校验放在提交前(2026-11-01 用户要求)。
|
|
|
|
|
+ * 两个同名文件夹在侧栏里完全无法区分,用户点了也不知道进了哪个。
|
|
|
|
|
+ * 校验失败时弹窗不关闭、当场提示,而不是静默失效。
|
|
|
|
|
+ */
|
|
|
|
|
+ validate: (name) =>
|
|
|
|
|
+ this.workspaceSvc.hasName(name) ? `已经有叫「${name.trim()}」的工作区了` : null,
|
|
|
onConfirm: (name) => this.workspaceSvc.create(name),
|
|
onConfirm: (name) => this.workspaceSvc.create(name),
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
@@ -1890,6 +1930,9 @@ export class SessionTreeComponent {
|
|
|
value: w.name,
|
|
value: w.name,
|
|
|
confirmText: '保存',
|
|
confirmText: '保存',
|
|
|
allowEmpty: false,
|
|
allowEmpty: false,
|
|
|
|
|
+ // 重命名同样查重(排除自己,否则「不改名直接保存」会被误判)
|
|
|
|
|
+ validate: (name) =>
|
|
|
|
|
+ this.workspaceSvc.hasName(name, w.id) ? `已经有叫「${name.trim()}」的工作区了` : null,
|
|
|
onConfirm: (name) => this.workspaceSvc.rename(w.id, name),
|
|
onConfirm: (name) => this.workspaceSvc.rename(w.id, name),
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
@@ -1900,12 +1943,35 @@ export class SessionTreeComponent {
|
|
|
* 🔴 **不删其中的会话** —— 把会话退回「日常对话」。
|
|
* 🔴 **不删其中的会话** —— 把会话退回「日常对话」。
|
|
|
* 连带删除会让用户看不到的东西被静默销毁,这是数据丢失,不是"整理"。
|
|
* 连带删除会让用户看不到的东西被静默销毁,这是数据丢失,不是"整理"。
|
|
|
*/
|
|
*/
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 删除工作区 —— **二次确认**(2026-11-01 用户要求)。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 删除工作区不是纯 UI 操作:它下面若有对话,会被移回「日常对话」。
|
|
|
|
|
+ * 不说清楚就直接删,用户会以为对话一起没了。
|
|
|
|
|
+ */
|
|
|
protected deleteWorkspace(id: string, e: Event): void {
|
|
protected deleteWorkspace(id: string, e: Event): void {
|
|
|
e.stopPropagation();
|
|
e.stopPropagation();
|
|
|
- for (const s of this.sessionsSvc.all().filter((x) => x.workspaceId === id)) {
|
|
|
|
|
|
|
+ const w = this.workspaceSvc.byId(id);
|
|
|
|
|
+ if (w) this.removingWorkspace.set(w);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 待删除的工作区(非 null = 确认框打开) */
|
|
|
|
|
+ protected readonly removingWorkspace = signal<Workspace | null>(null);
|
|
|
|
|
+
|
|
|
|
|
+ /** 确认删除工作区:其下会话退回日常对话 */
|
|
|
|
|
+ protected doDeleteWorkspace(): void {
|
|
|
|
|
+ const w = this.removingWorkspace();
|
|
|
|
|
+ this.removingWorkspace.set(null);
|
|
|
|
|
+ if (!w) return;
|
|
|
|
|
+ for (const s of this.sessionsSvc.all().filter((x) => x.workspaceId === w.id)) {
|
|
|
this.sessionsSvc.moveTo(s.id, null);
|
|
this.sessionsSvc.moveTo(s.id, null);
|
|
|
}
|
|
}
|
|
|
- this.workspaceSvc.remove(id);
|
|
|
|
|
|
|
+ this.workspaceSvc.remove(w.id);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 该工作区下的会话数(确认框里说明用) */
|
|
|
|
|
+ protected sessionCountIn(wsId: string): number {
|
|
|
|
|
+ return this.sessionsSvc.all().filter((s) => s.workspaceId === wsId).length;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ---- 会话操作 ----
|
|
// ---- 会话操作 ----
|