Преглед изворни кода

[Feat 0000]ai助手添加待做任务列表

wangkeyi пре 2 дана
родитељ
комит
10349237f8

+ 24 - 0
src/views/ventAI/manageAssistent/components/AiAssistantModal.vue

@@ -82,6 +82,9 @@
           </div>
         </div>
 
+        <!-- 任务进度条 -->
+        <TodoListBar v-if="currentTodos.length > 0" :todos="currentTodos" />
+
         <!-- 输入区域 -->
         <ChatInputArea
           ref="chatInputRef"
@@ -137,6 +140,7 @@
   import ChatMessages from './chatModal/ChatMessages.vue';
   import ChatInputArea from './chatModal/ChatInputArea.vue';
   import FilePreviewPanel from './chatModal/FilePreviewPanel.vue';
+  import TodoListBar from './chatModal/TodoListBar.vue';
   import type { AttachedFile, Message, Task } from './chatModal/types';
 
   interface Props {
@@ -212,6 +216,24 @@
   const downloadConfirm = ref<{ url: string; filename: string } | null>(null);
   const contextBreakdown = ref({ messages: 0, mcp: 0, skills: 0, system_prompt: 0, other: 0 });
 
+  const currentTodos = ref<Array<{ content: string; status: string }>>([]);
+
+  const syncTodoBarFromMessages = () => {
+    for (let i = messages.value.length - 1; i >= 0; i--) {
+      const msg = messages.value[i];
+      if (msg.type === 'ai' && msg.thinkingSteps) {
+        for (let j = msg.thinkingSteps.length - 1; j >= 0; j--) {
+          const step = msg.thinkingSteps[j];
+          if (step.type === 'updated_todo_list' && step.todos) {
+            currentTodos.value = [...step.todos];
+            return;
+          }
+        }
+      }
+    }
+    currentTodos.value = [];
+  };
+
   const panelCollapsed = ref(false);
   const togglePanel = () => {
     panelCollapsed.value = !panelCollapsed.value;
@@ -351,6 +373,7 @@
     }
 
     messages.value = newTask.messages && newTask.messages.length > 0 ? [...newTask.messages] : getDefaultMessages();
+    syncTodoBarFromMessages();
 
     await nextTick();
     chatMessagesRef.value?.forceScrollToBottom();
@@ -929,6 +952,7 @@
             timestamp: Date.now(),
           });
         }
+        currentTodos.value = [...(data.todos || [])];
         break;
       }
 

+ 0 - 47
src/views/ventAI/manageAssistent/components/chatModal/ChatMessages.vue

@@ -80,19 +80,6 @@
                             <span v-if="step.tool" class="step-tool-name">{{ step.tool }}</span>
                           </div>
                         </div>
-                        <!-- updated_todo_list -->
-                        <div v-else-if="step.type === 'updated_todo_list'" class="step-item step-todo">
-                          <div class="step-content">
-                            <div class="todo-list">
-                              <div v-for="(todo, todoIdx) in step.todos" :key="todoIdx" class="todo-item">
-                                <span :class="['todo-status', todo.status]">
-                                  {{ todo.status === 'completed' ? '✓' : todo.status === 'in_progress' ? '●' : '○' }}
-                                </span>
-                                <span class="todo-text">{{ todo.content }}</span>
-                              </div>
-                            </div>
-                          </div>
-                        </div>
                         <!-- tool_result -->
                         <div v-else-if="step.type === 'tool_result'" class="step-item step-tool-result">
                           <div class="step-content">
@@ -742,40 +729,6 @@
                     }
                   }
                 }
-
-                /* updated_todo_list */
-                .step-todo {
-                  .todo-list {
-                    .todo-item {
-                      display: flex;
-                      align-items: flex-start;
-                      gap: 6px;
-                      padding: 2px 0;
-
-                      .todo-status {
-                        flex-shrink: 0;
-                        width: 16px;
-                        text-align: center;
-                        font-size: 12px;
-
-                        &.completed {
-                          color: #3fb950;
-                        }
-                        &.in_progress {
-                          color: #0a84ff;
-                        }
-                        &.pending {
-                          color: #8b949e;
-                        }
-                      }
-
-                      .todo-text {
-                        font-size: 13px;
-                        color: #b0b8c4;
-                      }
-                    }
-                  }
-                }
               }
             }
           }

+ 156 - 0
src/views/ventAI/manageAssistent/components/chatModal/TodoListBar.vue

@@ -0,0 +1,156 @@
+<template>
+  <div class="todo-list-bar">
+    <div class="todo-list-bar-header" @click="expanded = !expanded">
+      <div class="todo-list-bar-header-left">
+        <span class="todo-list-bar-title">任务进度</span>
+        <span class="todo-list-bar-summary">{{ completedCount }}/{{ todos.length }}</span>
+      </div>
+      <div :class="['todo-list-bar-arrow', { expanded }]">
+        <img :src="arrowIcon" width="12" height="12" />
+      </div>
+    </div>
+    <div v-show="expanded" class="todo-list-bar-body">
+      <div v-for="(todo, idx) in todos" :key="idx" :class="['todo-bar-item', todo.status]">
+        <span :class="['todo-bar-dot', todo.status]"></span>
+        <span :class="['todo-bar-text', todo.status]">{{ todo.content }}</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+  import { ref, computed, onMounted } from 'vue';
+
+  const props = defineProps<{
+    todos: Array<{ content: string; status: string }>;
+  }>();
+
+  const expanded = ref(true);
+
+  const completedCount = computed(() => props.todos.filter((t) => t.status === 'completed').length);
+
+  const getCssUrl = (varName: string) => {
+    const val = getComputedStyle(document.documentElement).getPropertyValue(varName).trim();
+    return val.replace(/^url\(["']?/, '').replace(/["']?\)$/, '');
+  };
+
+  const arrowIcon = ref('');
+  onMounted(() => {
+    arrowIcon.value = getCssUrl('--img-chat-arrow-icon');
+  });
+</script>
+
+<style scoped lang="less">
+  .todo-list-bar {
+    display: flex;
+    flex-direction: column;
+    margin: 0 16px;
+    background: #0a2a3f;
+    border: 1px solid #1a4a6f;
+    border-radius: 8px;
+    flex-shrink: 0;
+    overflow: hidden;
+
+    .todo-list-bar-header {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 10px 16px;
+      cursor: pointer;
+      user-select: none;
+      transition: background 0.2s;
+
+      &:hover {
+        background: rgba(10, 132, 255, 0.08);
+      }
+    }
+
+    .todo-list-bar-header-left {
+      display: flex;
+      align-items: center;
+      gap: 10px;
+    }
+
+    .todo-list-bar-title {
+      font-size: 13px;
+      font-weight: 500;
+      color: #e0e6ed;
+    }
+
+    .todo-list-bar-summary {
+      font-size: 12px;
+      color: #8b949e;
+      background: rgba(10, 132, 255, 0.12);
+      padding: 1px 8px;
+      border-radius: 10px;
+    }
+
+    .todo-list-bar-arrow {
+      display: flex;
+      align-items: center;
+      transition: transform 0.2s;
+
+      &.expanded {
+        transform: rotate(180deg);
+      }
+    }
+
+    .todo-list-bar-body {
+      padding: 0 16px 10px;
+      display: flex;
+      flex-direction: column;
+      gap: 2px;
+    }
+
+    .todo-bar-item {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      padding: 6px 10px;
+      border-radius: 4px;
+      transition: background 0.2s;
+
+      &:hover {
+        background: rgba(10, 132, 255, 0.06);
+      }
+
+      &.completed .todo-bar-text {
+        color: #5a6a7e;
+        text-decoration: line-through;
+      }
+    }
+
+    .todo-bar-dot {
+      width: 8px;
+      height: 8px;
+      border-radius: 50%;
+      flex-shrink: 0;
+
+      &.completed {
+        background: #3fb950;
+      }
+      &.in_progress {
+        background: #0a84ff;
+        box-shadow: 0 0 6px rgba(10, 132, 255, 0.5);
+      }
+      &.pending {
+        background: #484f5a;
+      }
+    }
+
+    .todo-bar-text {
+      font-size: 13px;
+      line-height: 1.4;
+
+      &.completed {
+        color: #5a6a7e;
+      }
+      &.in_progress {
+        color: #e0e6ed;
+      }
+      &.pending {
+        color: #8b949e;
+      }
+    }
+  }
+</style>