Ver Fonte

feat(frontend): 智能工作台聊天界面与左栏配色优化

- 聊天区改为白色卡片,新增对话头、欢迎示例卡与消息气泡/头像/输入区重排
- 左栏菜单与会话历史背景、字体、悬停和选中配色对齐自主模式侧栏
cc há 1 dia atrás
pai
commit
cba5b1ec5d

+ 10 - 8
ai-electron/frontend/src/ai/components/ChatComposer.vue

@@ -334,8 +334,9 @@
 <style lang="less" scoped>
   .ai-composer {
     flex: 0 0 auto;
-    padding: var(--ai-sp-3) var(--ai-sp-5) var(--ai-sp-4);
-    background: var(--ai-bg-page);
+    padding: 12px 24px 14px;
+    background: #fff;
+    border-top: 1px solid #eff2f7;
   }
 
   /*
@@ -347,17 +348,18 @@
     max-width: calc(var(--ai-content-max) - var(--ai-sp-5) * 2);
     margin: 0 auto;
     padding: 10px 12px 8px;
-    background: var(--ai-bg);
-    border: 1px solid var(--ai-border);
+    background: #fafbfe;
+    border: 1px solid #e7ebf3;
     border-radius: 14px;
-    box-shadow: var(--ai-shadow-sm);
+    box-shadow: none;
     transition:
       border-color 0.16s ease-out,
       box-shadow 0.16s ease-out;
 
     &.is-focus {
-      border-color: fade(@primary-color, 55%);
-      box-shadow: 0 0 0 3px var(--ai-primary-softer);
+      background: #fff;
+      border-color: #2a50ec;
+      box-shadow: 0 0 0 3px rgb(42 80 236 / 10%);
     }
   }
 
@@ -577,7 +579,7 @@
     margin: 6px auto 0;
     font-size: 11px;
     text-align: center;
-    color: var(--ai-text-2);
+    color: #98a2b3;
   }
 
   @media (max-width: 900px) {

+ 34 - 20
ai-electron/frontend/src/ai/components/ChatMessageItem.vue

@@ -205,17 +205,17 @@
       align-items: center;
       gap: 4px;
       max-width: 240px;
-      height: 20px;
-      padding: 0 7px;
+      height: 22px;
+      padding: 0 8px;
+      color: #5b6577;
       font-size: 11px;
-      color: var(--ai-text-2);
-      background: var(--ai-bg-subtle);
-      border: 1px solid var(--ai-border);
-      border-radius: 10px;
+      background: #f4f6fa;
+      border: 1px solid #e7ebf3;
+      border-radius: 6px;
 
       &-name {
         overflow: hidden;
-        color: var(--ai-text);
+        color: #111827;
         white-space: nowrap;
         text-overflow: ellipsis;
       }
@@ -231,10 +231,11 @@
       justify-content: center;
       width: var(--ai-avatar);
       height: var(--ai-avatar);
-      margin-top: 2px;
-      color: var(--ai-primary);
-      background: var(--ai-primary-soft);
-      border-radius: 50%;
+      margin-top: 3px;
+      color: #2a50ec;
+      background: linear-gradient(135deg, #eef2ff, #dfe8ff);
+      border: 1px solid #dbe4ff;
+      border-radius: 9px;
     }
 
     &__main {
@@ -244,22 +245,34 @@
     &__head {
       display: flex;
       align-items: center;
-      gap: var(--ai-sp-2);
+      gap: 8px;
       min-height: 18px;
-      margin-bottom: var(--ai-sp-2);
+      margin-bottom: 10px;
+      padding-bottom: 8px;
+      border-bottom: 1px dashed #eff2f7;
     }
 
     &__role {
+      display: inline-flex;
+      align-items: center;
+      gap: 6px;
+      color: #111827;
       font-size: 13px;
       font-weight: 600;
-      color: var(--ai-primary);
+
+      &::before {
+        content: '';
+        width: 6px;
+        height: 6px;
+        background: #2a50ec;
+        border-radius: 50%;
+      }
     }
 
     &__time {
+      color: #98a2b3;
       font-size: 11px;
       font-variant-numeric: tabular-nums;
-      color: var(--ai-text-2);
-      opacity: 0.8;
     }
 
     &__star {
@@ -292,14 +305,15 @@
       max-width: 72%;
       margin: 0;
       padding: 10px 14px;
+      color: #111827;
       font-size: 14px;
       line-height: 1.7;
       white-space: pre-wrap;
       word-break: break-word;
-      color: var(--ai-text);
-      background: var(--ai-primary-soft);
-      border: 1px solid fade(@primary-color, 18%);
-      border-radius: var(--ai-radius-bubble);
+      background: #eef2ff;
+      border: 1px solid #dbe4ff;
+      border-radius: 14px 14px 4px 14px;
+      box-shadow: 0 1px 2px rgb(16 24 40 / 4%);
     }
 
     &__body {

+ 256 - 61
ai-electron/frontend/src/ai/components/ChatPanel.vue

@@ -1,30 +1,57 @@
 <template>
-  <main class="ai-main">
+  <main class="ai-main ai-chat-shell">
+    <!-- 对话头:工作台内没有外层标题栏,这里补会话名与状态;传统分析页有顶部条,默认不显示 -->
+    <header v-if="showHeader" class="ai-chat__head">
+      <div class="ai-chat__head-inner">
+        <span class="ai-chat__head-icon">
+          <Icon icon="mdi:message-text-outline" :size="18" />
+        </span>
+        <div class="ai-chat__head-main">
+          <div class="ai-chat__head-title">{{ hasConversation ? title || '当前对话' : '新对话' }}</div>
+          <div class="ai-chat__head-sub">
+            {{ hasConversation ? `共 ${messages.length} 条消息` : '描述分析目标,AI 会检索案件数据并执行只读查询' }}
+          </div>
+        </div>
+        <span v-if="streaming" class="ai-chat__head-status">
+          <i class="ai-chat__head-dot"></i>
+          生成中
+        </span>
+      </div>
+    </header>
+
     <!-- 未打开案件 -->
     <div v-if="disabled" class="ai-empty-tip ai-chat__center">
       请先打开一个案件,AI 数据分析将基于当前案件的数据空间工作。
     </div>
 
     <!-- 空态:用法引导 -->
-    <div v-else-if="!hasConversation" class="ai-scroll">
+    <div v-else-if="!hasConversation" class="ai-scroll ai-chat__welcome-scroll">
       <div class="ai-column ai-chat__welcome">
+        <span class="ai-chat__welcome-icon">
+          <Icon icon="mdi:sparkles" :size="22" />
+        </span>
         <h1 class="ai-chat__welcome-title">用自然语言完成一次数据研判</h1>
         <p class="ai-chat__welcome-desc">
           AI 会在当前案件的数据空间中检索表结构、编写并执行只读 SQL,把结果整理成可翻页的数据表、图表与关系图谱,
           并给出分析结论。所有取数过程都可展开核对。
         </p>
 
-        <ol class="ai-chat__steps">
-          <li v-for="(item, idx) in EXAMPLES" :key="item.text" class="ai-chat__step">
-            <span class="ai-chat__step-index">{{ idx + 1 }}</span>
-            <div class="ai-chat__step-body">
-              <div class="ai-chat__step-title">{{ item.title }}</div>
-              <button class="ai-chat__step-prompt" type="button" @click="fill(item.text)">
-                {{ item.text }}
-              </button>
+        <div class="ai-chat__examples">
+          <button
+            v-for="(item, idx) in EXAMPLES"
+            :key="item.text"
+            class="ai-chat__example"
+            type="button"
+            @click="fill(item.text)"
+          >
+            <div class="ai-chat__example-top">
+              <span class="ai-chat__example-index">{{ String(idx + 1).padStart(2, '0') }}</span>
+              <span class="ai-chat__example-title">{{ item.title }}</span>
+              <Icon class="ai-chat__example-arrow" icon="mdi:arrow-right" :size="15" />
             </div>
-          </li>
-        </ol>
+            <p class="ai-chat__example-text">{{ item.text }}</p>
+          </button>
+        </div>
       </div>
     </div>
 
@@ -103,6 +130,8 @@
       hasConversation?: boolean;
       /** 未打开案件时禁用输入与消息区 */
       disabled?: boolean;
+      /** 是否显示对话头(工作台内使用;传统分析页已有顶部条,保持默认关闭) */
+      showHeader?: boolean;
       streaming?: boolean;
       models?: AiModel[];
       /** 模型ID(雪花 ID 字符串,禁止转 number) */
@@ -114,6 +143,7 @@
       title: '',
       hasConversation: false,
       disabled: false,
+      showHeader: false,
       streaming: false,
       models: () => [],
       modelId: null,
@@ -254,6 +284,112 @@
 </script>
 
 <style lang="less" scoped>
+  /* 聊天外壳:与插件/模型页一致的白色卡片,靠 8/10px 外边距露出页面灰底 */
+  .ai-main.ai-chat-shell {
+    --ai-avatar: 30px;
+
+    margin: 8px 10px;
+    background: #fff;
+    border: 1px solid #e7ebf3;
+    border-radius: 14px;
+    box-shadow: 0 1px 2px rgb(16 24 40 / 4%);
+    overflow: hidden;
+  }
+
+  /* 对话头(工作台内):内容与消息列同宽居中,左右边缘严格对齐 */
+  .ai-chat__head {
+    flex: 0 0 auto;
+    min-height: 56px;
+    padding: 8px 0;
+    background: #fff;
+    border-bottom: 1px solid #eff2f7;
+    box-sizing: border-box;
+  }
+
+  .ai-chat__head-inner {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    width: 100%;
+    max-width: var(--ai-content-max);
+    margin: 0 auto;
+    padding: 0 24px;
+    box-sizing: border-box;
+  }
+
+  .ai-chat__head-icon {
+    display: inline-flex;
+    flex: 0 0 auto;
+    align-items: center;
+    justify-content: center;
+    width: 34px;
+    height: 34px;
+    color: #2a50ec;
+    background: rgb(42 80 236 / 8%);
+    border-radius: 9px;
+  }
+
+  .ai-chat__head-main {
+    display: flex;
+    flex: 1 1 auto;
+    flex-direction: column;
+    gap: 2px;
+    min-width: 0;
+  }
+
+  .ai-chat__head-title {
+    overflow: hidden;
+    color: #111827;
+    font-size: 14px;
+    font-weight: 600;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+  }
+
+  .ai-chat__head-sub {
+    overflow: hidden;
+    color: #98a2b3;
+    font-size: 11.5px;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+  }
+
+  .ai-chat__head-status {
+    display: inline-flex;
+    flex: 0 0 auto;
+    align-items: center;
+    gap: 6px;
+    height: 24px;
+    padding: 0 10px;
+    color: #2a50ec;
+    font-size: 11.5px;
+    font-weight: 500;
+    background: rgb(42 80 236 / 7%);
+    border: 1px solid rgb(42 80 236 / 16%);
+    border-radius: 999px;
+  }
+
+  .ai-chat__head-dot {
+    width: 6px;
+    height: 6px;
+    background: currentColor;
+    border-radius: 50%;
+    animation: ai-chat-pulse 1.2s ease-in-out infinite;
+  }
+
+  @keyframes ai-chat-pulse {
+    0%,
+    100% {
+      opacity: 0.35;
+      transform: scale(0.85);
+    }
+
+    50% {
+      opacity: 1;
+      transform: scale(1);
+    }
+  }
+
   .ai-chat {
     &__center {
       display: flex;
@@ -262,88 +398,140 @@
       flex: 1 1 auto;
     }
 
+    /* 无会话时内容垂直居中;内容比视口高时自动退回顶部滚动 */
+    &__welcome-scroll {
+      display: flex;
+      flex-direction: column;
+
+      > .ai-chat__welcome {
+        margin-top: auto;
+        margin-bottom: auto;
+      }
+    }
+
     &__welcome {
-      padding-top: var(--ai-sp-6);
-      padding-bottom: var(--ai-sp-6);
+      padding-top: 40px;
+      padding-bottom: 40px;
+    }
+
+    &__welcome-icon {
+      display: inline-flex;
+      align-items: center;
+      justify-content: center;
+      width: 44px;
+      height: 44px;
+      margin-bottom: 18px;
+      color: #2a50ec;
+      background: linear-gradient(135deg, #eef2ff, #dfe8ff);
+      border: 1px solid #dbe4ff;
+      border-radius: 12px;
     }
 
     &__welcome-title {
-      margin: 0 0 var(--ai-sp-2);
-      font-size: 20px;
+      margin: 0 0 8px;
+      color: #111827;
+      font-size: 22px;
       font-weight: 600;
       line-height: 1.4;
-      color: var(--ai-text);
     }
 
     &__welcome-desc {
-      max-width: 620px;
-      margin: 0 0 var(--ai-sp-5);
+      max-width: 680px;
+      margin: 0 0 24px;
+      color: #5b6577;
       font-size: 13.5px;
       line-height: 1.8;
-      color: var(--ai-text-2);
     }
 
-    /* 用法引导:编号列表 + 可点击示例,比等宽卡片网格更省空间也更像"操作指引" */
-    &__steps {
+    /* 示例问题:2×2 卡片,点击填入输入框(只填不发) */
+    &__examples {
+      display: grid;
+      grid-template-columns: repeat(2, minmax(0, 1fr));
+      gap: 10px;
+    }
+
+    &__example {
       display: flex;
       flex-direction: column;
-      gap: 0;
-      margin: 0;
-      padding: 0;
-      list-style: none;
-      border-top: 1px solid var(--ai-border);
+      gap: 8px;
+      min-width: 0;
+      padding: 14px;
+      font-family: inherit;
+      text-align: left;
+      background: #fafbfe;
+      border: 1px solid #e7ebf3;
+      border-radius: 12px;
+      cursor: pointer;
+      transition:
+        background-color 0.15s ease,
+        border-color 0.15s ease,
+        box-shadow 0.15s ease,
+        transform 0.15s ease;
+
+      &:hover {
+        background: #fff;
+        border-color: #bfcdfb;
+        box-shadow: 0 4px 14px rgb(16 24 40 / 8%);
+        transform: translateY(-1px);
+      }
     }
 
-    &__step {
+    &__example-top {
       display: flex;
-      align-items: flex-start;
-      gap: var(--ai-sp-3);
-      padding: var(--ai-sp-3) 0;
-      border-bottom: 1px solid var(--ai-border);
+      align-items: center;
+      gap: 8px;
+      min-width: 0;
     }
 
-    &__step-index {
+    &__example-index {
       flex: 0 0 auto;
       display: inline-flex;
       align-items: center;
       justify-content: center;
-      width: 22px;
-      height: 22px;
-      margin-top: 1px;
-      font-size: 11px;
-      font-weight: 600;
-      color: var(--ai-primary);
-      background: var(--ai-primary-soft);
-      border-radius: 50%;
+      height: 20px;
+      padding: 0 6px;
+      color: #2a50ec;
+      font-size: 10.5px;
+      font-weight: 700;
+      letter-spacing: 0.04em;
+      font-variant-numeric: tabular-nums;
+      background: rgb(42 80 236 / 8%);
+      border-radius: 5px;
     }
 
-    &__step-body {
+    &__example-title {
       flex: 1 1 auto;
       min-width: 0;
-    }
-
-    &__step-title {
+      overflow: hidden;
+      color: #111827;
       font-size: 13px;
       font-weight: 600;
-      color: var(--ai-text);
+      white-space: nowrap;
+      text-overflow: ellipsis;
     }
 
-    &__step-prompt {
-      display: block;
-      margin-top: var(--ai-sp-1);
-      padding: 0;
-      font-size: 13px;
-      line-height: 1.7;
-      text-align: left;
-      color: var(--ai-text-2);
-      background: none;
-      border: 0;
-      cursor: pointer;
-      transition: color 0.15s ease-out;
+    &__example-arrow {
+      flex: 0 0 auto;
+      color: #98a2b3;
+      transition:
+        color 0.15s ease,
+        transform 0.15s ease;
+    }
 
-      &:hover {
-        color: var(--ai-primary);
-      }
+    &__example:hover &__example-arrow {
+      color: #2a50ec;
+      transform: translateX(2px);
+    }
+
+    &__example-text {
+      display: -webkit-box;
+      margin: 0;
+      overflow: hidden;
+      color: #5b6577;
+      font-size: 12.5px;
+      line-height: 1.7;
+      -webkit-box-orient: vertical;
+      -webkit-line-clamp: 2;
     }
 
     /* 对话区:滚动区 + 悬浮按钮的定位父级 */
@@ -393,4 +581,11 @@
       padding-bottom: var(--ai-sp-6);
     }
   }
+
+  /* 右侧产物栏展开时聊天列会明显变窄,示例卡提前退成单列 */
+  @media (max-width: 1180px) {
+    .ai-chat__examples {
+      grid-template-columns: 1fr;
+    }
+  }
 </style>

+ 36 - 15
ai-electron/frontend/src/ai/components/SessionList.vue

@@ -254,7 +254,24 @@
       flex-direction: column;
       gap: var(--ai-sp-2);
       padding: var(--ai-sp-3);
-      border-bottom: 1px solid var(--ai-border);
+      border-bottom: 1px solid #e7ebf3;
+    }
+
+    /* 搜索框与自主模式侧栏的控件描边/圆角一致 */
+    &__search.ant-input-affix-wrapper {
+      background: #fff;
+      border: 1px solid #e7ebf3;
+      border-radius: 8px;
+      box-shadow: none;
+
+      &:hover {
+        border-color: #bfcdfb;
+      }
+
+      &.ant-input-affix-wrapper-focused {
+        border-color: #2a50ec;
+        box-shadow: 0 0 0 2px rgb(42 80 236 / 12%);
+      }
     }
 
     &__list {
@@ -276,11 +293,11 @@
 
     &__group-title {
       padding: var(--ai-sp-1) var(--ai-sp-2);
+      color: #98a2b3;
       font-size: 11px;
       font-weight: 600;
       letter-spacing: 0.06em;
       text-transform: uppercase;
-      color: var(--ai-text-2);
     }
   }
 </style>
@@ -297,7 +314,11 @@
     transition: background 0.15s ease-out;
 
     &:hover {
-      background: var(--ai-bg-hover);
+      background: #f1f5f9;
+
+      .ai-session__title {
+        color: #111827;
+      }
 
       .ai-session__ops {
         opacity: 1;
@@ -305,10 +326,10 @@
     }
 
     &.is-active {
-      background: var(--ai-bg-active);
+      background: #dbe5ff;
 
       .ai-session__title {
-        color: var(--ai-primary);
+        color: #2448c9;
         font-weight: 600;
       }
     }
@@ -320,28 +341,28 @@
 
     &__title {
       display: block;
+      overflow: hidden;
+      color: #1f2937;
       font-size: 13px;
       line-height: 1.5;
-      color: var(--ai-text);
-      overflow: hidden;
       white-space: nowrap;
       text-overflow: ellipsis;
     }
 
     &__meta {
+      min-height: 15px;
       margin-top: 1px;
+      overflow: hidden;
+      color: #98a2b3;
       font-size: 11px;
       line-height: 1.4;
-      color: var(--ai-text-2);
-      overflow: hidden;
       white-space: nowrap;
       text-overflow: ellipsis;
-      min-height: 15px;
     }
 
     &__ops {
-      flex: 0 0 auto;
       display: flex;
+      flex: 0 0 auto;
       align-items: center;
       gap: 2px;
       opacity: 0;
@@ -350,13 +371,13 @@
 
     &__op {
       padding: 2px;
-      color: var(--ai-text-2);
-      border-radius: var(--ai-radius-sm);
+      color: #98a2b3;
+      border-radius: 4px;
       cursor: pointer;
 
       &:hover {
-        color: var(--ai-primary);
-        background: var(--ai-primary-soft);
+        color: #2a50ec;
+        background: rgb(42 80 236 / 8%);
       }
 
       &.is-danger:hover {

+ 28 - 9
ai-electron/frontend/src/ai/views/aiWorkbench/index.vue

@@ -45,6 +45,7 @@
           ref="panelRef"
           :messages="store.messages"
           :title="activeTitle"
+          show-header
           :has-conversation="hasConversation"
           :disabled="!workspaceId"
           :streaming="store.streaming"
@@ -295,7 +296,7 @@
     min-height: 0;
   }
 
-  /* ── 左栏:菜单 + 会话 ── */
+  /* ── 左栏:菜单 + 会话(配色对齐自主模式侧栏) ── */
   .awb-rail {
     display: flex;
     flex-direction: column;
@@ -303,8 +304,8 @@
     width: 264px;
     min-height: 0;
     overflow: hidden;
-    background: var(--ai-bg-subtle);
-    border-right: 1px solid var(--ai-border);
+    background: #f8fafc;
+    border-right: 1px solid #e7ebf3;
   }
 
   .awb-rail__menu {
@@ -313,7 +314,7 @@
     gap: 2px;
     flex: 0 0 auto;
     padding: 10px 8px;
-    border-bottom: 1px solid var(--ai-border);
+    border-bottom: 1px solid #e7ebf3;
   }
 
   .awb-rm {
@@ -321,23 +322,39 @@
     align-items: center;
     gap: 9px;
     padding: 8px 10px;
+    color: #1f2937;
     font-size: 13px;
-    color: var(--ai-text-2);
     text-align: left;
     background: transparent;
     border: none;
     border-radius: 7px;
     cursor: pointer;
+    transition:
+      color 0.15s ease-out,
+      background-color 0.15s ease-out;
+
+    > .jeesite-icon {
+      color: #94a3b8;
+      transition: color 0.15s ease-out;
+    }
 
     &:hover {
-      color: var(--ai-primary);
-      background: var(--ai-bg-hover);
+      color: #111827;
+      background: #f1f5f9;
+
+      > .jeesite-icon {
+        color: #475569;
+      }
     }
 
     &.is-on {
+      color: #2448c9;
       font-weight: 600;
-      color: var(--ai-primary);
-      background: var(--ai-primary-soft);
+      background: #dbe5ff;
+
+      > .jeesite-icon {
+        color: #2a50ec;
+      }
     }
   }
 
@@ -354,6 +371,8 @@
     flex-direction: column;
     min-width: 0;
     min-height: 0;
+    /* 聊天卡片四周露出的底色与插件/模型页保持一致 */
+    background: #f4f6fa;
   }
 
   /*