Преглед на файлове

feat(codex): 移除会话导入导出,新建会话改为侧栏整行主按钮

1) 按用户要求移除会话导入导出:删侧栏导出/导入按钮与隐藏 file input、相关函数,store 的 exportSessionsJson/importSessionsJson 一并删除;CodexSessionMeta.external 与「外部」徽标保留以兼容存量数据。2) 新建会话按钮移出 panel-head,改为整行主按钮(创建中禁用)。3) 顶栏右上角「新建会话」按钮删除,侧栏按钮成为唯一入口。

验证:pnpm type:check 89 条基线 / 本包 0 报错;pnpm build exit=0。
cc преди 2 седмици
родител
ревизия
28c6c5c747

+ 12 - 0
.workbuddy/memory/2026-09-24.md

@@ -250,3 +250,15 @@ composer 的 `cwd` prop 仅用于「切会话清空草稿」的 watch,不渲
 弹框文案明确「只从本机列表移除,Codex 侧已落盘的历史不受影响」,避免用户误以为会删服务器数据。
 顺带删掉行内确认的 `pendingDeleteId` 状态与 `__confirm` 样式。
 验证:`pnpm type:check` 89 基线 / 本包 0 报错;`pnpm build` exit=0。
+
+## 移除会话导入导出 + 新建会话入口调整
+
+用户三条要求:① 移除会话导入导出功能;② 放大新建会话按钮;③ 移除顶栏右上角的「新建会话」。
+- **导入导出**:侧栏「导出 / 导入」按钮 + 隐藏 file input + `onExportSessions/onImportClick/onFileChange`
+  全删;store 的 `exportSessionsJson / importSessionsJson` 一并删除(已无调用方)。
+  `CodexSessionMeta.external` 标记与侧栏「外部」徽标保留(兼容已导入过的存量数据)。
+- **新建会话**:侧栏 head 里的「新建」小按钮移出,改成 head 下方**整行主按钮**
+  (`__create`:34px 高、主色虚线边框、hover 变实心主色、创建中禁用并显示「创建中…」)。
+- **顶栏**:右上角「新建会话」按钮删除,emit `'create'` 一并移除;视图里工具栏的 `@create` 解绑。
+  侧栏按钮成为唯一入口。
+验证:`pnpm type:check` 89 基线 / 本包 0 报错;`pnpm build` exit=0。

+ 1 - 6
ai-electron/frontend/src/codex/components/codexChatToolbar.vue

@@ -52,17 +52,13 @@
         诊断
       </a-button>
 
-      <a-button size="small" @click="emit('create')">
-        <template #icon><Icon icon="mdi:plus" :size="14" /></template>
-        新建会话
-      </a-button>
     </div>
   </header>
 </template>
 
 <script lang="ts" setup>
   /**
-   * 顶部状态条:运行时状态 / 工作目录 / 导出 / 诊断 / 新建会话。
+   * 顶部状态条:运行时状态 / 运行参数徽标 / 导出 / 诊断。
    *
    * 状态直接读 store(这里没有第二份状态),只有需要 DOM 的动作(导出长图)才上抛给页面。
    */
@@ -74,7 +70,6 @@
   import type { CodexReportFormat } from '../types/codexTypes';
 
   const emit = defineEmits<{
-    (e: 'create'): void;
     (e: 'diagnostics'): void;
     (e: 'export', format: CodexReportFormat): void;
   }>();

+ 39 - 60
ai-electron/frontend/src/codex/components/codexSessionSidebar.vue

@@ -3,24 +3,14 @@
     <div class="codex-panel-head">
       <span>会话</span>
       <span class="codex-panel-head__count">{{ sessions.length }}</span>
-      <span class="codex-panel-head__spacer"></span>
-      <button class="codex-btn" type="button" title="导出会话列表(仅元数据,不含消息正文)" @click="onExportSessions">
-        导出
-      </button>
-      <button class="codex-btn" type="button" title="导入会话列表" @click="onImportClick">导入</button>
-      <button class="codex-btn" type="button" :disabled="creating" @click="onCreate">
-        <Icon icon="mdi:plus" :size="13" />
-        新建
-      </button>
-      <input
-        ref="fileInputRef"
-        class="codex-session__file"
-        type="file"
-        accept="application/json,.json"
-        @change="onFileChange"
-      />
     </div>
 
+    <!-- 新建会话:唯一入口,放大成整行主按钮 -->
+    <button class="codex-session__create" type="button" :disabled="creating" @click="onCreate">
+      <Icon icon="mdi:plus" :size="16" />
+      {{ creating ? '创建中…' : '新建会话' }}
+    </button>
+
     <div class="codex-panel-body">
       <div v-if="!sessions.length" class="codex-placeholder">
         还没有会话。<br />
@@ -86,11 +76,10 @@
    */
   import { computed, nextTick, ref } from 'vue';
   import { Input, Modal } from 'ant-design-vue';
-  import { downloadByData } from '@/core/utils/file/download';
   import Icon from '@/core/components/Icon/src/Icon.vue';
   import { useMessage } from '@/core/hooks/web/useMessage';
   import { useCodexChatStore } from '../store/codexChatStore';
-  import { codexTimestampTag, formatCodexRelative } from '../utils/codexFormat';
+  import { formatCodexRelative } from '../utils/codexFormat';
   import type { CodexSessionMeta } from '../types/codexTypes';
 
   const emit = defineEmits<{
@@ -107,7 +96,6 @@
   const renamingId = ref('');
   const renameDraft = ref('');
   const renameInputRef = ref();
-  const fileInputRef = ref<HTMLInputElement>();
 
   const sessions = computed(() => store.sessions);
   const activeThreadId = computed(() => store.activeThreadId);
@@ -138,38 +126,6 @@
     }
   }
 
-  /* ---------------- 会话列表导出 / 导入 ---------------- */
-
-  function onExportSessions() {
-    if (!store.sessions.length) {
-      showMessage('还没有会话可导出', 'warning');
-      return;
-    }
-    downloadByData(
-      store.exportSessionsJson(),
-      `codex-sessions-${codexTimestampTag()}.json`,
-      'application/json;charset=utf-8',
-    );
-    showMessage(`已导出 ${store.sessions.length} 条会话记录`, 'success');
-  }
-
-  function onImportClick() {
-    fileInputRef.value?.click();
-  }
-
-  async function onFileChange(event: Event) {
-    const input = event.target as HTMLInputElement;
-    const file = input.files?.[0];
-    input.value = '';
-    if (!file) return;
-    try {
-      const { imported, skipped } = store.importSessionsJson(await file.text());
-      showMessage(`导入完成:新增 ${imported}、跳过 ${skipped}(重复或本机已存在)`, 'success');
-    } catch (error: any) {
-      showMessage(error?.message || '导入失败:文件格式不正确', 'error');
-    }
-  }
-
   function startRename(session: CodexSessionMeta) {
     renamingId.value = session.threadId;
     renameDraft.value = session.title;
@@ -223,15 +179,6 @@
     flex: 1 1 auto;
   }
 
-  /* 导入会话列表用的隐藏 file input:不能 display:none,否则某些环境点不动 */
-  .codex-session__file {
-    position: absolute;
-    width: 1px;
-    height: 1px;
-    opacity: 0;
-    pointer-events: none;
-  }
-
   /* 「外部导入」标记 */
   .codex-session__external {
     flex: 0 0 auto;
@@ -302,6 +249,38 @@
       text-overflow: ellipsis;
     }
 
+    /* 新建会话:整行主按钮(顶栏的新建已移除,这是唯一入口) */
+    &__create {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      gap: 6px;
+      flex: 0 0 auto;
+      height: 34px;
+      margin: var(--codex-sp-2) 10px;
+      font-size: 13px;
+      font-weight: 600;
+      color: var(--codex-primary);
+      background: var(--codex-primary-soft);
+      border: 1px dashed fade(@primary-color, 45%);
+      border-radius: var(--codex-radius);
+      cursor: pointer;
+      transition:
+        background 0.15s ease-out,
+        border-color 0.15s ease-out;
+
+      &:hover:not(:disabled) {
+        color: #fff;
+        background: var(--codex-primary);
+        border-color: var(--codex-primary);
+      }
+
+      &:disabled {
+        opacity: 0.6;
+        cursor: not-allowed;
+      }
+    }
+
     &__meta {
       flex: 0 0 auto;
       font-size: 11px;

+ 0 - 58
ai-electron/frontend/src/codex/store/codexChatStore.ts

@@ -456,64 +456,6 @@ export const useCodexChatStore = defineStore('codex-chat', {
       writeBoard(list);
     },
 
-    /* ---------------- 会话列表导入导出 ---------------- */
-
-    /** 导出会话元数据(不含消息正文:正文在主进程事件日志里,导出没意义) */
-    exportSessionsJson(): string {
-      return JSON.stringify(
-        { version: 1, exportedAt: new Date().toISOString(), sessions: this.sessions },
-        null,
-        2,
-      );
-    },
-
-    /**
-     * 导入会话元数据:按 threadId 去重,已存在则跳过。
-     *
-     * 导入进来的会话在本机 Codex 侧没有记录(事件回读不到),
-     * 因此标记 external,打开时如实提示而不是装作能恢复。
-     */
-    importSessionsJson(raw: string): { imported: number; skipped: number } {
-      let parsed: any;
-      try {
-        parsed = JSON.parse(raw);
-      } catch {
-        throw new Error('文件不是合法的 JSON');
-      }
-      const list = Array.isArray(parsed?.sessions) ? parsed.sessions : Array.isArray(parsed) ? parsed : [];
-      if (!list.length) throw new Error('文件里没有会话记录');
-
-      const existing = new Set(this.sessions.map((item) => item.threadId));
-      const added: CodexSessionMeta[] = [];
-      let skipped = 0;
-
-      for (const item of list) {
-        const threadId = String(item?.threadId ?? '').trim();
-        if (!threadId || existing.has(threadId)) {
-          skipped += 1;
-          continue;
-        }
-        existing.add(threadId);
-        added.push({
-          threadId,
-          title: String(item?.title ?? '导入的会话'),
-          cwd: String(item?.cwd ?? ''),
-          model: item?.model ?? null,
-          createdAt: String(item?.createdAt ?? new Date().toISOString()),
-          updatedAt: String(item?.updatedAt ?? new Date().toISOString()),
-          turnCount: Number(item?.turnCount ?? 0) || 0,
-          sandbox: item?.sandbox ?? this.runDefaults.sandbox,
-          approvalPolicy: item?.approvalPolicy ?? this.runDefaults.approvalPolicy,
-          external: true,
-        });
-      }
-
-      if (added.length) {
-        this.sessions = [...added, ...this.sessions];
-        this.persistSessions();
-      }
-      return { imported: added.length, skipped };
-    },
 
     /** 切换会话使用的模型(按回合下发给 Codex,下一轮生效) */
     setSessionModel(threadId: string, model: string | null) {

+ 0 - 1
ai-electron/frontend/src/codex/views/codexAnalysis/codexAnalysis.vue

@@ -11,7 +11,6 @@
 
     <template v-else>
       <CodexChatToolbar
-        @create="onCreate"
         @diagnostics="diagnosticsOpen = true"
         @export="onExport"
       />