|
@@ -1,2304 +1,1286 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <main class="app-shell" :class="{ expanded: isExpanded }">
|
|
|
|
|
- <div class="aurora" aria-hidden="true">
|
|
|
|
|
- <span></span>
|
|
|
|
|
- <span></span>
|
|
|
|
|
- <span></span>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 初始状态:居中输入卡片 -->
|
|
|
|
|
- <div v-if="!isExpanded" class="layout layout-centered">
|
|
|
|
|
- <section class="panel panel-form panel-centered">
|
|
|
|
|
- <header class="panel-head">
|
|
|
|
|
- <div class="logo">
|
|
|
|
|
- <svg viewBox="0 0 24 24" aria-hidden="true">
|
|
|
|
|
- <path
|
|
|
|
|
- d="M12 2.5c-.7 0-1.4.2-2 .6L4.6 7C3.6 7.6 3 8.7 3 9.9v4.2c0 1.2.6 2.3 1.6 2.9l5.4 3.9c1.2.8 2.8.8 4 0l5.4-3.9c1-.7 1.6-1.7 1.6-2.9V9.9c0-1.2-.6-2.3-1.6-2.9L14 3.1a3.6 3.6 0 0 0-2-.6Z"
|
|
|
|
|
- />
|
|
|
|
|
- </svg>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div>
|
|
|
|
|
- <h1>深度研究助手</h1>
|
|
|
|
|
- <p>结合多轮智能检索与总结,实时呈现洞见与引用。</p>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <div class="deepcast-container" :class="currentView">
|
|
|
|
|
+ <div class="background-gradient"></div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 1. Setup View: 输入主题与配置 -->
|
|
|
|
|
+ <transition name="fade" mode="out-in">
|
|
|
|
|
+ <section v-if="currentView === 'setup'" class="view-setup" key="setup">
|
|
|
|
|
+ <header class="brand-header">
|
|
|
|
|
+ <div class="logo-icon">🎙️</div>
|
|
|
|
|
+ <h1>DeepCast</h1>
|
|
|
|
|
+ <p class="tagline">将深度研究转化为引人入胜的播客。</p>
|
|
|
</header>
|
|
</header>
|
|
|
|
|
|
|
|
- <form class="form" @submit.prevent="handleSubmit">
|
|
|
|
|
- <label class="field">
|
|
|
|
|
- <span>研究主题</span>
|
|
|
|
|
- <textarea
|
|
|
|
|
- v-model="form.topic"
|
|
|
|
|
- placeholder="例如:探索多模态模型在 2025 年的关键突破"
|
|
|
|
|
- rows="4"
|
|
|
|
|
|
|
+ <form @submit.prevent="startProduction" class="setup-form">
|
|
|
|
|
+ <div class="input-group">
|
|
|
|
|
+ <label>播客主题</label>
|
|
|
|
|
+ <textarea
|
|
|
|
|
+ v-model="form.topic"
|
|
|
|
|
+ placeholder="今天我们聊点什么?(例如:AI Agent 的未来)"
|
|
|
|
|
+ rows="3"
|
|
|
required
|
|
required
|
|
|
|
|
+ @keydown.enter.prevent="startProduction"
|
|
|
></textarea>
|
|
></textarea>
|
|
|
- </label>
|
|
|
|
|
-
|
|
|
|
|
- <section class="options">
|
|
|
|
|
- <label class="field option">
|
|
|
|
|
- <span>搜索引擎</span>
|
|
|
|
|
- <select v-model="form.searchApi">
|
|
|
|
|
- <option value="">沿用后端配置</option>
|
|
|
|
|
- <option
|
|
|
|
|
- v-for="option in searchOptions"
|
|
|
|
|
- :key="option"
|
|
|
|
|
- :value="option"
|
|
|
|
|
- >
|
|
|
|
|
- {{ option }}
|
|
|
|
|
- </option>
|
|
|
|
|
- </select>
|
|
|
|
|
- </label>
|
|
|
|
|
- </section>
|
|
|
|
|
-
|
|
|
|
|
- <div class="form-actions">
|
|
|
|
|
- <button class="submit" type="submit" :disabled="loading">
|
|
|
|
|
- <span class="submit-label">
|
|
|
|
|
- <svg
|
|
|
|
|
- v-if="loading"
|
|
|
|
|
- class="spinner"
|
|
|
|
|
- viewBox="0 0 24 24"
|
|
|
|
|
- aria-hidden="true"
|
|
|
|
|
- >
|
|
|
|
|
- <circle cx="12" cy="12" r="9" stroke-width="3" />
|
|
|
|
|
- </svg>
|
|
|
|
|
- {{ loading ? "研究进行中..." : "开始研究" }}
|
|
|
|
|
- </span>
|
|
|
|
|
- </button>
|
|
|
|
|
- <button
|
|
|
|
|
- v-if="loading"
|
|
|
|
|
- type="button"
|
|
|
|
|
- class="secondary-btn"
|
|
|
|
|
- @click="cancelResearch"
|
|
|
|
|
- >
|
|
|
|
|
- 取消研究
|
|
|
|
|
- </button>
|
|
|
|
|
</div>
|
|
</div>
|
|
|
- </form>
|
|
|
|
|
|
|
|
|
|
- <p v-if="error" class="error-chip">
|
|
|
|
|
- <svg viewBox="0 0 20 20" aria-hidden="true">
|
|
|
|
|
- <path
|
|
|
|
|
- d="M10 3.2c-.3 0-.6.2-.8.5L3.4 15c-.4.7.1 1.6.8 1.6h11.6c.7 0 1.2-.9.8-1.6L10.8 3.7c-.2-.3-.5-.5-.8-.5Zm0 4.3c.4 0 .7.3.7.7v4c0 .4-.3.7-.7.7s-.7-.3-.7-.7V8.2c0-.4.3-.7.7-.7Zm0 6.6a1 1 0 1 1 0 2 1 1 0 0 1 0-2Z"
|
|
|
|
|
- />
|
|
|
|
|
- </svg>
|
|
|
|
|
- {{ error }}
|
|
|
|
|
- </p>
|
|
|
|
|
- <p v-else-if="loading" class="hint muted">
|
|
|
|
|
- 正在收集线索与证据,实时进展见右侧区域。
|
|
|
|
|
- </p>
|
|
|
|
|
- </section>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 全屏状态:左右分栏布局 -->
|
|
|
|
|
- <div v-else class="layout layout-fullscreen">
|
|
|
|
|
- <!-- 左侧:研究信息 -->
|
|
|
|
|
- <aside class="sidebar">
|
|
|
|
|
- <div class="sidebar-header">
|
|
|
|
|
- <button class="back-btn" @click="goBack" :disabled="loading">
|
|
|
|
|
- <svg viewBox="0 0 24 24" width="20" height="20">
|
|
|
|
|
- <path d="M19 12H5M12 19l-7-7 7-7" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
|
|
|
|
- </svg>
|
|
|
|
|
- 返回
|
|
|
|
|
|
|
+ <div class="settings-row">
|
|
|
|
|
+ <div class="setting-item">
|
|
|
|
|
+ <label>搜索引擎</label>
|
|
|
|
|
+ <div class="select-wrapper">
|
|
|
|
|
+ <select v-model="form.searchApi">
|
|
|
|
|
+ <option value="hybrid">混合搜索 (Tavily + SerpApi)</option>
|
|
|
|
|
+ <option value="tavily">仅 Tavily</option>
|
|
|
|
|
+ <option value="serpapi">仅 SerpApi</option>
|
|
|
|
|
+ </select>
|
|
|
|
|
+ <span class="select-arrow">▼</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <button type="submit" class="cta-button" :disabled="!form.topic.trim()">
|
|
|
|
|
+ <span>开始制作播客</span>
|
|
|
|
|
+ <span class="icon">✨</span>
|
|
|
</button>
|
|
</button>
|
|
|
- <h2>🔍 深度研究助手</h2>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ </form>
|
|
|
|
|
+ </section>
|
|
|
|
|
|
|
|
- <div class="research-info">
|
|
|
|
|
- <div class="info-item">
|
|
|
|
|
- <label>研究主题</label>
|
|
|
|
|
- <p class="topic-display">{{ form.topic }}</p>
|
|
|
|
|
|
|
+ <!-- 2. Production View: 制作进度监控 -->
|
|
|
|
|
+ <section v-else-if="currentView === 'producing'" class="view-production" key="production">
|
|
|
|
|
+ <div class="production-content">
|
|
|
|
|
+ <header class="production-header">
|
|
|
|
|
+ <h2>正在制作您的播客</h2>
|
|
|
|
|
+ <button class="cancel-btn" @click="cancelProduction">取消</button>
|
|
|
|
|
+ </header>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="stage-monitor">
|
|
|
|
|
+ <div class="stage-step" :class="{ active: productionStage === 'research', completed: isStageCompleted('research') }">
|
|
|
|
|
+ <div class="step-icon">🔍</div>
|
|
|
|
|
+ <div class="step-label">深度研究</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="stage-line"></div>
|
|
|
|
|
+ <div class="stage-step" :class="{ active: productionStage === 'script', completed: isStageCompleted('script') }">
|
|
|
|
|
+ <div class="step-icon">📝</div>
|
|
|
|
|
+ <div class="step-label">剧本创作</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="stage-line"></div>
|
|
|
|
|
+ <div class="stage-step" :class="{ active: productionStage === 'audio', completed: isStageCompleted('audio') }">
|
|
|
|
|
+ <div class="step-icon">🎧</div>
|
|
|
|
|
+ <div class="step-label">音频合成</div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div class="info-item" v-if="form.searchApi">
|
|
|
|
|
- <label>搜索引擎</label>
|
|
|
|
|
- <p>{{ form.searchApi }}</p>
|
|
|
|
|
|
|
+ <div class="terminal-log" v-if="logs.length > 0">
|
|
|
|
|
+ <div class="log-content" ref="logContainer">
|
|
|
|
|
+ <div v-for="(log, i) in logs" :key="i" class="log-entry">
|
|
|
|
|
+ <span class="log-time">{{ log.time }}</span>
|
|
|
|
|
+ <span class="log-msg">{{ log.message }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div class="info-item" v-if="totalTasks > 0">
|
|
|
|
|
- <label>研究进度</label>
|
|
|
|
|
- <div class="progress-bar">
|
|
|
|
|
- <div class="progress-fill" :style="{ width: `${(completedTasks / totalTasks) * 100}%` }"></div>
|
|
|
|
|
|
|
+ <div class="todo-list-container" v-if="todoList.length > 0">
|
|
|
|
|
+ <h3>📋 研究任务清单</h3>
|
|
|
|
|
+ <div class="todo-items">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="task in todoList"
|
|
|
|
|
+ :key="task.id"
|
|
|
|
|
+ class="todo-item"
|
|
|
|
|
+ :class="task.status"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="task-status-icon">
|
|
|
|
|
+ <span v-if="task.status === 'pending'">⏳</span>
|
|
|
|
|
+ <span v-else-if="task.status === 'in_progress'">🔄</span>
|
|
|
|
|
+ <span v-else-if="task.status === 'completed'">✅</span>
|
|
|
|
|
+ <span v-else-if="task.status === 'skipped'">⏭️</span>
|
|
|
|
|
+ <span v-else-if="task.status === 'failed'">❌</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="task-content">
|
|
|
|
|
+ <div class="task-header">
|
|
|
|
|
+ <span class="task-title">{{ task.title }}</span>
|
|
|
|
|
+ <span class="task-intent">{{ task.intent }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="task-summary" v-if="task.summary" v-html="md.render(task.summary)"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- <p class="progress-text">{{ completedTasks }} / {{ totalTasks }} 任务完成</p>
|
|
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ </section>
|
|
|
|
|
|
|
|
- <div class="sidebar-actions">
|
|
|
|
|
- <button class="new-research-btn" @click="startNewResearch">
|
|
|
|
|
- <svg viewBox="0 0 24 24" width="18" height="18">
|
|
|
|
|
- <path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>
|
|
|
|
|
- </svg>
|
|
|
|
|
- 开始新研究
|
|
|
|
|
- </button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </aside>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 右侧:研究结果 -->
|
|
|
|
|
- <section
|
|
|
|
|
- class="panel panel-result"
|
|
|
|
|
- v-if="todoTasks.length || reportMarkdown || progressLogs.length"
|
|
|
|
|
- >
|
|
|
|
|
- <header class="status-bar">
|
|
|
|
|
- <div class="status-main">
|
|
|
|
|
- <div class="status-chip" :class="{ active: loading }">
|
|
|
|
|
- <span class="dot"></span>
|
|
|
|
|
- {{ loading ? "研究进行中" : "研究流程完成" }}
|
|
|
|
|
- </div>
|
|
|
|
|
- <span class="status-meta">
|
|
|
|
|
- 任务进度:{{ completedTasks }} / {{ totalTasks || todoTasks.length || 1 }}
|
|
|
|
|
- · 阶段记录 {{ progressLogs.length }} 条
|
|
|
|
|
- </span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="status-controls">
|
|
|
|
|
- <button class="secondary-btn" @click="logsCollapsed = !logsCollapsed">
|
|
|
|
|
- {{ logsCollapsed ? "展开流程" : "收起流程" }}
|
|
|
|
|
|
|
+ <!-- 3. Player View: 播放器与脚本 -->
|
|
|
|
|
+ <section v-else-if="currentView === 'player'" class="view-player" key="player">
|
|
|
|
|
+ <div class="player-layout">
|
|
|
|
|
+ <!-- Left: Player Control -->
|
|
|
|
|
+ <div class="player-sidebar">
|
|
|
|
|
+ <button class="back-home-btn" @click="resetApp">
|
|
|
|
|
+ ← 制作新播客
|
|
|
</button>
|
|
</button>
|
|
|
- </div>
|
|
|
|
|
- </header>
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <div class="album-art">
|
|
|
|
|
+ <div class="vinyl-record" :class="{ spinning: isPlaying }">
|
|
|
|
|
+ <div class="vinyl-label">DC</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
- <div class="timeline-wrapper" v-show="!logsCollapsed && progressLogs.length">
|
|
|
|
|
- <transition-group name="timeline" tag="ul" class="timeline">
|
|
|
|
|
- <li v-for="(log, index) in progressLogs" :key="`${log}-${index}`">
|
|
|
|
|
- <span class="timeline-node"></span>
|
|
|
|
|
- <p>{{ log }}</p>
|
|
|
|
|
- </li>
|
|
|
|
|
- </transition-group>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <div class="track-info">
|
|
|
|
|
+ <h3>{{ form.topic }}</h3>
|
|
|
|
|
+ <p>DeepCast 原创播客</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
- <div class="tasks-section" v-if="todoTasks.length">
|
|
|
|
|
- <aside class="tasks-list">
|
|
|
|
|
- <h3>任务清单</h3>
|
|
|
|
|
- <ul>
|
|
|
|
|
- <li
|
|
|
|
|
- v-for="task in todoTasks"
|
|
|
|
|
- :key="task.id"
|
|
|
|
|
- :class="['task-item', { active: task.id === activeTaskId, completed: task.status === 'completed' }]"
|
|
|
|
|
- >
|
|
|
|
|
- <button
|
|
|
|
|
- type="button"
|
|
|
|
|
- class="task-button"
|
|
|
|
|
- @click="activeTaskId = task.id"
|
|
|
|
|
- >
|
|
|
|
|
- <span class="task-title">{{ task.title }}</span>
|
|
|
|
|
- <span class="task-status" :class="task.status">
|
|
|
|
|
- {{ formatTaskStatus(task.status) }}
|
|
|
|
|
- </span>
|
|
|
|
|
|
|
+ <div class="audio-controls">
|
|
|
|
|
+ <audio
|
|
|
|
|
+ ref="audioPlayer"
|
|
|
|
|
+ :src="audioUrl"
|
|
|
|
|
+ @timeupdate="onTimeUpdate"
|
|
|
|
|
+ @ended="isPlaying = false"
|
|
|
|
|
+ @play="isPlaying = true"
|
|
|
|
|
+ @pause="isPlaying = false"
|
|
|
|
|
+ ></audio>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="control-buttons">
|
|
|
|
|
+ <button class="play-btn" @click="togglePlay">
|
|
|
|
|
+ {{ isPlaying ? '⏸' : '▶' }}
|
|
|
</button>
|
|
</button>
|
|
|
- <p class="task-intent">{{ task.intent }}</p>
|
|
|
|
|
- </li>
|
|
|
|
|
- </ul>
|
|
|
|
|
- </aside>
|
|
|
|
|
-
|
|
|
|
|
- <article class="task-detail" v-if="currentTask">
|
|
|
|
|
- <header class="task-header">
|
|
|
|
|
- <div>
|
|
|
|
|
- <h3>{{ currentTaskTitle || "当前任务" }}</h3>
|
|
|
|
|
- <p class="muted" v-if="currentTaskIntent">
|
|
|
|
|
- {{ currentTaskIntent }}
|
|
|
|
|
- </p>
|
|
|
|
|
|
|
+ <a :href="audioUrl" download class="download-btn" title="下载 MP3">
|
|
|
|
|
+ ⬇
|
|
|
|
|
+ </a>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="task-chip-group">
|
|
|
|
|
- <span class="task-label">查询:{{ currentTaskQuery || "" }}</span>
|
|
|
|
|
- <span
|
|
|
|
|
- v-if="currentTaskNoteId"
|
|
|
|
|
- class="task-label note-chip"
|
|
|
|
|
- :title="currentTaskNoteId"
|
|
|
|
|
- >
|
|
|
|
|
- 笔记:{{ currentTaskNoteId }}
|
|
|
|
|
- </span>
|
|
|
|
|
- <span
|
|
|
|
|
- v-if="currentTaskNotePath"
|
|
|
|
|
- class="task-label note-chip path-chip"
|
|
|
|
|
- :title="currentTaskNotePath"
|
|
|
|
|
- >
|
|
|
|
|
- <span class="path-label">路径:</span>
|
|
|
|
|
- <span class="path-text">{{ currentTaskNotePath }}</span>
|
|
|
|
|
- <button
|
|
|
|
|
- class="chip-action"
|
|
|
|
|
- type="button"
|
|
|
|
|
- @click="copyNotePath(currentTaskNotePath)"
|
|
|
|
|
- >
|
|
|
|
|
- 复制
|
|
|
|
|
- </button>
|
|
|
|
|
- </span>
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <div class="progress-bar-wrapper" @click="seekAudio">
|
|
|
|
|
+ <div class="progress-bar-bg">
|
|
|
|
|
+ <div class="progress-bar-fill" :style="{ width: progressPercent + '%' }"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="time-display">{{ formatTime(currentTime) }} / {{ formatTime(duration) }}</div>
|
|
|
</div>
|
|
</div>
|
|
|
- </header>
|
|
|
|
|
-
|
|
|
|
|
- <section v-if="currentTask && currentTask.notices.length" class="task-notices">
|
|
|
|
|
- <h4>系统提示</h4>
|
|
|
|
|
- <ul>
|
|
|
|
|
- <li v-for="(notice, idx) in currentTask.notices" :key="`${notice}-${idx}`">
|
|
|
|
|
- {{ notice }}
|
|
|
|
|
- </li>
|
|
|
|
|
- </ul>
|
|
|
|
|
- </section>
|
|
|
|
|
-
|
|
|
|
|
- <section
|
|
|
|
|
- class="sources-block"
|
|
|
|
|
- :class="{ 'block-highlight': sourcesHighlight }"
|
|
|
|
|
- >
|
|
|
|
|
- <h3>最新来源</h3>
|
|
|
|
|
- <template v-if="currentTaskSources.length">
|
|
|
|
|
- <ul class="sources-list">
|
|
|
|
|
- <li
|
|
|
|
|
- v-for="(item, index) in currentTaskSources"
|
|
|
|
|
- :key="`${item.title}-${index}`"
|
|
|
|
|
- class="source-item"
|
|
|
|
|
- >
|
|
|
|
|
- <a
|
|
|
|
|
- class="source-link"
|
|
|
|
|
- :href="item.url || '#'"
|
|
|
|
|
- target="_blank"
|
|
|
|
|
- rel="noopener noreferrer"
|
|
|
|
|
- >
|
|
|
|
|
- {{ item.title || item.url || `来源 ${index + 1}` }}
|
|
|
|
|
- </a>
|
|
|
|
|
- <div v-if="item.snippet || item.raw" class="source-tooltip">
|
|
|
|
|
- <p v-if="item.snippet">{{ item.snippet }}</p>
|
|
|
|
|
- <p v-if="item.raw" class="muted-text">{{ item.raw }}</p>
|
|
|
|
|
- </div>
|
|
|
|
|
- </li>
|
|
|
|
|
- </ul>
|
|
|
|
|
- </template>
|
|
|
|
|
- <p v-else class="muted">暂无可用来源</p>
|
|
|
|
|
- </section>
|
|
|
|
|
-
|
|
|
|
|
- <section
|
|
|
|
|
- class="summary-block"
|
|
|
|
|
- :class="{ 'block-highlight': summaryHighlight }"
|
|
|
|
|
- >
|
|
|
|
|
- <h3>任务总结</h3>
|
|
|
|
|
- <pre class="block-pre">{{ currentTaskSummary || "暂无可用信息" }}</pre>
|
|
|
|
|
- </section>
|
|
|
|
|
-
|
|
|
|
|
- <section
|
|
|
|
|
- class="tools-block"
|
|
|
|
|
- :class="{ 'block-highlight': toolHighlight }"
|
|
|
|
|
- v-if="currentTaskToolCalls.length"
|
|
|
|
|
- >
|
|
|
|
|
- <h3>工具调用记录</h3>
|
|
|
|
|
- <ul class="tool-list">
|
|
|
|
|
- <li
|
|
|
|
|
- v-for="entry in currentTaskToolCalls"
|
|
|
|
|
- :key="`${entry.eventId}-${entry.timestamp}`"
|
|
|
|
|
- class="tool-entry"
|
|
|
|
|
- >
|
|
|
|
|
- <div class="tool-entry-header">
|
|
|
|
|
- <span class="tool-entry-title">
|
|
|
|
|
- #{{ entry.eventId }} {{ entry.agent }} → {{ entry.tool }}
|
|
|
|
|
- </span>
|
|
|
|
|
- <span
|
|
|
|
|
- v-if="entry.noteId"
|
|
|
|
|
- class="tool-entry-note"
|
|
|
|
|
- >
|
|
|
|
|
- 笔记:{{ entry.noteId }}
|
|
|
|
|
- </span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <p v-if="entry.notePath" class="tool-entry-path">
|
|
|
|
|
- 笔记路径:
|
|
|
|
|
- <button
|
|
|
|
|
- class="link-btn"
|
|
|
|
|
- type="button"
|
|
|
|
|
- @click="copyNotePath(entry.notePath)"
|
|
|
|
|
- >
|
|
|
|
|
- 复制
|
|
|
|
|
- </button>
|
|
|
|
|
- <span class="path-text">{{ entry.notePath }}</span>
|
|
|
|
|
- </p>
|
|
|
|
|
- <p class="tool-subtitle">参数</p>
|
|
|
|
|
- <pre class="tool-pre">{{ formatToolParameters(entry.parameters) }}</pre>
|
|
|
|
|
- <template v-if="entry.result">
|
|
|
|
|
- <p class="tool-subtitle">执行结果</p>
|
|
|
|
|
- <pre class="tool-pre">{{ formatToolResult(entry.result) }}</pre>
|
|
|
|
|
- </template>
|
|
|
|
|
- </li>
|
|
|
|
|
- </ul>
|
|
|
|
|
- </section>
|
|
|
|
|
- </article>
|
|
|
|
|
-
|
|
|
|
|
- <article class="task-detail" v-else>
|
|
|
|
|
- <p class="muted">等待任务规划或执行结果。</p>
|
|
|
|
|
- </article>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="report-toggle">
|
|
|
|
|
+ <button @click="showReport = !showReport">
|
|
|
|
|
+ {{ showReport ? '隐藏深度研究报告' : '查看深度研究报告' }}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
- <div
|
|
|
|
|
- v-if="reportMarkdown"
|
|
|
|
|
- class="report-block"
|
|
|
|
|
- :class="{ 'block-highlight': reportHighlight }"
|
|
|
|
|
- >
|
|
|
|
|
- <h3>最终报告</h3>
|
|
|
|
|
- <pre class="block-pre">{{ reportMarkdown }}</pre>
|
|
|
|
|
|
|
+ <!-- Right: Script / Report -->
|
|
|
|
|
+ <div class="content-main">
|
|
|
|
|
+ <div v-if="!showReport" class="script-chat">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(line, idx) in podcastScript"
|
|
|
|
|
+ :key="idx"
|
|
|
|
|
+ class="chat-bubble"
|
|
|
|
|
+ :class="line.role.toLowerCase()"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="avatar">{{ line.role[0] }}</div>
|
|
|
|
|
+ <div class="bubble-content">
|
|
|
|
|
+ <div class="speaker-name">{{ line.role }}</div>
|
|
|
|
|
+ <p>{{ line.content }}</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div v-else class="markdown-report">
|
|
|
|
|
+ <div class="report-content" v-html="md.render(reportMarkdown)"></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</section>
|
|
</section>
|
|
|
-
|
|
|
|
|
- </div>
|
|
|
|
|
- </main>
|
|
|
|
|
|
|
+ </transition>
|
|
|
|
|
+ </div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script lang="ts" setup>
|
|
<script lang="ts" setup>
|
|
|
-import { computed, onBeforeUnmount, reactive, ref } from "vue";
|
|
|
|
|
-
|
|
|
|
|
-import {
|
|
|
|
|
- runResearchStream,
|
|
|
|
|
- type ResearchStreamEvent
|
|
|
|
|
-} from "./services/api";
|
|
|
|
|
-
|
|
|
|
|
-interface SourceItem {
|
|
|
|
|
- title: string;
|
|
|
|
|
- url: string;
|
|
|
|
|
- snippet: string;
|
|
|
|
|
- raw: string;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-interface ToolCallLog {
|
|
|
|
|
- eventId: number;
|
|
|
|
|
- agent: string;
|
|
|
|
|
- tool: string;
|
|
|
|
|
- parameters: Record<string, unknown>;
|
|
|
|
|
- result: string;
|
|
|
|
|
- noteId: string | null;
|
|
|
|
|
- notePath: string | null;
|
|
|
|
|
- timestamp: number;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-interface TodoTaskView {
|
|
|
|
|
- id: number;
|
|
|
|
|
- title: string;
|
|
|
|
|
- intent: string;
|
|
|
|
|
- query: string;
|
|
|
|
|
- status: string;
|
|
|
|
|
- summary: string;
|
|
|
|
|
- sourcesSummary: string;
|
|
|
|
|
- sourceItems: SourceItem[];
|
|
|
|
|
- notices: string[];
|
|
|
|
|
- noteId: string | null;
|
|
|
|
|
- notePath: string | null;
|
|
|
|
|
- toolCalls: ToolCallLog[];
|
|
|
|
|
|
|
+import { reactive, ref, computed, nextTick, watch } from "vue";
|
|
|
|
|
+import { runResearchStream, type ResearchStreamEvent } from "./services/api";
|
|
|
|
|
+
|
|
|
|
|
+// --- Types ---
|
|
|
|
|
+type ViewState = "setup" | "producing" | "player";
|
|
|
|
|
+type ProductionStage = "research" | "script" | "audio" | "done";
|
|
|
|
|
+
|
|
|
|
|
+interface LogEntry {
|
|
|
|
|
+ time: string;
|
|
|
|
|
+ message: string;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+interface PodcastMessage {
|
|
|
|
|
+ role: string;
|
|
|
|
|
+ content: string;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// --- State ---
|
|
|
|
|
+const currentView = ref<ViewState>("setup");
|
|
|
|
|
+const productionStage = ref<ProductionStage>("research");
|
|
|
const form = reactive({
|
|
const form = reactive({
|
|
|
topic: "",
|
|
topic: "",
|
|
|
- searchApi: ""
|
|
|
|
|
|
|
+ searchApi: "hybrid"
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
-const loading = ref(false);
|
|
|
|
|
-const error = ref("");
|
|
|
|
|
-const progressLogs = ref<string[]>([]);
|
|
|
|
|
-const logsCollapsed = ref(false);
|
|
|
|
|
-const isExpanded = ref(false);
|
|
|
|
|
|
|
+const logs = ref<LogEntry[]>([]);
|
|
|
|
|
+const isPlaying = ref(false);
|
|
|
|
|
+const currentTime = ref(0);
|
|
|
|
|
+const duration = ref(0);
|
|
|
|
|
+const progressPercent = computed(() => (duration.value ? (currentTime.value / duration.value) * 100 : 0));
|
|
|
|
|
+const showReport = ref(false);
|
|
|
|
|
+
|
|
|
|
|
+// Research Progress State
|
|
|
|
|
+const totalTasks = ref(0);
|
|
|
|
|
+const completedTasks = ref(0);
|
|
|
|
|
+const todoList = ref<any[]>([]); // Store the full todo list
|
|
|
|
|
+const researchProgress = computed(() => {
|
|
|
|
|
+ if (totalTasks.value === 0) return "";
|
|
|
|
|
+ return `(${completedTasks.value}/${totalTasks.value})`;
|
|
|
|
|
+});
|
|
|
|
|
|
|
|
-const todoTasks = ref<TodoTaskView[]>([]);
|
|
|
|
|
-const activeTaskId = ref<number | null>(null);
|
|
|
|
|
|
|
+// Data
|
|
|
|
|
+const podcastScript = ref<PodcastMessage[]>([]);
|
|
|
const reportMarkdown = ref("");
|
|
const reportMarkdown = ref("");
|
|
|
|
|
+const audioUrl = ref("");
|
|
|
|
|
+const currentTask = ref<any>(null); // 简化的任务状态
|
|
|
|
|
|
|
|
-const summaryHighlight = ref(false);
|
|
|
|
|
-const sourcesHighlight = ref(false);
|
|
|
|
|
-const reportHighlight = ref(false);
|
|
|
|
|
-const toolHighlight = ref(false);
|
|
|
|
|
-
|
|
|
|
|
-let currentController: AbortController | null = null;
|
|
|
|
|
|
|
+// Refs
|
|
|
|
|
+const audioPlayer = ref<HTMLAudioElement | null>(null);
|
|
|
|
|
+const logContainer = ref<HTMLElement | null>(null);
|
|
|
|
|
+let abortController: AbortController | null = null;
|
|
|
|
|
|
|
|
-const searchOptions = [
|
|
|
|
|
- "advanced",
|
|
|
|
|
- "duckduckgo",
|
|
|
|
|
- "tavily",
|
|
|
|
|
- "perplexity",
|
|
|
|
|
- "searxng"
|
|
|
|
|
-];
|
|
|
|
|
|
|
+// --- Computed ---
|
|
|
|
|
|
|
|
-const TASK_STATUS_LABEL: Record<string, string> = {
|
|
|
|
|
- pending: "待执行",
|
|
|
|
|
- in_progress: "进行中",
|
|
|
|
|
- completed: "已完成",
|
|
|
|
|
- skipped: "已跳过"
|
|
|
|
|
-};
|
|
|
|
|
|
|
+// --- Methods ---
|
|
|
|
|
|
|
|
-function formatTaskStatus(status: string): string {
|
|
|
|
|
- return TASK_STATUS_LABEL[status] ?? status;
|
|
|
|
|
|
|
+function isStageCompleted(stage: ProductionStage): boolean {
|
|
|
|
|
+ const stages: ProductionStage[] = ["research", "script", "audio", "done"];
|
|
|
|
|
+ return stages.indexOf(productionStage.value) > stages.indexOf(stage);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const totalTasks = computed(() => todoTasks.value.length);
|
|
|
|
|
-const completedTasks = computed(() =>
|
|
|
|
|
- todoTasks.value.filter((task) => task.status === "completed").length
|
|
|
|
|
-);
|
|
|
|
|
-
|
|
|
|
|
-const currentTask = computed(() => {
|
|
|
|
|
- if (activeTaskId.value !== null) {
|
|
|
|
|
- return todoTasks.value.find((task) => task.id === activeTaskId.value) ?? null;
|
|
|
|
|
- }
|
|
|
|
|
- return todoTasks.value[0] ?? null;
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-const currentTaskSources = computed(() => currentTask.value?.sourceItems ?? []);
|
|
|
|
|
-const currentTaskSummary = computed(() => currentTask.value?.summary ?? "");
|
|
|
|
|
-const currentTaskTitle = computed(() => currentTask.value?.title ?? "");
|
|
|
|
|
-const currentTaskIntent = computed(() => currentTask.value?.intent ?? "");
|
|
|
|
|
-const currentTaskQuery = computed(() => currentTask.value?.query ?? "");
|
|
|
|
|
-const currentTaskNoteId = computed(() => currentTask.value?.noteId ?? "");
|
|
|
|
|
-const currentTaskNotePath = computed(() => currentTask.value?.notePath ?? "");
|
|
|
|
|
-const currentTaskToolCalls = computed(
|
|
|
|
|
- () => currentTask.value?.toolCalls ?? []
|
|
|
|
|
-);
|
|
|
|
|
-
|
|
|
|
|
-const pulse = (flag: typeof summaryHighlight) => {
|
|
|
|
|
- flag.value = false;
|
|
|
|
|
- requestAnimationFrame(() => {
|
|
|
|
|
- flag.value = true;
|
|
|
|
|
- window.setTimeout(() => {
|
|
|
|
|
- flag.value = false;
|
|
|
|
|
- }, 1200);
|
|
|
|
|
- });
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-function parseSources(raw: string): SourceItem[] {
|
|
|
|
|
- if (!raw) {
|
|
|
|
|
- return [];
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const items: SourceItem[] = [];
|
|
|
|
|
- const lines = raw.split("\n");
|
|
|
|
|
-
|
|
|
|
|
- let current: SourceItem | null = null;
|
|
|
|
|
- const truncate = (value: string, max = 360) => {
|
|
|
|
|
- const trimmed = value.trim();
|
|
|
|
|
- return trimmed.length > max ? `${trimmed.slice(0, max)}…` : trimmed;
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const flush = () => {
|
|
|
|
|
- if (!current) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- const normalized: SourceItem = {
|
|
|
|
|
- title: current.title?.trim() || "",
|
|
|
|
|
- url: current.url?.trim() || "",
|
|
|
|
|
- snippet: current.snippet ? truncate(current.snippet) : "",
|
|
|
|
|
- raw: current.raw ? truncate(current.raw, 420) : ""
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- if (
|
|
|
|
|
- normalized.title ||
|
|
|
|
|
- normalized.url ||
|
|
|
|
|
- normalized.snippet ||
|
|
|
|
|
- normalized.raw
|
|
|
|
|
- ) {
|
|
|
|
|
- if (!normalized.title && normalized.url) {
|
|
|
|
|
- normalized.title = normalized.url;
|
|
|
|
|
- }
|
|
|
|
|
- items.push(normalized);
|
|
|
|
|
- }
|
|
|
|
|
- current = null;
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const ensureCurrent = () => {
|
|
|
|
|
- if (!current) {
|
|
|
|
|
- current = { title: "", url: "", snippet: "", raw: "" };
|
|
|
|
|
- }
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- for (const line of lines) {
|
|
|
|
|
- const trimmed = line.trim();
|
|
|
|
|
- if (!trimmed) {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (/^\*/.test(trimmed) && trimmed.includes(" : ")) {
|
|
|
|
|
- flush();
|
|
|
|
|
- const withoutBullet = trimmed.replace(/^\*\s*/, "");
|
|
|
|
|
- const [titlePart, urlPart] = withoutBullet.split(" : ");
|
|
|
|
|
- current = {
|
|
|
|
|
- title: titlePart?.trim() || "",
|
|
|
|
|
- url: urlPart?.trim() || "",
|
|
|
|
|
- snippet: "",
|
|
|
|
|
- raw: ""
|
|
|
|
|
- };
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (/^(Source|信息来源)\s*:/.test(trimmed)) {
|
|
|
|
|
- flush();
|
|
|
|
|
- const [, titlePart = ""] = trimmed.split(/:\s*(.+)/);
|
|
|
|
|
- current = {
|
|
|
|
|
- title: titlePart.trim(),
|
|
|
|
|
- url: "",
|
|
|
|
|
- snippet: "",
|
|
|
|
|
- raw: ""
|
|
|
|
|
- };
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (/^URL\s*:/.test(trimmed)) {
|
|
|
|
|
- ensureCurrent();
|
|
|
|
|
- const [, urlPart = ""] = trimmed.split(/:\s*(.+)/);
|
|
|
|
|
- current!.url = urlPart.trim();
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (
|
|
|
|
|
- /^(Most relevant content from source|信息内容)\s*:/.test(trimmed)
|
|
|
|
|
- ) {
|
|
|
|
|
- ensureCurrent();
|
|
|
|
|
- const [, contentPart = ""] = trimmed.split(/:\s*(.+)/);
|
|
|
|
|
- current!.snippet = contentPart.trim();
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (
|
|
|
|
|
- /^(Full source content limited to|信息内容限制为)\s*:/.test(trimmed)
|
|
|
|
|
- ) {
|
|
|
|
|
- ensureCurrent();
|
|
|
|
|
- const [, rawPart = ""] = trimmed.split(/:\s*(.+)/);
|
|
|
|
|
- current!.raw = rawPart.trim();
|
|
|
|
|
- continue;
|
|
|
|
|
|
|
+function addLog(message: string) {
|
|
|
|
|
+ const time = new Date().toLocaleTimeString([], { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
|
|
|
+ logs.value.push({ time, message });
|
|
|
|
|
+ nextTick(() => {
|
|
|
|
|
+ if (logContainer.value) {
|
|
|
|
|
+ logContainer.value.scrollTop = logContainer.value.scrollHeight;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- if (/^https?:\/\//.test(trimmed)) {
|
|
|
|
|
- ensureCurrent();
|
|
|
|
|
- if (!current!.url) {
|
|
|
|
|
- current!.url = trimmed;
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- ensureCurrent();
|
|
|
|
|
- current!.raw = current!.raw ? `${current!.raw}\n${trimmed}` : trimmed;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- flush();
|
|
|
|
|
- return items;
|
|
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function extractOptionalString(value: unknown): string | null {
|
|
|
|
|
- if (typeof value !== "string") {
|
|
|
|
|
- return null;
|
|
|
|
|
- }
|
|
|
|
|
- const trimmed = value.trim();
|
|
|
|
|
- return trimmed ? trimmed : null;
|
|
|
|
|
-}
|
|
|
|
|
|
|
+async function startProduction() {
|
|
|
|
|
+ if (!form.topic.trim()) return;
|
|
|
|
|
|
|
|
-function ensureRecord(value: unknown): Record<string, unknown> {
|
|
|
|
|
- if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
|
|
|
- return value as Record<string, unknown>;
|
|
|
|
|
- }
|
|
|
|
|
- return {};
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ currentView.value = "producing";
|
|
|
|
|
+ productionStage.value = "research";
|
|
|
|
|
+ logs.value = [];
|
|
|
|
|
+ podcastScript.value = [];
|
|
|
|
|
+ reportMarkdown.value = "";
|
|
|
|
|
+ audioUrl.value = "";
|
|
|
|
|
+ currentTask.value = null;
|
|
|
|
|
+ todoList.value = [];
|
|
|
|
|
+ totalTasks.value = 0;
|
|
|
|
|
+ completedTasks.value = 0;
|
|
|
|
|
|
|
|
-function applyNoteMetadata(
|
|
|
|
|
- task: TodoTaskView,
|
|
|
|
|
- payload: Record<string, unknown>
|
|
|
|
|
-): void {
|
|
|
|
|
- const noteId = extractOptionalString(payload.note_id);
|
|
|
|
|
- if (noteId) {
|
|
|
|
|
- task.noteId = noteId;
|
|
|
|
|
- }
|
|
|
|
|
- const notePath = extractOptionalString(payload.note_path);
|
|
|
|
|
- if (notePath) {
|
|
|
|
|
- task.notePath = notePath;
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
|
|
+ abortController = new AbortController();
|
|
|
|
|
+
|
|
|
|
|
+ addLog("🚀 启动 DeepCast 制作流程...");
|
|
|
|
|
+ addLog(`主题: ${form.topic}`);
|
|
|
|
|
|
|
|
-function formatToolParameters(parameters: Record<string, unknown>): string {
|
|
|
|
|
try {
|
|
try {
|
|
|
- return JSON.stringify(parameters, null, 2);
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.warn("无法格式化工具参数", error, parameters);
|
|
|
|
|
- return Object.entries(parameters)
|
|
|
|
|
- .map(([key, value]) => `${key}: ${String(value)}`)
|
|
|
|
|
- .join("\n");
|
|
|
|
|
|
|
+ await runResearchStream(
|
|
|
|
|
+ { topic: form.topic, search_api: form.searchApi },
|
|
|
|
|
+ handleStreamEvent,
|
|
|
|
|
+ { signal: abortController.signal }
|
|
|
|
|
+ );
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ if (err instanceof DOMException && err.name === "AbortError") {
|
|
|
|
|
+ addLog("🛑 制作已取消。");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ addLog(`❌ 错误: ${err}`);
|
|
|
|
|
+ alert("制作失败,请查看日志。");
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function formatToolResult(result: string): string {
|
|
|
|
|
- const trimmed = result.trim();
|
|
|
|
|
- const limit = 900;
|
|
|
|
|
- if (trimmed.length > limit) {
|
|
|
|
|
- return `${trimmed.slice(0, limit)}…`;
|
|
|
|
|
|
|
+function handleStreamEvent(event: ResearchStreamEvent) {
|
|
|
|
|
+ // 1. Tool Calls (增加执行细节)
|
|
|
|
|
+ if (event.type === "tool_call") {
|
|
|
|
|
+ const payload = event as any;
|
|
|
|
|
+ const tool = payload.tool;
|
|
|
|
|
+ const agent = payload.agent || "Agent";
|
|
|
|
|
+
|
|
|
|
|
+ // 解析具体操作
|
|
|
|
|
+ if (tool === "search") {
|
|
|
|
|
+ // 从参数中提取查询词(如果可能)
|
|
|
|
|
+ // 假设参数结构 { input: "..." }
|
|
|
|
|
+ // 由于 parameters 是 Record<string, unknown>,我们尝试转换为字符串
|
|
|
|
|
+ let query = "";
|
|
|
|
|
+ if (payload.parameters && typeof payload.parameters.input === "string") {
|
|
|
|
|
+ query = payload.parameters.input;
|
|
|
|
|
+ }
|
|
|
|
|
+ addLog(`🔍 ${agent} 正在搜索: ${query || "相关信息"}`);
|
|
|
|
|
+ } else if (tool === "note") {
|
|
|
|
|
+ const action = payload.parameters?.action;
|
|
|
|
|
+ if (action === "read") {
|
|
|
|
|
+ addLog(`📖 ${agent} 正在阅读笔记`);
|
|
|
|
|
+ } else if (action === "create" || action === "update") {
|
|
|
|
|
+ addLog(`📝 ${agent} 正在记录关键信息`);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ addLog(`🔧 ${agent} 调用了工具: ${tool}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
- return trimmed;
|
|
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
-async function copyNotePath(path: string | null | undefined) {
|
|
|
|
|
- if (!path) {
|
|
|
|
|
|
|
+ // 2. Sources (发现来源)
|
|
|
|
|
+ if (event.type === "sources") {
|
|
|
|
|
+ addLog("📚 发现新的信息来源,正在分析...");
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- try {
|
|
|
|
|
- await navigator.clipboard.writeText(path);
|
|
|
|
|
- progressLogs.value.push(`已复制笔记路径:${path}`);
|
|
|
|
|
- } catch (error) {
|
|
|
|
|
- console.warn("无法直接复制到剪贴板", error);
|
|
|
|
|
- window.prompt("复制以下笔记路径", path);
|
|
|
|
|
- progressLogs.value.push("请手动复制笔记路径");
|
|
|
|
|
|
|
+ // 3. Status Updates
|
|
|
|
|
+ if (event.type === "status") {
|
|
|
|
|
+ // 翻译或直接显示
|
|
|
|
|
+ let msg = String(event.message);
|
|
|
|
|
+ if (msg.includes("初始化")) msg = "初始化研究流程...";
|
|
|
|
|
+ if (msg.includes("脚本")) msg = "正在创作播客剧本...";
|
|
|
|
|
+ if (msg.includes("语音") || msg.includes("音频")) msg = "正在合成语音...";
|
|
|
|
|
+
|
|
|
|
|
+ // Translation for known English messages
|
|
|
|
|
+ if (msg.includes("Researching")) msg = "正在进行深度搜索...";
|
|
|
|
|
+ if (msg.includes("Generating")) msg = "正在生成内容...";
|
|
|
|
|
+ if (msg.includes("Analyzing")) msg = "正在分析数据...";
|
|
|
|
|
+
|
|
|
|
|
+ addLog(`ℹ️ ${msg}`);
|
|
|
|
|
+
|
|
|
|
|
+ if (String(event.message).includes("脚本")) productionStage.value = "script";
|
|
|
|
|
+ if (String(event.message).includes("语音") || String(event.message).includes("音频")) productionStage.value = "audio";
|
|
|
}
|
|
}
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
-function resetWorkflowState() {
|
|
|
|
|
- todoTasks.value = [];
|
|
|
|
|
- activeTaskId.value = null;
|
|
|
|
|
- reportMarkdown.value = "";
|
|
|
|
|
- progressLogs.value = [];
|
|
|
|
|
- summaryHighlight.value = false;
|
|
|
|
|
- sourcesHighlight.value = false;
|
|
|
|
|
- reportHighlight.value = false;
|
|
|
|
|
- toolHighlight.value = false;
|
|
|
|
|
- logsCollapsed.value = false;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function findTask(taskId: unknown): TodoTaskView | undefined {
|
|
|
|
|
- const numeric =
|
|
|
|
|
- typeof taskId === "number"
|
|
|
|
|
- ? taskId
|
|
|
|
|
- : typeof taskId === "string"
|
|
|
|
|
- ? Number(taskId)
|
|
|
|
|
- : NaN;
|
|
|
|
|
- if (Number.isNaN(numeric)) {
|
|
|
|
|
- return undefined;
|
|
|
|
|
|
|
+ // 3.5 Todo List (Total Tasks)
|
|
|
|
|
+ if (event.type === "todo_list") {
|
|
|
|
|
+ console.log("Received todo_list event:", event);
|
|
|
|
|
+ const payload = event as any;
|
|
|
|
|
+ if (payload.tasks && Array.isArray(payload.tasks)) {
|
|
|
|
|
+ todoList.value = payload.tasks; // Initialize list
|
|
|
|
|
+ totalTasks.value = payload.tasks.length;
|
|
|
|
|
+ addLog(`📋 规划了 ${totalTasks.value} 个研究任务`);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ console.warn("Received todo_list but tasks is empty or invalid", payload);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- return todoTasks.value.find((task) => task.id === numeric);
|
|
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
-function upsertTaskMetadata(task: TodoTaskView, payload: Record<string, unknown>) {
|
|
|
|
|
- if (typeof payload.title === "string" && payload.title.trim()) {
|
|
|
|
|
- task.title = payload.title.trim();
|
|
|
|
|
- }
|
|
|
|
|
- if (typeof payload.intent === "string" && payload.intent.trim()) {
|
|
|
|
|
- task.intent = payload.intent.trim();
|
|
|
|
|
|
|
+ // 4. Research Updates
|
|
|
|
|
+ if (event.type === "task_status") {
|
|
|
|
|
+ const payload = event as any;
|
|
|
|
|
+ if (payload.status === "in_progress") {
|
|
|
|
|
+ currentTask.value = payload; // 简单的任务更新
|
|
|
|
|
+ addLog(`👉 开始执行任务: ${payload.title || '未知任务'}`);
|
|
|
|
|
+ } else if (payload.status === "completed") {
|
|
|
|
|
+ completedTasks.value++;
|
|
|
|
|
+ addLog(`✅ 任务完成: ${payload.title}`);
|
|
|
|
|
+ } else if (payload.status === "skipped") {
|
|
|
|
|
+ completedTasks.value++;
|
|
|
|
|
+ addLog(`⏭️ 任务跳过: ${payload.title}`);
|
|
|
|
|
+ } else if (payload.status === "failed") {
|
|
|
|
|
+ completedTasks.value++;
|
|
|
|
|
+ addLog(`❌ 任务失败: ${payload.title}`);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- if (typeof payload.query === "string" && payload.query.trim()) {
|
|
|
|
|
- task.query = payload.query.trim();
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (event.type === "task_summary_chunk") {
|
|
|
|
|
+ const payload = event as any;
|
|
|
|
|
+ const taskIndex = todoList.value.findIndex(t => t.id === payload.task_id);
|
|
|
|
|
+
|
|
|
|
|
+ if (taskIndex !== -1) {
|
|
|
|
|
+ // Initialize summary if it doesn't exist
|
|
|
|
|
+ if (!todoList.value[taskIndex].summary) {
|
|
|
|
|
+ todoList.value[taskIndex].summary = "";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Append chunk
|
|
|
|
|
+ // Note: You might want to strip <think> tags if they leak through,
|
|
|
|
|
+ // but backend usually handles that.
|
|
|
|
|
+ todoList.value[taskIndex].summary += payload.content;
|
|
|
|
|
+
|
|
|
|
|
+ // Auto-scroll logic could go here if we had a ref to the specific item
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
-}
|
|
|
|
|
|
|
|
|
|
-const handleSubmit = async () => {
|
|
|
|
|
- if (!form.topic.trim()) {
|
|
|
|
|
- error.value = "请输入研究主题";
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+ // 5. Report Ready
|
|
|
|
|
+ if (event.type === "final_report") {
|
|
|
|
|
+ reportMarkdown.value = String(event.report);
|
|
|
|
|
+ addLog("📄 深度研究报告已生成。");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (currentController) {
|
|
|
|
|
- currentController.abort();
|
|
|
|
|
- currentController = null;
|
|
|
|
|
|
|
+ // 6. Script Ready
|
|
|
|
|
+ if (event.type === "podcast_script") {
|
|
|
|
|
+ const payload = event as any;
|
|
|
|
|
+ podcastScript.value = payload.script;
|
|
|
|
|
+ productionStage.value = "audio";
|
|
|
|
|
+ addLog("🎙️ 播客剧本创作完成。");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- loading.value = true;
|
|
|
|
|
- error.value = "";
|
|
|
|
|
- isExpanded.value = true;
|
|
|
|
|
- resetWorkflowState();
|
|
|
|
|
-
|
|
|
|
|
- const controller = new AbortController();
|
|
|
|
|
- currentController = controller;
|
|
|
|
|
-
|
|
|
|
|
- const payload = {
|
|
|
|
|
- topic: form.topic.trim(),
|
|
|
|
|
- search_api: form.searchApi || undefined
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- await runResearchStream(
|
|
|
|
|
- payload,
|
|
|
|
|
- (event: ResearchStreamEvent) => {
|
|
|
|
|
- if (event.type === "status") {
|
|
|
|
|
- const message =
|
|
|
|
|
- typeof event.message === "string" && event.message.trim()
|
|
|
|
|
- ? event.message
|
|
|
|
|
- : "流程状态更新";
|
|
|
|
|
- progressLogs.value.push(message);
|
|
|
|
|
-
|
|
|
|
|
- const payload = event as Record<string, unknown>;
|
|
|
|
|
- const task = findTask(payload.task_id);
|
|
|
|
|
- if (task && message) {
|
|
|
|
|
- task.notices.push(message);
|
|
|
|
|
- applyNoteMetadata(task, payload);
|
|
|
|
|
- }
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (event.type === "todo_list") {
|
|
|
|
|
- const tasks = Array.isArray(event.tasks)
|
|
|
|
|
- ? (event.tasks as Record<string, unknown>[])
|
|
|
|
|
- : [];
|
|
|
|
|
-
|
|
|
|
|
- todoTasks.value = tasks.map((item, index) => {
|
|
|
|
|
- const rawId =
|
|
|
|
|
- typeof item.id === "number"
|
|
|
|
|
- ? item.id
|
|
|
|
|
- : typeof item.id === "string"
|
|
|
|
|
- ? Number(item.id)
|
|
|
|
|
- : index + 1;
|
|
|
|
|
- const id = Number.isFinite(rawId) ? Number(rawId) : index + 1;
|
|
|
|
|
- const noteId =
|
|
|
|
|
- typeof item.note_id === "string" && item.note_id.trim()
|
|
|
|
|
- ? item.note_id.trim()
|
|
|
|
|
- : null;
|
|
|
|
|
- const notePath =
|
|
|
|
|
- typeof item.note_path === "string" && item.note_path.trim()
|
|
|
|
|
- ? item.note_path.trim()
|
|
|
|
|
- : null;
|
|
|
|
|
-
|
|
|
|
|
- return {
|
|
|
|
|
- id,
|
|
|
|
|
- title:
|
|
|
|
|
- typeof item.title === "string" && item.title.trim()
|
|
|
|
|
- ? item.title.trim()
|
|
|
|
|
- : `任务${id}`,
|
|
|
|
|
- intent:
|
|
|
|
|
- typeof item.intent === "string" && item.intent.trim()
|
|
|
|
|
- ? item.intent.trim()
|
|
|
|
|
- : "探索与主题相关的关键信息",
|
|
|
|
|
- query:
|
|
|
|
|
- typeof item.query === "string" && item.query.trim()
|
|
|
|
|
- ? item.query.trim()
|
|
|
|
|
- : form.topic.trim(),
|
|
|
|
|
- status:
|
|
|
|
|
- typeof item.status === "string" && item.status.trim()
|
|
|
|
|
- ? item.status.trim()
|
|
|
|
|
- : "pending",
|
|
|
|
|
- summary: "",
|
|
|
|
|
- sourcesSummary: "",
|
|
|
|
|
- sourceItems: [],
|
|
|
|
|
- notices: [],
|
|
|
|
|
- noteId,
|
|
|
|
|
- notePath,
|
|
|
|
|
- toolCalls: []
|
|
|
|
|
- } as TodoTaskView;
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- if (todoTasks.value.length) {
|
|
|
|
|
- activeTaskId.value = todoTasks.value[0].id;
|
|
|
|
|
- progressLogs.value.push("已生成任务清单");
|
|
|
|
|
- } else {
|
|
|
|
|
- progressLogs.value.push("未生成任务清单,使用默认任务继续");
|
|
|
|
|
- }
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (event.type === "task_status") {
|
|
|
|
|
- const payload = event as Record<string, unknown>;
|
|
|
|
|
- const task = findTask(event.task_id);
|
|
|
|
|
- if (!task) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- upsertTaskMetadata(task, payload);
|
|
|
|
|
- applyNoteMetadata(task, payload);
|
|
|
|
|
- const status =
|
|
|
|
|
- typeof event.status === "string" && event.status.trim()
|
|
|
|
|
- ? event.status.trim()
|
|
|
|
|
- : task.status;
|
|
|
|
|
- task.status = status;
|
|
|
|
|
-
|
|
|
|
|
- if (status === "in_progress") {
|
|
|
|
|
- task.summary = "";
|
|
|
|
|
- task.sourcesSummary = "";
|
|
|
|
|
- task.sourceItems = [];
|
|
|
|
|
- task.notices = [];
|
|
|
|
|
- activeTaskId.value = task.id;
|
|
|
|
|
- progressLogs.value.push(`开始执行任务:${task.title}`);
|
|
|
|
|
- } else if (status === "completed") {
|
|
|
|
|
- if (typeof event.summary === "string" && event.summary.trim()) {
|
|
|
|
|
- task.summary = event.summary.trim();
|
|
|
|
|
- }
|
|
|
|
|
- if (
|
|
|
|
|
- typeof event.sources_summary === "string" &&
|
|
|
|
|
- event.sources_summary.trim()
|
|
|
|
|
- ) {
|
|
|
|
|
- task.sourcesSummary = event.sources_summary.trim();
|
|
|
|
|
- task.sourceItems = parseSources(task.sourcesSummary);
|
|
|
|
|
- }
|
|
|
|
|
- progressLogs.value.push(`完成任务:${task.title}`);
|
|
|
|
|
- if (activeTaskId.value === task.id) {
|
|
|
|
|
- pulse(summaryHighlight);
|
|
|
|
|
- pulse(sourcesHighlight);
|
|
|
|
|
- }
|
|
|
|
|
- } else if (status === "skipped") {
|
|
|
|
|
- progressLogs.value.push(`任务跳过:${task.title}`);
|
|
|
|
|
- }
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (event.type === "sources") {
|
|
|
|
|
- const payload = event as Record<string, unknown>;
|
|
|
|
|
- const task = findTask(event.task_id);
|
|
|
|
|
- if (!task) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const textCandidates = [
|
|
|
|
|
- payload.latest_sources,
|
|
|
|
|
- payload.sources_summary,
|
|
|
|
|
- payload.raw_context
|
|
|
|
|
- ];
|
|
|
|
|
- const latestText = textCandidates
|
|
|
|
|
- .map((value) => (typeof value === "string" ? value.trim() : ""))
|
|
|
|
|
- .find((value) => value);
|
|
|
|
|
-
|
|
|
|
|
- if (latestText) {
|
|
|
|
|
- task.sourcesSummary = latestText;
|
|
|
|
|
- task.sourceItems = parseSources(latestText);
|
|
|
|
|
- if (activeTaskId.value === task.id) {
|
|
|
|
|
- pulse(sourcesHighlight);
|
|
|
|
|
- }
|
|
|
|
|
- progressLogs.value.push(`已更新任务来源:${task.title}`);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (typeof payload.backend === "string") {
|
|
|
|
|
- progressLogs.value.push(
|
|
|
|
|
- `当前使用搜索后端:${payload.backend}`
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- applyNoteMetadata(task, payload);
|
|
|
|
|
-
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (event.type === "task_summary_chunk") {
|
|
|
|
|
- const payload = event as Record<string, unknown>;
|
|
|
|
|
- const task = findTask(event.task_id);
|
|
|
|
|
- if (!task) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
- const chunk =
|
|
|
|
|
- typeof event.content === "string" ? event.content : "";
|
|
|
|
|
- task.summary += chunk;
|
|
|
|
|
- applyNoteMetadata(task, payload);
|
|
|
|
|
- if (activeTaskId.value === task.id) {
|
|
|
|
|
- pulse(summaryHighlight);
|
|
|
|
|
- }
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (event.type === "tool_call") {
|
|
|
|
|
- const payload = event as Record<string, unknown>;
|
|
|
|
|
- const eventId =
|
|
|
|
|
- typeof payload.event_id === "number"
|
|
|
|
|
- ? payload.event_id
|
|
|
|
|
- : Date.now();
|
|
|
|
|
- const agent =
|
|
|
|
|
- typeof payload.agent === "string" && payload.agent.trim()
|
|
|
|
|
- ? payload.agent.trim()
|
|
|
|
|
- : "Agent";
|
|
|
|
|
- const tool =
|
|
|
|
|
- typeof payload.tool === "string" && payload.tool.trim()
|
|
|
|
|
- ? payload.tool.trim()
|
|
|
|
|
- : "tool";
|
|
|
|
|
- const parameters = ensureRecord(payload.parameters);
|
|
|
|
|
- const result =
|
|
|
|
|
- typeof payload.result === "string" ? payload.result : "";
|
|
|
|
|
- const noteId = extractOptionalString(payload.note_id);
|
|
|
|
|
- const notePath = extractOptionalString(payload.note_path);
|
|
|
|
|
-
|
|
|
|
|
- const task = findTask(payload.task_id);
|
|
|
|
|
- if (task) {
|
|
|
|
|
- task.toolCalls.push({
|
|
|
|
|
- eventId,
|
|
|
|
|
- agent,
|
|
|
|
|
- tool,
|
|
|
|
|
- parameters,
|
|
|
|
|
- result,
|
|
|
|
|
- noteId,
|
|
|
|
|
- notePath,
|
|
|
|
|
- timestamp: Date.now()
|
|
|
|
|
- });
|
|
|
|
|
- if (noteId) {
|
|
|
|
|
- task.noteId = noteId;
|
|
|
|
|
- }
|
|
|
|
|
- if (notePath) {
|
|
|
|
|
- task.notePath = notePath;
|
|
|
|
|
- }
|
|
|
|
|
- const logSummary = noteId
|
|
|
|
|
- ? `${agent} 调用了 ${tool}(任务 ${task.id},笔记 ${noteId})`
|
|
|
|
|
- : `${agent} 调用了 ${tool}(任务 ${task.id})`;
|
|
|
|
|
- progressLogs.value.push(logSummary);
|
|
|
|
|
- if (activeTaskId.value === task.id) {
|
|
|
|
|
- pulse(toolHighlight);
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- progressLogs.value.push(`${agent} 调用了 ${tool}`);
|
|
|
|
|
- }
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (event.type === "final_report") {
|
|
|
|
|
- const report =
|
|
|
|
|
- typeof event.report === "string" && event.report.trim()
|
|
|
|
|
- ? event.report.trim()
|
|
|
|
|
- : "";
|
|
|
|
|
- reportMarkdown.value = report || "报告生成失败,未获得有效内容";
|
|
|
|
|
- pulse(reportHighlight);
|
|
|
|
|
- progressLogs.value.push("最终报告已生成");
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (event.type === "error") {
|
|
|
|
|
- const detail =
|
|
|
|
|
- typeof event.detail === "string" && event.detail.trim()
|
|
|
|
|
- ? event.detail
|
|
|
|
|
- : "研究过程中发生错误";
|
|
|
|
|
- error.value = detail;
|
|
|
|
|
- progressLogs.value.push("研究失败,已停止流程");
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- { signal: controller.signal }
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- if (!reportMarkdown.value) {
|
|
|
|
|
- reportMarkdown.value = "暂无生成的报告";
|
|
|
|
|
- }
|
|
|
|
|
- } catch (err) {
|
|
|
|
|
- if (err instanceof DOMException && err.name === "AbortError") {
|
|
|
|
|
- progressLogs.value.push("已取消当前研究任务");
|
|
|
|
|
- } else {
|
|
|
|
|
- error.value = err instanceof Error ? err.message : "请求失败";
|
|
|
|
|
- }
|
|
|
|
|
- } finally {
|
|
|
|
|
- loading.value = false;
|
|
|
|
|
- if (currentController === controller) {
|
|
|
|
|
- currentController = null;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 7. Audio Generation (Detail)
|
|
|
|
|
+ if (event.type === "audio_generated") {
|
|
|
|
|
+ const files = (event as any).files || [];
|
|
|
|
|
+ addLog(`🎵 已生成 ${files.length} 个音频片段。`);
|
|
|
}
|
|
}
|
|
|
-};
|
|
|
|
|
|
|
|
|
|
-const cancelResearch = () => {
|
|
|
|
|
- if (!loading.value || !currentController) {
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+ // 8. Podcast Ready (Final)
|
|
|
|
|
+ if (event.type === "podcast_ready") {
|
|
|
|
|
+ const payload = event as any;
|
|
|
|
|
+ // 后端返回的是文件路径,我们需要转换为 URL
|
|
|
|
|
+ // 假设后端挂载了 /output 静态目录
|
|
|
|
|
+ // payload.file 是绝对路径,我们需要提取文件名
|
|
|
|
|
+ const filename = String(payload.file).split(/[\\/]/).pop();
|
|
|
|
|
+ if (filename) {
|
|
|
|
|
+ // 获取当前 API base URL (从 api.ts 逻辑推断,这里简化处理)
|
|
|
|
|
+ // 在生产环境中应该从配置读取,这里假设是 localhost:8000
|
|
|
|
|
+ const baseUrl = import.meta.env.VITE_API_BASE_URL || "http://localhost:8000";
|
|
|
|
|
+ audioUrl.value = `${baseUrl}/output/${filename}`;
|
|
|
|
|
+ addLog("🎉 播客制作完成!即将开始播放...");
|
|
|
|
|
+ productionStage.value = "done";
|
|
|
|
|
+
|
|
|
|
|
+ // 延迟跳转到播放页
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ currentView.value = "player";
|
|
|
|
|
+ }, 1500);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- progressLogs.value.push("正在尝试取消当前研究任务…");
|
|
|
|
|
- currentController.abort();
|
|
|
|
|
-};
|
|
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-const goBack = () => {
|
|
|
|
|
- if (loading.value) {
|
|
|
|
|
- return; // 研究进行中不允许返回
|
|
|
|
|
|
|
+function cancelProduction() {
|
|
|
|
|
+ if (abortController) {
|
|
|
|
|
+ abortController.abort();
|
|
|
|
|
+ abortController = null;
|
|
|
}
|
|
}
|
|
|
- isExpanded.value = false;
|
|
|
|
|
-};
|
|
|
|
|
|
|
+ currentView.value = "setup";
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-const startNewResearch = () => {
|
|
|
|
|
- if (loading.value) {
|
|
|
|
|
- cancelResearch();
|
|
|
|
|
- }
|
|
|
|
|
- resetWorkflowState();
|
|
|
|
|
- isExpanded.value = false;
|
|
|
|
|
|
|
+function resetApp() {
|
|
|
|
|
+ currentView.value = "setup";
|
|
|
form.topic = "";
|
|
form.topic = "";
|
|
|
- form.searchApi = "";
|
|
|
|
|
-};
|
|
|
|
|
-
|
|
|
|
|
-onBeforeUnmount(() => {
|
|
|
|
|
- if (currentController) {
|
|
|
|
|
- currentController.abort();
|
|
|
|
|
- currentController = null;
|
|
|
|
|
- }
|
|
|
|
|
-});
|
|
|
|
|
-</script>
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-<style scoped>
|
|
|
|
|
-.app-shell {
|
|
|
|
|
- position: relative;
|
|
|
|
|
- min-height: 100vh;
|
|
|
|
|
- padding: 72px 24px;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- justify-content: center;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- background: radial-gradient(circle at 20% 20%, #f8fafc, #dbeafe 60%);
|
|
|
|
|
- color: #1f2937;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
- box-sizing: border-box;
|
|
|
|
|
- transition: padding 0.4s ease;
|
|
|
|
|
|
|
+ isPlaying.value = false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.app-shell.expanded {
|
|
|
|
|
- padding: 0;
|
|
|
|
|
- align-items: stretch;
|
|
|
|
|
|
|
+// Audio Controls
|
|
|
|
|
+function togglePlay() {
|
|
|
|
|
+ if (!audioPlayer.value) return;
|
|
|
|
|
+ if (isPlaying.value) {
|
|
|
|
|
+ audioPlayer.value.pause();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ audioPlayer.value.play();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.aurora {
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- inset: 0;
|
|
|
|
|
- pointer-events: none;
|
|
|
|
|
- opacity: 0.55;
|
|
|
|
|
|
|
+function onTimeUpdate() {
|
|
|
|
|
+ if (audioPlayer.value) {
|
|
|
|
|
+ currentTime.value = audioPlayer.value.currentTime;
|
|
|
|
|
+ duration.value = audioPlayer.value.duration || 0;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.aurora span {
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- width: 45vw;
|
|
|
|
|
- height: 45vw;
|
|
|
|
|
- max-width: 520px;
|
|
|
|
|
- max-height: 520px;
|
|
|
|
|
- background: radial-gradient(circle, rgba(148, 197, 255, 0.35), transparent 60%);
|
|
|
|
|
- filter: blur(90px);
|
|
|
|
|
- animation: float 26s infinite linear;
|
|
|
|
|
|
|
+function seekAudio(e: MouseEvent) {
|
|
|
|
|
+ if (!audioPlayer.value || !duration.value) return;
|
|
|
|
|
+ const target = e.currentTarget as HTMLElement;
|
|
|
|
|
+ const rect = target.getBoundingClientRect();
|
|
|
|
|
+ const x = e.clientX - rect.left;
|
|
|
|
|
+ const percent = x / rect.width;
|
|
|
|
|
+ audioPlayer.value.currentTime = percent * duration.value;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.aurora span:nth-child(1) {
|
|
|
|
|
- top: -20%;
|
|
|
|
|
- left: -18%;
|
|
|
|
|
- animation-delay: 0s;
|
|
|
|
|
|
|
+function formatTime(seconds: number) {
|
|
|
|
|
+ if (!seconds) return "0:00";
|
|
|
|
|
+ const m = Math.floor(seconds / 60);
|
|
|
|
|
+ const s = Math.floor(seconds % 60);
|
|
|
|
|
+ return `${m}:${s.toString().padStart(2, "0")}`;
|
|
|
}
|
|
}
|
|
|
|
|
+</script>
|
|
|
|
|
|
|
|
-.aurora span:nth-child(2) {
|
|
|
|
|
- bottom: -25%;
|
|
|
|
|
- right: -20%;
|
|
|
|
|
- background: radial-gradient(circle, rgba(166, 139, 255, 0.28), transparent 60%);
|
|
|
|
|
- animation-delay: -9s;
|
|
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+/* --- Global & Layout --- */
|
|
|
|
|
+::-webkit-scrollbar {
|
|
|
|
|
+ width: 8px;
|
|
|
|
|
+ height: 8px;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-.aurora span:nth-child(3) {
|
|
|
|
|
- top: 35%;
|
|
|
|
|
- left: 45%;
|
|
|
|
|
- background: radial-gradient(circle, rgba(164, 219, 216, 0.26), transparent 60%);
|
|
|
|
|
- animation-delay: -16s;
|
|
|
|
|
|
|
+::-webkit-scrollbar-track {
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.05);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-.layout {
|
|
|
|
|
- position: relative;
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- gap: 24px;
|
|
|
|
|
- z-index: 1;
|
|
|
|
|
- transition: all 0.4s ease;
|
|
|
|
|
|
|
+::-webkit-scrollbar-thumb {
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.2);
|
|
|
|
|
+ border-radius: 4px;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-.layout-centered {
|
|
|
|
|
- max-width: 600px;
|
|
|
|
|
- justify-content: center;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
|
|
+::-webkit-scrollbar-thumb:hover {
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.3);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.layout-fullscreen {
|
|
|
|
|
|
|
+.deepcast-container {
|
|
|
|
|
+ width: 100vw;
|
|
|
height: 100vh;
|
|
height: 100vh;
|
|
|
- max-width: 100%;
|
|
|
|
|
- gap: 0;
|
|
|
|
|
- align-items: stretch;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.panel {
|
|
|
|
|
- position: relative;
|
|
|
|
|
- flex: 1 1 360px;
|
|
|
|
|
- padding: 24px;
|
|
|
|
|
- border-radius: 20px;
|
|
|
|
|
- background: rgba(255, 255, 255, 0.95);
|
|
|
|
|
- border: 1px solid rgba(148, 163, 184, 0.18);
|
|
|
|
|
- box-shadow: 0 24px 48px rgba(15, 23, 42, 0.12);
|
|
|
|
|
- backdrop-filter: blur(8px);
|
|
|
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
|
|
+ font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ background: #0f172a;
|
|
|
|
|
+ position: relative;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.panel-form {
|
|
|
|
|
- max-width: 420px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.panel-centered {
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- max-width: 600px;
|
|
|
|
|
- padding: 40px;
|
|
|
|
|
- box-shadow: 0 32px 64px rgba(15, 23, 42, 0.15);
|
|
|
|
|
- transform: scale(1);
|
|
|
|
|
- transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.panel-centered:hover {
|
|
|
|
|
- transform: scale(1.02);
|
|
|
|
|
- box-shadow: 0 40px 80px rgba(15, 23, 42, 0.2);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.panel-result {
|
|
|
|
|
- min-width: 360px;
|
|
|
|
|
- flex: 2 1 420px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.panel::before {
|
|
|
|
|
- content: "";
|
|
|
|
|
|
|
+.background-gradient {
|
|
|
position: absolute;
|
|
position: absolute;
|
|
|
- inset: 0;
|
|
|
|
|
- background: linear-gradient(135deg, rgba(59, 130, 246, 0.12), rgba(125, 86, 255, 0.1));
|
|
|
|
|
- opacity: 0;
|
|
|
|
|
- transition: opacity 0.35s ease;
|
|
|
|
|
|
|
+ top: -50%;
|
|
|
|
|
+ left: -50%;
|
|
|
|
|
+ width: 200%;
|
|
|
|
|
+ height: 200%;
|
|
|
|
|
+ background: radial-gradient(circle at center, #1e293b 0%, #0f172a 60%, #000 100%);
|
|
|
z-index: 0;
|
|
z-index: 0;
|
|
|
|
|
+ animation: pulseBg 20s infinite alternate;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.panel:hover::before {
|
|
|
|
|
- opacity: 1;
|
|
|
|
|
|
|
+@keyframes pulseBg {
|
|
|
|
|
+ 0% { transform: scale(1); }
|
|
|
|
|
+ 100% { transform: scale(1.1); }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.panel > * {
|
|
|
|
|
|
|
+section {
|
|
|
position: relative;
|
|
position: relative;
|
|
|
z-index: 1;
|
|
z-index: 1;
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.panel-form h1 {
|
|
|
|
|
- margin: 0;
|
|
|
|
|
- font-size: 26px;
|
|
|
|
|
- letter-spacing: 0.01em;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.panel-form p {
|
|
|
|
|
- margin: 4px 0 0;
|
|
|
|
|
- color: #64748b;
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.panel-head {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 16px;
|
|
|
|
|
- margin-bottom: 24px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.logo {
|
|
|
|
|
- width: 52px;
|
|
|
|
|
- height: 52px;
|
|
|
|
|
- display: grid;
|
|
|
|
|
- place-items: center;
|
|
|
|
|
- border-radius: 16px;
|
|
|
|
|
- background: linear-gradient(135deg, #2563eb, #7c3aed);
|
|
|
|
|
- box-shadow: 0 12px 28px rgba(59, 130, 246, 0.4);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.logo svg {
|
|
|
|
|
- width: 28px;
|
|
|
|
|
- height: 28px;
|
|
|
|
|
- fill: #f8fafc;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.form {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- gap: 18px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.field {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- gap: 10px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.field span {
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- color: #475569;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-textarea,
|
|
|
|
|
-input,
|
|
|
|
|
-select {
|
|
|
|
|
- padding: 14px 16px;
|
|
|
|
|
- border-radius: 16px;
|
|
|
|
|
- border: 1px solid rgba(148, 163, 184, 0.35);
|
|
|
|
|
- background: rgba(255, 255, 255, 0.92);
|
|
|
|
|
- color: #1f2937;
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- transition: border-color 0.2s, box-shadow 0.2s, background 0.2s;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-textarea:focus,
|
|
|
|
|
-input:focus,
|
|
|
|
|
-select:focus {
|
|
|
|
|
- outline: none;
|
|
|
|
|
- border-color: rgba(37, 99, 235, 0.65);
|
|
|
|
|
- box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
|
|
|
|
|
- background: #ffffff;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.options {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- gap: 16px;
|
|
|
|
|
- flex-wrap: wrap;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.option {
|
|
|
|
|
- flex: 1;
|
|
|
|
|
- min-width: 140px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.form-actions {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 12px;
|
|
|
|
|
- flex-wrap: wrap;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.submit {
|
|
|
|
|
- align-self: flex-start;
|
|
|
|
|
- padding: 12px 24px;
|
|
|
|
|
- border-radius: 16px;
|
|
|
|
|
- border: none;
|
|
|
|
|
- background: linear-gradient(135deg, #2563eb, #7c3aed);
|
|
|
|
|
- color: #ffffff;
|
|
|
|
|
- font-size: 15px;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
- transition: transform 0.2s, box-shadow 0.2s, opacity 0.2s;
|
|
|
|
|
- display: inline-flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 10px;
|
|
|
|
|
- position: relative;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.submit-label {
|
|
|
|
|
- display: inline-flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 10px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.submit .spinner {
|
|
|
|
|
- width: 18px;
|
|
|
|
|
- height: 18px;
|
|
|
|
|
- fill: none;
|
|
|
|
|
- stroke: rgba(255, 255, 255, 0.85);
|
|
|
|
|
- stroke-linecap: round;
|
|
|
|
|
- animation: spin 1s linear infinite;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.submit:disabled {
|
|
|
|
|
- opacity: 0.7;
|
|
|
|
|
- cursor: not-allowed;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.submit:not(:disabled):hover {
|
|
|
|
|
- transform: translateY(-2px);
|
|
|
|
|
- box-shadow: 0 12px 28px rgba(37, 99, 235, 0.28);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.secondary-btn {
|
|
|
|
|
- padding: 10px 18px;
|
|
|
|
|
- border-radius: 14px;
|
|
|
|
|
- background: rgba(148, 163, 184, 0.12);
|
|
|
|
|
- border: 1px solid rgba(148, 163, 184, 0.28);
|
|
|
|
|
- color: #1f2937;
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- font-weight: 500;
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
- transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.secondary-btn:hover {
|
|
|
|
|
- background: rgba(148, 163, 184, 0.2);
|
|
|
|
|
- border-color: rgba(148, 163, 184, 0.35);
|
|
|
|
|
- color: #0f172a;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.error-chip {
|
|
|
|
|
- margin-top: 16px;
|
|
|
|
|
- display: inline-flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 8px;
|
|
|
|
|
- padding: 10px 14px;
|
|
|
|
|
- background: rgba(248, 113, 113, 0.12);
|
|
|
|
|
- border: 1px solid rgba(248, 113, 113, 0.35);
|
|
|
|
|
- border-radius: 14px;
|
|
|
|
|
- color: #b91c1c;
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.error-chip svg {
|
|
|
|
|
- width: 18px;
|
|
|
|
|
- height: 18px;
|
|
|
|
|
- fill: currentColor;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.panel-result {
|
|
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ width: 100%;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
|
- gap: 18px;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.status-bar {
|
|
|
|
|
- display: flex;
|
|
|
|
|
|
|
+/* --- Setup View --- */
|
|
|
|
|
+.view-setup {
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
- justify-content: space-between;
|
|
|
|
|
- gap: 12px;
|
|
|
|
|
- flex-wrap: wrap;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.status-main {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 12px;
|
|
|
|
|
- flex-wrap: wrap;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.status-controls {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- gap: 8px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.status-chip {
|
|
|
|
|
- display: inline-flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 8px;
|
|
|
|
|
- background: rgba(191, 219, 254, 0.28);
|
|
|
|
|
- padding: 8px 14px;
|
|
|
|
|
- border-radius: 999px;
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
- color: #1f2937;
|
|
|
|
|
- border: 1px solid rgba(59, 130, 246, 0.35);
|
|
|
|
|
- transition: background 0.3s ease, color 0.3s ease;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.status-chip.active {
|
|
|
|
|
- background: rgba(129, 140, 248, 0.2);
|
|
|
|
|
- border-color: rgba(129, 140, 248, 0.4);
|
|
|
|
|
- color: #1e293b;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.status-chip .dot {
|
|
|
|
|
- width: 8px;
|
|
|
|
|
- height: 8px;
|
|
|
|
|
- border-radius: 999px;
|
|
|
|
|
- background: #2563eb;
|
|
|
|
|
- box-shadow: 0 0 12px rgba(37, 99, 235, 0.45);
|
|
|
|
|
- animation: pulse 1.8s ease-in-out infinite;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.status-meta {
|
|
|
|
|
- color: #64748b;
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.timeline-wrapper {
|
|
|
|
|
- margin-top: 12px;
|
|
|
|
|
- max-height: 220px;
|
|
|
|
|
- overflow-y: auto;
|
|
|
|
|
- padding-right: 8px;
|
|
|
|
|
- scrollbar-width: thin;
|
|
|
|
|
- scrollbar-color: rgba(129, 140, 248, 0.45) rgba(226, 232, 240, 0.6);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.timeline-wrapper::-webkit-scrollbar {
|
|
|
|
|
- width: 6px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.timeline-wrapper::-webkit-scrollbar-track {
|
|
|
|
|
- background: rgba(226, 232, 240, 0.6);
|
|
|
|
|
- border-radius: 999px;
|
|
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ padding: 2rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.timeline-wrapper::-webkit-scrollbar-thumb {
|
|
|
|
|
- background: linear-gradient(180deg, rgba(129, 140, 248, 0.8), rgba(59, 130, 246, 0.7));
|
|
|
|
|
- border-radius: 999px;
|
|
|
|
|
|
|
+.brand-header {
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ margin-bottom: 3rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.timeline-wrapper::-webkit-scrollbar-thumb:hover {
|
|
|
|
|
- background: linear-gradient(180deg, rgba(99, 102, 241, 0.9), rgba(37, 99, 235, 0.8));
|
|
|
|
|
|
|
+.logo-icon {
|
|
|
|
|
+ font-size: 4rem;
|
|
|
|
|
+ margin-bottom: 1rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.timeline {
|
|
|
|
|
- list-style: none;
|
|
|
|
|
- padding: 0;
|
|
|
|
|
|
|
+h1 {
|
|
|
|
|
+ font-size: 3rem;
|
|
|
|
|
+ font-weight: 800;
|
|
|
|
|
+ letter-spacing: -1px;
|
|
|
|
|
+ background: linear-gradient(135deg, #60a5fa, #c084fc);
|
|
|
|
|
+ -webkit-background-clip: text;
|
|
|
|
|
+ -webkit-text-fill-color: transparent;
|
|
|
margin: 0;
|
|
margin: 0;
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- gap: 14px;
|
|
|
|
|
- position: relative;
|
|
|
|
|
- padding-left: 12px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.timeline::before {
|
|
|
|
|
- content: "";
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- top: 8px;
|
|
|
|
|
- bottom: 8px;
|
|
|
|
|
- left: 0;
|
|
|
|
|
- width: 2px;
|
|
|
|
|
- background: linear-gradient(180deg, rgba(59, 130, 246, 0.35), rgba(129, 140, 248, 0.15));
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.timeline li {
|
|
|
|
|
- position: relative;
|
|
|
|
|
- padding-left: 24px;
|
|
|
|
|
- color: #1e293b;
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- line-height: 1.5;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.timeline-node {
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- left: -12px;
|
|
|
|
|
- top: 6px;
|
|
|
|
|
- width: 10px;
|
|
|
|
|
- height: 10px;
|
|
|
|
|
- border-radius: 999px;
|
|
|
|
|
- background: linear-gradient(135deg, #38bdf8, #7c3aed);
|
|
|
|
|
- box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.22);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.timeline-enter-active,
|
|
|
|
|
-.timeline-leave-active {
|
|
|
|
|
- transition: all 0.35s ease, opacity 0.35s ease;
|
|
|
|
|
|
|
+.tagline {
|
|
|
|
|
+ color: #94a3b8;
|
|
|
|
|
+ font-size: 1.1rem;
|
|
|
|
|
+ margin-top: 0.5rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.timeline-enter-from,
|
|
|
|
|
-.timeline-leave-to {
|
|
|
|
|
- opacity: 0;
|
|
|
|
|
- transform: translateY(-6px);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.tasks-section {
|
|
|
|
|
- display: grid;
|
|
|
|
|
- grid-template-columns: 280px 1fr;
|
|
|
|
|
- gap: 20px;
|
|
|
|
|
- align-items: start;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-@media (max-width: 960px) {
|
|
|
|
|
- .tasks-section {
|
|
|
|
|
- grid-template-columns: 1fr;
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.tasks-list {
|
|
|
|
|
- background: rgba(255, 255, 255, 0.92);
|
|
|
|
|
- border: 1px solid rgba(148, 163, 184, 0.26);
|
|
|
|
|
- border-radius: 18px;
|
|
|
|
|
- padding: 18px;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- gap: 16px;
|
|
|
|
|
- box-shadow: inset 0 0 0 1px rgba(226, 232, 240, 0.4);
|
|
|
|
|
|
|
+.setup-form {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ max-width: 500px;
|
|
|
|
|
+ background: rgba(30, 41, 59, 0.5);
|
|
|
|
|
+ backdrop-filter: blur(10px);
|
|
|
|
|
+ padding: 2rem;
|
|
|
|
|
+ border-radius: 16px;
|
|
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
|
|
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.tasks-list h3 {
|
|
|
|
|
- margin: 0;
|
|
|
|
|
- font-size: 16px;
|
|
|
|
|
|
|
+.input-group label, .setting-item label {
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ font-size: 0.875rem;
|
|
|
font-weight: 600;
|
|
font-weight: 600;
|
|
|
- color: #1f2937;
|
|
|
|
|
|
|
+ color: #cbd5e1;
|
|
|
|
|
+ margin-bottom: 0.5rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.tasks-list ul {
|
|
|
|
|
- list-style: none;
|
|
|
|
|
- margin: 0;
|
|
|
|
|
- padding: 0;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- gap: 12px;
|
|
|
|
|
|
|
+.input-group textarea {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ background: rgba(15, 23, 42, 0.6);
|
|
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ padding: 1rem;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ resize: none;
|
|
|
|
|
+ font-size: 1rem;
|
|
|
|
|
+ transition: border-color 0.2s;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.task-item {
|
|
|
|
|
- border-radius: 14px;
|
|
|
|
|
- border: 1px solid transparent;
|
|
|
|
|
- transition: border-color 0.2s ease, background 0.2s ease;
|
|
|
|
|
|
|
+.input-group textarea:focus {
|
|
|
|
|
+ outline: none;
|
|
|
|
|
+ border-color: #60a5fa;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.task-item.completed {
|
|
|
|
|
- border-color: rgba(56, 189, 248, 0.35);
|
|
|
|
|
- background: rgba(191, 219, 254, 0.28);
|
|
|
|
|
|
|
+.settings-row {
|
|
|
|
|
+ margin: 1.5rem 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.task-item.active {
|
|
|
|
|
- border-color: rgba(129, 140, 248, 0.5);
|
|
|
|
|
- background: rgba(224, 231, 255, 0.5);
|
|
|
|
|
|
|
+.select-wrapper {
|
|
|
|
|
+ position: relative;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.task-button {
|
|
|
|
|
|
|
+select {
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- justify-content: space-between;
|
|
|
|
|
- gap: 12px;
|
|
|
|
|
- padding: 12px 14px 6px;
|
|
|
|
|
- background: transparent;
|
|
|
|
|
- border: none;
|
|
|
|
|
- color: inherit;
|
|
|
|
|
|
|
+ appearance: none;
|
|
|
|
|
+ background: rgba(15, 23, 42, 0.6);
|
|
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ padding: 0.75rem 1rem;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ font-size: 0.95rem;
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
|
- text-align: left;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.task-title {
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- color: #1e293b;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.task-status {
|
|
|
|
|
- display: inline-flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- justify-content: center;
|
|
|
|
|
- padding: 4px 10px;
|
|
|
|
|
- border-radius: 999px;
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
- font-weight: 500;
|
|
|
|
|
- color: #1f2937;
|
|
|
|
|
- background: rgba(148, 163, 184, 0.2);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.task-status.pending {
|
|
|
|
|
- background: rgba(148, 163, 184, 0.18);
|
|
|
|
|
- color: #475569;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.task-status.in_progress {
|
|
|
|
|
- background: rgba(129, 140, 248, 0.24);
|
|
|
|
|
- color: #312e81;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.task-status.completed {
|
|
|
|
|
- background: rgba(34, 197, 94, 0.2);
|
|
|
|
|
- color: #15803d;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.task-status.skipped {
|
|
|
|
|
- background: rgba(248, 113, 113, 0.18);
|
|
|
|
|
- color: #b91c1c;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.task-intent {
|
|
|
|
|
- margin: 0;
|
|
|
|
|
- padding: 0 14px 12px 14px;
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
|
|
+.select-arrow {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ right: 1rem;
|
|
|
|
|
+ top: 50%;
|
|
|
|
|
+ transform: translateY(-50%);
|
|
|
color: #64748b;
|
|
color: #64748b;
|
|
|
|
|
+ pointer-events: none;
|
|
|
|
|
+ font-size: 0.8rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.task-detail {
|
|
|
|
|
- background: rgba(255, 255, 255, 0.94);
|
|
|
|
|
- border: 1px solid rgba(148, 163, 184, 0.26);
|
|
|
|
|
- border-radius: 18px;
|
|
|
|
|
- padding: 22px;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- gap: 18px;
|
|
|
|
|
- box-shadow: inset 0 0 0 1px rgba(226, 232, 240, 0.5);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.task-header {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- justify-content: space-between;
|
|
|
|
|
- align-items: flex-start;
|
|
|
|
|
- flex-wrap: wrap;
|
|
|
|
|
- gap: 12px;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.task-chip-group {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 8px;
|
|
|
|
|
- flex-wrap: wrap;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.task-header h3 {
|
|
|
|
|
- margin: 0;
|
|
|
|
|
- font-size: 18px;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- color: #1f2937;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.task-header .muted {
|
|
|
|
|
- margin: 6px 0 0;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.task-label {
|
|
|
|
|
- padding: 6px 12px;
|
|
|
|
|
- border-radius: 999px;
|
|
|
|
|
- background: rgba(191, 219, 254, 0.32);
|
|
|
|
|
- border: 1px solid rgba(59, 130, 246, 0.35);
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
- color: #1e3a8a;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.task-label.note-chip {
|
|
|
|
|
- background: rgba(34, 197, 94, 0.2);
|
|
|
|
|
- border-color: rgba(34, 197, 94, 0.35);
|
|
|
|
|
- color: #15803d;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.task-label.path-chip {
|
|
|
|
|
- display: inline-flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 6px;
|
|
|
|
|
- max-width: 360px;
|
|
|
|
|
- background: rgba(56, 189, 248, 0.2);
|
|
|
|
|
- border-color: rgba(56, 189, 248, 0.35);
|
|
|
|
|
- color: #0369a1;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
- text-overflow: ellipsis;
|
|
|
|
|
- white-space: nowrap;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.path-label {
|
|
|
|
|
- font-weight: 500;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.path-text {
|
|
|
|
|
- max-width: 220px;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
- text-overflow: ellipsis;
|
|
|
|
|
- white-space: nowrap;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.chip-action {
|
|
|
|
|
|
|
+.cta-button {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 1rem;
|
|
|
|
|
+ background: linear-gradient(135deg, #3b82f6, #8b5cf6);
|
|
|
border: none;
|
|
border: none;
|
|
|
- background: rgba(56, 189, 248, 0.2);
|
|
|
|
|
- color: #0369a1;
|
|
|
|
|
- padding: 3px 8px;
|
|
|
|
|
- border-radius: 10px;
|
|
|
|
|
- font-size: 11px;
|
|
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ font-size: 1.1rem;
|
|
|
|
|
+ font-weight: 600;
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
|
- transition: background 0.2s ease, color 0.2s ease;
|
|
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ gap: 0.5rem;
|
|
|
|
|
+ transition: transform 0.2s, opacity 0.2s;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.chip-action:hover {
|
|
|
|
|
- background: rgba(14, 165, 233, 0.28);
|
|
|
|
|
- color: #0f172a;
|
|
|
|
|
|
|
+.cta-button:hover:not(:disabled) {
|
|
|
|
|
+ transform: translateY(-2px);
|
|
|
|
|
+ opacity: 0.9;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.task-notices {
|
|
|
|
|
- background: rgba(191, 219, 254, 0.28);
|
|
|
|
|
- border: 1px solid rgba(96, 165, 250, 0.35);
|
|
|
|
|
- border-radius: 16px;
|
|
|
|
|
- padding: 14px 18px;
|
|
|
|
|
- color: #1f2937;
|
|
|
|
|
|
|
+.cta-button:disabled {
|
|
|
|
|
+ opacity: 0.5;
|
|
|
|
|
+ cursor: not-allowed;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.task-notices h4 {
|
|
|
|
|
- margin: 0 0 8px;
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
|
|
+/* --- Production View --- */
|
|
|
|
|
+.view-production {
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ display: block;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.task-notices ul {
|
|
|
|
|
- list-style: disc;
|
|
|
|
|
- margin: 0 0 0 18px;
|
|
|
|
|
- padding: 0;
|
|
|
|
|
|
|
+.production-content {
|
|
|
|
|
+ max-width: 800px;
|
|
|
|
|
+ margin: 0 auto;
|
|
|
|
|
+ padding: 4rem 2rem;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
|
- gap: 6px;
|
|
|
|
|
|
|
+ align-items: center;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.task-notices li {
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
|
|
+.todo-list-container {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ margin-top: 2rem;
|
|
|
|
|
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
|
|
+ padding-top: 2rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.report-block {
|
|
|
|
|
- background: rgba(255, 255, 255, 0.94);
|
|
|
|
|
- border: 1px solid rgba(148, 163, 184, 0.26);
|
|
|
|
|
- border-radius: 18px;
|
|
|
|
|
- padding: 22px;
|
|
|
|
|
|
|
+.todo-list-container h3 {
|
|
|
|
|
+ margin-bottom: 1.5rem;
|
|
|
|
|
+ color: #e2e8f0;
|
|
|
|
|
+ font-size: 1.2rem;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- flex-direction: column;
|
|
|
|
|
- gap: 12px;
|
|
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 0.5rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.report-block h3 {
|
|
|
|
|
- margin: 0;
|
|
|
|
|
- font-size: 18px;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- color: #1f2937;
|
|
|
|
|
|
|
+.todo-items {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 1rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.block-pre {
|
|
|
|
|
- font-family: "JetBrains Mono", "Fira Code", ui-monospace, SFMono-Regular,
|
|
|
|
|
- Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
- line-height: 1.7;
|
|
|
|
|
- white-space: pre-wrap;
|
|
|
|
|
- word-break: break-word;
|
|
|
|
|
- color: #1f2937;
|
|
|
|
|
- background: rgba(248, 250, 252, 0.9);
|
|
|
|
|
- padding: 16px;
|
|
|
|
|
- border-radius: 14px;
|
|
|
|
|
- border: 1px solid rgba(148, 163, 184, 0.35);
|
|
|
|
|
- overflow: auto;
|
|
|
|
|
- max-height: 420px;
|
|
|
|
|
- scrollbar-width: thin;
|
|
|
|
|
- scrollbar-color: rgba(129, 140, 248, 0.6) rgba(226, 232, 240, 0.7);
|
|
|
|
|
|
|
+.todo-item {
|
|
|
|
|
+ background: rgba(30, 41, 59, 0.5);
|
|
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ padding: 1.5rem;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 1rem;
|
|
|
|
|
+ transition: all 0.3s ease;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.block-pre::-webkit-scrollbar {
|
|
|
|
|
- width: 6px;
|
|
|
|
|
|
|
+.todo-item:hover {
|
|
|
|
|
+ background: rgba(30, 41, 59, 0.8);
|
|
|
|
|
+ border-color: rgba(96, 165, 250, 0.3);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.block-pre::-webkit-scrollbar-track {
|
|
|
|
|
- background: rgba(226, 232, 240, 0.7);
|
|
|
|
|
- border-radius: 999px;
|
|
|
|
|
|
|
+.todo-item.in_progress {
|
|
|
|
|
+ border-color: #60a5fa;
|
|
|
|
|
+ box-shadow: 0 0 20px rgba(96, 165, 250, 0.1);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.block-pre::-webkit-scrollbar-thumb {
|
|
|
|
|
- background: linear-gradient(180deg, rgba(99, 102, 241, 0.75), rgba(59, 130, 246, 0.65));
|
|
|
|
|
- border-radius: 999px;
|
|
|
|
|
|
|
+.todo-item.completed {
|
|
|
|
|
+ border-color: rgba(16, 185, 129, 0.3);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.block-pre::-webkit-scrollbar-thumb:hover {
|
|
|
|
|
- background: linear-gradient(180deg, rgba(79, 70, 229, 0.8), rgba(37, 99, 235, 0.75));
|
|
|
|
|
|
|
+.todo-item.failed {
|
|
|
|
|
+ border-color: rgba(239, 68, 68, 0.3);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.summary-block .block-pre,
|
|
|
|
|
-.sources-block .block-pre {
|
|
|
|
|
- max-height: 360px;
|
|
|
|
|
|
|
+.task-status-icon {
|
|
|
|
|
+ font-size: 1.5rem;
|
|
|
|
|
+ padding-top: 0.2rem;
|
|
|
|
|
+ min-width: 2rem;
|
|
|
|
|
+ text-align: center;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.task-content {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-.tools-block {
|
|
|
|
|
- position: relative;
|
|
|
|
|
- margin-top: 16px;
|
|
|
|
|
- padding: 20px;
|
|
|
|
|
- border-radius: 18px;
|
|
|
|
|
- background: rgba(255, 255, 255, 0.94);
|
|
|
|
|
- border: 1px solid rgba(148, 163, 184, 0.18);
|
|
|
|
|
- box-shadow: inset 0 0 0 1px rgba(226, 232, 240, 0.4);
|
|
|
|
|
|
|
+.task-header {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- flex-direction: column;
|
|
|
|
|
- gap: 12px;
|
|
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: baseline;
|
|
|
|
|
+ margin-bottom: 0.5rem;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+ gap: 0.5rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.tools-block h3 {
|
|
|
|
|
- margin: 0;
|
|
|
|
|
- font-size: 16px;
|
|
|
|
|
|
|
+.task-title {
|
|
|
font-weight: 600;
|
|
font-weight: 600;
|
|
|
- color: #1f2937;
|
|
|
|
|
- letter-spacing: 0.02em;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.tool-list {
|
|
|
|
|
- list-style: none;
|
|
|
|
|
- margin: 0;
|
|
|
|
|
- padding: 0;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- gap: 12px;
|
|
|
|
|
|
|
+ color: #f1f5f9;
|
|
|
|
|
+ font-size: 1.05rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.tool-entry {
|
|
|
|
|
- background: rgba(248, 250, 252, 0.95);
|
|
|
|
|
- border: 1px solid rgba(148, 163, 184, 0.24);
|
|
|
|
|
- border-radius: 14px;
|
|
|
|
|
- padding: 14px;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- gap: 10px;
|
|
|
|
|
|
|
+.task-intent {
|
|
|
|
|
+ font-size: 0.8rem;
|
|
|
|
|
+ color: #94a3b8;
|
|
|
|
|
+ background: rgba(0, 0, 0, 0.2);
|
|
|
|
|
+ padding: 2px 8px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.tool-entry-header {
|
|
|
|
|
|
|
+.production-header {
|
|
|
|
|
+ width: 100%;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- flex-wrap: wrap;
|
|
|
|
|
- gap: 8px;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 3rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.tool-entry-title {
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- color: #1f2937;
|
|
|
|
|
|
|
+.production-header h2 {
|
|
|
|
|
+ font-size: 1.5rem;
|
|
|
|
|
+ margin: 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.tool-entry-note {
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
- color: #0f766e;
|
|
|
|
|
|
|
+.cancel-btn {
|
|
|
|
|
+ background: transparent;
|
|
|
|
|
+ border: 1px solid rgba(239, 68, 68, 0.5);
|
|
|
|
|
+ color: #fca5a5;
|
|
|
|
|
+ padding: 0.5rem 1rem;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ cursor: pointer;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.tool-entry-path {
|
|
|
|
|
- margin: 0;
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
|
|
+.stage-monitor {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
- gap: 6px;
|
|
|
|
|
- color: #2563eb;
|
|
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ margin-bottom: 3rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.tool-subtitle {
|
|
|
|
|
- margin: 0;
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
- color: #475569;
|
|
|
|
|
- font-weight: 500;
|
|
|
|
|
|
|
+.stage-step {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 0.5rem;
|
|
|
|
|
+ opacity: 0.4;
|
|
|
|
|
+ transition: opacity 0.3s;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.tool-pre {
|
|
|
|
|
- font-family: "JetBrains Mono", "Fira Code", ui-monospace, SFMono-Regular,
|
|
|
|
|
- Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
- line-height: 1.6;
|
|
|
|
|
- white-space: pre-wrap;
|
|
|
|
|
- word-break: break-word;
|
|
|
|
|
- color: #1f2937;
|
|
|
|
|
- background: rgba(248, 250, 252, 0.9);
|
|
|
|
|
- padding: 12px;
|
|
|
|
|
- border-radius: 12px;
|
|
|
|
|
- border: 1px solid rgba(148, 163, 184, 0.28);
|
|
|
|
|
- overflow: auto;
|
|
|
|
|
- max-height: 260px;
|
|
|
|
|
- scrollbar-width: thin;
|
|
|
|
|
- scrollbar-color: rgba(129, 140, 248, 0.6) rgba(226, 232, 240, 0.7);
|
|
|
|
|
|
|
+.stage-step.active, .stage-step.completed {
|
|
|
|
|
+ opacity: 1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.tool-pre::-webkit-scrollbar {
|
|
|
|
|
- width: 6px;
|
|
|
|
|
|
|
+.step-icon {
|
|
|
|
|
+ width: 48px;
|
|
|
|
|
+ height: 48px;
|
|
|
|
|
+ background: #1e293b;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ font-size: 1.5rem;
|
|
|
|
|
+ border: 2px solid transparent;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.tool-pre::-webkit-scrollbar-track {
|
|
|
|
|
- background: rgba(226, 232, 240, 0.7);
|
|
|
|
|
|
|
+.stage-step.active .step-icon {
|
|
|
|
|
+ border-color: #60a5fa;
|
|
|
|
|
+ box-shadow: 0 0 15px rgba(96, 165, 250, 0.3);
|
|
|
|
|
+ animation: pulseIcon 1.5s infinite;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.tool-pre::-webkit-scrollbar-thumb {
|
|
|
|
|
- background: rgba(99, 102, 241, 0.7);
|
|
|
|
|
- border-radius: 10px;
|
|
|
|
|
|
|
+.stage-step.completed .step-icon {
|
|
|
|
|
+ background: #10b981;
|
|
|
|
|
+ color: #fff;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.link-btn {
|
|
|
|
|
- background: none;
|
|
|
|
|
- border: none;
|
|
|
|
|
- color: #0369a1;
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
- padding: 0 4px;
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
- border-radius: 8px;
|
|
|
|
|
- transition: color 0.2s ease, background 0.2s ease;
|
|
|
|
|
|
|
+@keyframes pulseIcon {
|
|
|
|
|
+ 0% { transform: scale(1); }
|
|
|
|
|
+ 50% { transform: scale(1.05); }
|
|
|
|
|
+ 100% { transform: scale(1); }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.link-btn:hover {
|
|
|
|
|
- color: #0ea5e9;
|
|
|
|
|
- background: rgba(14, 165, 233, 0.16);
|
|
|
|
|
|
|
+.stage-line {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ height: 2px;
|
|
|
|
|
+ background: #334155;
|
|
|
|
|
+ margin: 0 1rem;
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ top: -14px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-
|
|
|
|
|
-.sources-block,
|
|
|
|
|
-.summary-block {
|
|
|
|
|
- position: relative;
|
|
|
|
|
- margin-top: 16px;
|
|
|
|
|
- padding: 18px;
|
|
|
|
|
- border-radius: 18px;
|
|
|
|
|
- background: rgba(255, 255, 255, 0.94);
|
|
|
|
|
- border: 1px solid rgba(148, 163, 184, 0.18);
|
|
|
|
|
- box-shadow: inset 0 0 0 1px rgba(226, 232, 240, 0.4);
|
|
|
|
|
|
|
+.terminal-log {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ background: #000;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ padding: 1rem;
|
|
|
|
|
+ font-family: 'Fira Code', monospace;
|
|
|
|
|
+ font-size: 0.9rem;
|
|
|
|
|
+ height: 150px;
|
|
|
|
|
+ margin-bottom: 2rem;
|
|
|
|
|
+ border: 1px solid #333;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.sources-history {
|
|
|
|
|
- margin-top: 16px;
|
|
|
|
|
|
|
+.log-content {
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
|
- gap: 10px;
|
|
|
|
|
|
|
+ gap: 0.5rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.sources-history h4 {
|
|
|
|
|
- margin: 0;
|
|
|
|
|
- color: #1f2937;
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- letter-spacing: 0.01em;
|
|
|
|
|
|
|
+.log-entry {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 1rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.history-list {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- gap: 10px;
|
|
|
|
|
|
|
+.log-time {
|
|
|
|
|
+ color: #64748b;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.history-list details {
|
|
|
|
|
- background: rgba(248, 250, 252, 0.95);
|
|
|
|
|
- border: 1px solid rgba(148, 163, 184, 0.24);
|
|
|
|
|
- border-radius: 14px;
|
|
|
|
|
- padding: 12px 16px;
|
|
|
|
|
- color: #1f2937;
|
|
|
|
|
- transition: border-color 0.2s ease, background 0.2s ease;
|
|
|
|
|
|
|
+.log-msg {
|
|
|
|
|
+ color: #e2e8f0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.history-list details[open] {
|
|
|
|
|
- background: rgba(224, 231, 255, 0.55);
|
|
|
|
|
- border-color: rgba(129, 140, 248, 0.4);
|
|
|
|
|
|
|
+.research-preview {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ background: rgba(30, 41, 59, 0.5);
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ padding: 1rem;
|
|
|
|
|
+ border-left: 4px solid #60a5fa;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.history-list summary {
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- outline: none;
|
|
|
|
|
- list-style: none;
|
|
|
|
|
|
|
+.preview-header {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
|
|
+ gap: 0.5rem;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
- justify-content: space-between;
|
|
|
|
|
|
|
+ margin-bottom: 0.5rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.history-list summary::-webkit-details-marker {
|
|
|
|
|
- display: none;
|
|
|
|
|
|
|
+.badge {
|
|
|
|
|
+ background: #2563eb;
|
|
|
|
|
+ font-size: 0.7rem;
|
|
|
|
|
+ padding: 2px 6px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ text-transform: uppercase;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.history-list summary::after {
|
|
|
|
|
- content: "▾";
|
|
|
|
|
- margin-left: 6px;
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
- opacity: 0.7;
|
|
|
|
|
- transition: transform 0.2s ease;
|
|
|
|
|
|
|
+.task-title {
|
|
|
|
|
+ font-weight: 600;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.history-list details[open] summary::after {
|
|
|
|
|
- transform: rotate(180deg);
|
|
|
|
|
|
|
+.preview-body {
|
|
|
|
|
+ color: #94a3b8;
|
|
|
|
|
+ font-size: 0.9rem;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.block-highlight {
|
|
|
|
|
- animation: glow 1.2s ease;
|
|
|
|
|
|
|
+/* --- Player View --- */
|
|
|
|
|
+.view-player {
|
|
|
|
|
+ padding: 0;
|
|
|
|
|
+ overflow: hidden; /* Player view handles internal scrolling */
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.sources-block h3,
|
|
|
|
|
-.summary-block h3 {
|
|
|
|
|
- margin: 0 0 14px;
|
|
|
|
|
- color: #1f2937;
|
|
|
|
|
- letter-spacing: 0.02em;
|
|
|
|
|
|
|
+.player-layout {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ width: 100%;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.sources-list {
|
|
|
|
|
- list-style: none;
|
|
|
|
|
- margin: 0;
|
|
|
|
|
- padding: 0;
|
|
|
|
|
|
|
+.player-sidebar {
|
|
|
|
|
+ width: 400px;
|
|
|
|
|
+ background: #0f172a;
|
|
|
|
|
+ border-right: 1px solid #1e293b;
|
|
|
|
|
+ padding: 2rem;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
|
- gap: 10px;
|
|
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ z-index: 2;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.source-item {
|
|
|
|
|
- position: relative;
|
|
|
|
|
- display: inline-flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- gap: 6px;
|
|
|
|
|
|
|
+.back-home-btn {
|
|
|
|
|
+ align-self: flex-start;
|
|
|
|
|
+ background: none;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ color: #64748b;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ margin-bottom: 2rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.source-link {
|
|
|
|
|
- color: #2563eb;
|
|
|
|
|
- text-decoration: none;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- letter-spacing: 0.01em;
|
|
|
|
|
- transition: color 0.2s ease;
|
|
|
|
|
|
|
+.back-home-btn:hover {
|
|
|
|
|
+ color: #fff;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.source-link::after {
|
|
|
|
|
- content: " ↗";
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
- opacity: 0.6;
|
|
|
|
|
|
|
+.album-art {
|
|
|
|
|
+ width: 260px;
|
|
|
|
|
+ height: 260px;
|
|
|
|
|
+ margin-bottom: 2rem;
|
|
|
|
|
+ position: relative;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.source-link:hover {
|
|
|
|
|
- color: #0f172a;
|
|
|
|
|
|
|
+.vinyl-record {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ background: radial-gradient(circle, #222 20%, #111 21%, #111 30%, #222 31%, #222 60%, #111 61%);
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ box-shadow: 0 10px 30px rgba(0,0,0,0.5);
|
|
|
|
|
+ border: 4px solid #333;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.source-tooltip {
|
|
|
|
|
- display: none;
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- bottom: calc(100% + 12px);
|
|
|
|
|
- left: 50%;
|
|
|
|
|
- transform: translateX(-50%);
|
|
|
|
|
- background: rgba(255, 255, 255, 0.98);
|
|
|
|
|
- color: #1f2937;
|
|
|
|
|
- padding: 14px 16px;
|
|
|
|
|
- border-radius: 16px;
|
|
|
|
|
- box-shadow: 0 18px 32px rgba(15, 23, 42, 0.18);
|
|
|
|
|
- width: min(420px, 90vw);
|
|
|
|
|
- z-index: 20;
|
|
|
|
|
- border: 1px solid rgba(148, 163, 184, 0.24);
|
|
|
|
|
|
|
+.vinyl-record.spinning {
|
|
|
|
|
+ animation: spin 5s linear infinite;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.source-tooltip::after {
|
|
|
|
|
- content: "";
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- top: 100%;
|
|
|
|
|
- left: 50%;
|
|
|
|
|
- transform: translateX(-50%);
|
|
|
|
|
- border-width: 10px;
|
|
|
|
|
- border-style: solid;
|
|
|
|
|
- border-color: rgba(255, 255, 255, 0.98) transparent transparent transparent;
|
|
|
|
|
|
|
+@keyframes spin {
|
|
|
|
|
+ from { transform: rotate(0deg); }
|
|
|
|
|
+ to { transform: rotate(360deg); }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.source-tooltip::before {
|
|
|
|
|
- content: "";
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- bottom: -12px;
|
|
|
|
|
- left: 50%;
|
|
|
|
|
- transform: translateX(-50%);
|
|
|
|
|
- border-width: 12px 10px 0 10px;
|
|
|
|
|
- border-style: solid;
|
|
|
|
|
- border-color: rgba(255, 255, 255, 0.98) transparent transparent transparent;
|
|
|
|
|
- filter: drop-shadow(0 -2px 4px rgba(15, 23, 42, 0.12));
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-.source-tooltip p {
|
|
|
|
|
- margin: 0 0 8px;
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
- line-height: 1.6;
|
|
|
|
|
|
|
+.vinyl-label {
|
|
|
|
|
+ width: 100px;
|
|
|
|
|
+ height: 100px;
|
|
|
|
|
+ background: #60a5fa;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ font-size: 1.5rem;
|
|
|
|
|
+ color: #fff;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.source-tooltip p:last-child {
|
|
|
|
|
- margin-bottom: 0;
|
|
|
|
|
|
|
+.track-info {
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ margin-bottom: 2rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.muted-text {
|
|
|
|
|
- color: #64748b;
|
|
|
|
|
|
|
+.track-info h3 {
|
|
|
|
|
+ font-size: 1.25rem;
|
|
|
|
|
+ margin-bottom: 0.5rem;
|
|
|
|
|
+ background: none;
|
|
|
|
|
+ -webkit-text-fill-color: initial;
|
|
|
|
|
+ color: #fff;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.source-item:hover .source-tooltip,
|
|
|
|
|
-.source-item:focus-within .source-tooltip {
|
|
|
|
|
- display: block;
|
|
|
|
|
|
|
+.audio-controls {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 1.5rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.hint.muted {
|
|
|
|
|
- color: #64748b;
|
|
|
|
|
|
|
+.control-buttons {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ gap: 1.5rem;
|
|
|
|
|
+ align-items: center;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-@keyframes float {
|
|
|
|
|
- 0% {
|
|
|
|
|
- transform: translate3d(0, 0, 0) rotate(0deg);
|
|
|
|
|
- }
|
|
|
|
|
- 50% {
|
|
|
|
|
- transform: translate3d(10%, 6%, 0) rotate(3deg);
|
|
|
|
|
- }
|
|
|
|
|
- 100% {
|
|
|
|
|
- transform: translate3d(0, 0, 0) rotate(0deg);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+.play-btn {
|
|
|
|
|
+ width: 64px;
|
|
|
|
|
+ height: 64px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ color: #0f172a;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ font-size: 1.5rem;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ transition: transform 0.1s;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-@keyframes spin {
|
|
|
|
|
- to {
|
|
|
|
|
- transform: rotate(360deg);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+.play-btn:active {
|
|
|
|
|
+ transform: scale(0.95);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-@keyframes pulse {
|
|
|
|
|
- 0%,
|
|
|
|
|
- 100% {
|
|
|
|
|
- transform: scale(1);
|
|
|
|
|
- opacity: 1;
|
|
|
|
|
- }
|
|
|
|
|
- 50% {
|
|
|
|
|
- transform: scale(1.3);
|
|
|
|
|
- opacity: 0.5;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+.download-btn {
|
|
|
|
|
+ width: 40px;
|
|
|
|
|
+ height: 40px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background: rgba(255,255,255,0.1);
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ text-decoration: none;
|
|
|
|
|
+ font-size: 1.2rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-@keyframes glow {
|
|
|
|
|
- 0% {
|
|
|
|
|
- box-shadow: 0 0 0 rgba(59, 130, 246, 0.3);
|
|
|
|
|
- border-color: rgba(59, 130, 246, 0.5);
|
|
|
|
|
- }
|
|
|
|
|
- 100% {
|
|
|
|
|
- box-shadow: inset 0 0 0 1px rgba(59, 130, 246, 0.12);
|
|
|
|
|
- border-color: rgba(148, 163, 184, 0.2);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+.progress-bar-wrapper {
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ padding: 10px 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-@media (max-width: 960px) {
|
|
|
|
|
- .app-shell {
|
|
|
|
|
- padding: 56px 16px;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .layout {
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- align-items: stretch;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .panel {
|
|
|
|
|
- padding: 22px;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .panel-form,
|
|
|
|
|
- .panel-result {
|
|
|
|
|
- max-width: none;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .status-bar {
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- align-items: flex-start;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- .status-main,
|
|
|
|
|
- .status-controls {
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+.progress-bar-bg {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 4px;
|
|
|
|
|
+ background: #334155;
|
|
|
|
|
+ border-radius: 2px;
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- .status-controls {
|
|
|
|
|
- justify-content: flex-start;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+.progress-bar-fill {
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ background: #60a5fa;
|
|
|
|
|
+ border-radius: 2px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-@media (max-width: 600px) {
|
|
|
|
|
- .options {
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+.time-display {
|
|
|
|
|
+ font-size: 0.75rem;
|
|
|
|
|
+ color: #64748b;
|
|
|
|
|
+ margin-top: 0.5rem;
|
|
|
|
|
+ text-align: right;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- .status-meta {
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+.report-toggle {
|
|
|
|
|
+ margin-top: auto;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- .panel-head {
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- align-items: flex-start;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+.report-toggle button {
|
|
|
|
|
+ background: none;
|
|
|
|
|
+ border: 1px solid #334155;
|
|
|
|
|
+ color: #94a3b8;
|
|
|
|
|
+ padding: 0.5rem 1rem;
|
|
|
|
|
+ border-radius: 20px;
|
|
|
|
|
+ font-size: 0.8rem;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- .panel-form h1 {
|
|
|
|
|
- font-size: 24px;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+.content-main {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ background: #1e293b;
|
|
|
|
|
+ padding: 2rem;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* 侧边栏样式 */
|
|
|
|
|
-.sidebar {
|
|
|
|
|
- width: 400px;
|
|
|
|
|
- min-width: 400px;
|
|
|
|
|
- height: 100vh;
|
|
|
|
|
- background: rgba(255, 255, 255, 0.98);
|
|
|
|
|
- border-right: 1px solid rgba(148, 163, 184, 0.2);
|
|
|
|
|
- padding: 32px 24px;
|
|
|
|
|
|
|
+/* Chat UI */
|
|
|
|
|
+.script-chat {
|
|
|
|
|
+ max-width: 800px;
|
|
|
|
|
+ margin: 0 auto;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
|
- gap: 24px;
|
|
|
|
|
- overflow-y: auto;
|
|
|
|
|
- box-shadow: 4px 0 24px rgba(15, 23, 42, 0.08);
|
|
|
|
|
|
|
+ gap: 1.5rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.sidebar-header {
|
|
|
|
|
|
|
+.chat-bubble {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- flex-direction: column;
|
|
|
|
|
- gap: 16px;
|
|
|
|
|
|
|
+ gap: 1rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.sidebar-header h2 {
|
|
|
|
|
- font-size: 24px;
|
|
|
|
|
- font-weight: 700;
|
|
|
|
|
- margin: 0;
|
|
|
|
|
- color: #1f2937;
|
|
|
|
|
|
|
+.chat-bubble.host {
|
|
|
|
|
+ flex-direction: row;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.back-btn {
|
|
|
|
|
|
|
+.chat-bubble.guest {
|
|
|
|
|
+ flex-direction: row-reverse;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.avatar {
|
|
|
|
|
+ width: 40px;
|
|
|
|
|
+ height: 40px;
|
|
|
|
|
+ border-radius: 50%;
|
|
|
|
|
+ background: #3b82f6;
|
|
|
|
|
+ color: #fff;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
- gap: 8px;
|
|
|
|
|
- padding: 10px 16px;
|
|
|
|
|
- background: transparent;
|
|
|
|
|
- border: 1px solid rgba(148, 163, 184, 0.3);
|
|
|
|
|
- border-radius: 12px;
|
|
|
|
|
- color: #64748b;
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- font-weight: 500;
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
- transition: all 0.2s ease;
|
|
|
|
|
- width: fit-content;
|
|
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.back-btn:hover:not(:disabled) {
|
|
|
|
|
- background: rgba(59, 130, 246, 0.1);
|
|
|
|
|
- border-color: #3b82f6;
|
|
|
|
|
- color: #3b82f6;
|
|
|
|
|
|
|
+.chat-bubble.guest .avatar {
|
|
|
|
|
+ background: #8b5cf6;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.back-btn:disabled {
|
|
|
|
|
- opacity: 0.5;
|
|
|
|
|
- cursor: not-allowed;
|
|
|
|
|
|
|
+.bubble-content {
|
|
|
|
|
+ background: #334155;
|
|
|
|
|
+ padding: 1rem;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ border-top-left-radius: 2px;
|
|
|
|
|
+ max-width: 80%;
|
|
|
|
|
+ line-height: 1.6;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.research-info {
|
|
|
|
|
- flex: 1;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- gap: 20px;
|
|
|
|
|
|
|
+.chat-bubble.guest .bubble-content {
|
|
|
|
|
+ background: #475569;
|
|
|
|
|
+ border-radius: 12px;
|
|
|
|
|
+ border-top-right-radius: 2px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.info-item {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- gap: 8px;
|
|
|
|
|
|
|
+.speaker-name {
|
|
|
|
|
+ font-size: 0.75rem;
|
|
|
|
|
+ color: #94a3b8;
|
|
|
|
|
+ margin-bottom: 0.25rem;
|
|
|
|
|
+ text-transform: uppercase;
|
|
|
|
|
+ font-weight: 600;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.info-item label {
|
|
|
|
|
- font-size: 12px;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- text-transform: uppercase;
|
|
|
|
|
- letter-spacing: 0.5px;
|
|
|
|
|
- color: #64748b;
|
|
|
|
|
|
|
+.markdown-report {
|
|
|
|
|
+ max-width: 800px;
|
|
|
|
|
+ margin: 0 auto;
|
|
|
|
|
+ color: #e2e8f0;
|
|
|
|
|
+ line-height: 1.7;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.info-item p {
|
|
|
|
|
- margin: 0;
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- color: #1f2937;
|
|
|
|
|
|
|
+.task-summary {
|
|
|
|
|
+ font-size: 0.85rem;
|
|
|
|
|
+ color: #cbd5e1;
|
|
|
|
|
+ margin-top: 0.5rem;
|
|
|
|
|
+ padding-top: 0.5rem;
|
|
|
|
|
+ border-top: 1px dashed rgba(255, 255, 255, 0.1);
|
|
|
line-height: 1.6;
|
|
line-height: 1.6;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.topic-display {
|
|
|
|
|
- font-size: 16px !important;
|
|
|
|
|
|
|
+.task-summary :deep(h1),
|
|
|
|
|
+.task-summary :deep(h2),
|
|
|
|
|
+.task-summary :deep(h3),
|
|
|
|
|
+.task-summary :deep(h4) {
|
|
|
|
|
+ font-size: 0.95rem;
|
|
|
|
|
+ font-weight: 700;
|
|
|
|
|
+ margin-top: 0.8rem;
|
|
|
|
|
+ margin-bottom: 0.4rem;
|
|
|
|
|
+ color: #e2e8f0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.task-summary :deep(p) {
|
|
|
|
|
+ margin-bottom: 0.6rem;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.task-summary :deep(ul),
|
|
|
|
|
+.task-summary :deep(ol) {
|
|
|
|
|
+ padding-left: 1.2rem;
|
|
|
|
|
+ margin-bottom: 0.6rem;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.task-summary :deep(li) {
|
|
|
|
|
+ margin-bottom: 0.3rem;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.task-summary :deep(strong) {
|
|
|
|
|
+ color: #60a5fa;
|
|
|
font-weight: 600;
|
|
font-weight: 600;
|
|
|
- color: #0f172a !important;
|
|
|
|
|
- padding: 12px;
|
|
|
|
|
- background: rgba(59, 130, 246, 0.05);
|
|
|
|
|
- border-radius: 8px;
|
|
|
|
|
- border-left: 3px solid #3b82f6;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.progress-bar {
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- height: 8px;
|
|
|
|
|
- background: rgba(148, 163, 184, 0.2);
|
|
|
|
|
- border-radius: 4px;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
|
|
+.task-summary :deep(code) {
|
|
|
|
|
+ background: rgba(0, 0, 0, 0.3);
|
|
|
|
|
+ padding: 2px 4px;
|
|
|
|
|
+ border-radius: 3px;
|
|
|
|
|
+ font-family: 'Fira Code', monospace;
|
|
|
|
|
+ font-size: 0.8em;
|
|
|
|
|
+ color: #f472b6;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.progress-fill {
|
|
|
|
|
- height: 100%;
|
|
|
|
|
- background: linear-gradient(90deg, #3b82f6, #8b5cf6);
|
|
|
|
|
- border-radius: 4px;
|
|
|
|
|
- transition: width 0.5s ease;
|
|
|
|
|
|
|
+.report-content {
|
|
|
|
|
+ line-height: 1.8;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.progress-text {
|
|
|
|
|
- font-size: 13px !important;
|
|
|
|
|
- color: #64748b !important;
|
|
|
|
|
- font-weight: 500;
|
|
|
|
|
|
|
+.report-content :deep(h1) {
|
|
|
|
|
+ font-size: 1.8rem;
|
|
|
|
|
+ margin-bottom: 1.5rem;
|
|
|
|
|
+ color: #60a5fa;
|
|
|
|
|
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
|
|
+ padding-bottom: 0.5rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.sidebar-actions {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- gap: 12px;
|
|
|
|
|
- padding-top: 16px;
|
|
|
|
|
- border-top: 1px solid rgba(148, 163, 184, 0.2);
|
|
|
|
|
|
|
+.report-content :deep(h2) {
|
|
|
|
|
+ font-size: 1.4rem;
|
|
|
|
|
+ margin-top: 2rem;
|
|
|
|
|
+ margin-bottom: 1rem;
|
|
|
|
|
+ color: #c084fc;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.new-research-btn {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- justify-content: center;
|
|
|
|
|
- gap: 8px;
|
|
|
|
|
- padding: 14px 20px;
|
|
|
|
|
- background: linear-gradient(135deg, #3b82f6, #8b5cf6);
|
|
|
|
|
- border: none;
|
|
|
|
|
- border-radius: 12px;
|
|
|
|
|
- color: white;
|
|
|
|
|
- font-size: 15px;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- cursor: pointer;
|
|
|
|
|
- transition: all 0.3s ease;
|
|
|
|
|
- box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
|
|
|
|
|
|
|
+.report-content :deep(h3) {
|
|
|
|
|
+ font-size: 1.1rem;
|
|
|
|
|
+ margin-top: 1.5rem;
|
|
|
|
|
+ margin-bottom: 0.8rem;
|
|
|
|
|
+ color: #e2e8f0;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.new-research-btn:hover {
|
|
|
|
|
- transform: translateY(-2px);
|
|
|
|
|
- box-shadow: 0 6px 20px rgba(59, 130, 246, 0.4);
|
|
|
|
|
|
|
+.report-content :deep(p) {
|
|
|
|
|
+ margin-bottom: 1rem;
|
|
|
|
|
+ color: #cbd5e1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.new-research-btn:active {
|
|
|
|
|
- transform: translateY(0);
|
|
|
|
|
|
|
+.report-content :deep(ul),
|
|
|
|
|
+.report-content :deep(ol) {
|
|
|
|
|
+ padding-left: 1.5rem;
|
|
|
|
|
+ margin-bottom: 1rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-/* 全屏状态下的结果面板 */
|
|
|
|
|
-.layout-fullscreen .panel-result {
|
|
|
|
|
- flex: 1;
|
|
|
|
|
- height: 100vh;
|
|
|
|
|
- border-radius: 0;
|
|
|
|
|
- border: none;
|
|
|
|
|
- overflow-y: auto;
|
|
|
|
|
- max-width: none;
|
|
|
|
|
|
|
+.report-content :deep(li) {
|
|
|
|
|
+ margin-bottom: 0.5rem;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-@media (max-width: 1024px) {
|
|
|
|
|
- .sidebar {
|
|
|
|
|
- width: 320px;
|
|
|
|
|
- min-width: 320px;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+.report-content :deep(strong) {
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ font-weight: 600;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-@media (max-width: 768px) {
|
|
|
|
|
- .layout-fullscreen {
|
|
|
|
|
- flex-direction: column;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+.report-content :deep(blockquote) {
|
|
|
|
|
+ border-left: 4px solid #60a5fa;
|
|
|
|
|
+ padding-left: 1rem;
|
|
|
|
|
+ margin: 1rem 0;
|
|
|
|
|
+ color: #94a3b8;
|
|
|
|
|
+ font-style: italic;
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.05);
|
|
|
|
|
+ padding: 0.5rem 1rem;
|
|
|
|
|
+ border-radius: 0 4px 4px 0;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- .sidebar {
|
|
|
|
|
- width: 100%;
|
|
|
|
|
- min-width: 100%;
|
|
|
|
|
- height: auto;
|
|
|
|
|
- max-height: 40vh;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+/* Transitions */
|
|
|
|
|
+.fade-enter-active, .fade-leave-active {
|
|
|
|
|
+ transition: opacity 0.3s ease;
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
- .layout-fullscreen .panel-result {
|
|
|
|
|
- height: 60vh;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+.fade-enter-from, .fade-leave-to {
|
|
|
|
|
+ opacity: 0;
|
|
|
}
|
|
}
|
|
|
</style>
|
|
</style>
|