fix(eslint): reduce warnings in GitHub Actions deployment
- Disable formatting rules (handled by Prettier) - Relaxed strict Vue/JS rules for demo code compatibility - Fix syntax errors in ApiPlayground and VoiceCloningDemo - Fix duplicate else-if condition in ApiPlayground - Fix Promise executor async pattern in AutoregressiveAudioDemo - Add TypeScript file support to ESLint config Warnings reduced from 295 to 251 problems. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
+36
-14
@@ -43,12 +43,15 @@ const totalHeight = computed(() => systemHeight.value + historyHeight.value + in
|
||||
<span class="value">{{ round }}</span>
|
||||
<span class="label">当前轮次</span>
|
||||
</div>
|
||||
<div class="stat-divider"></div>
|
||||
<div class="stat-divider" />
|
||||
<div class="stat-item">
|
||||
<span class="value" :class="{ error: isOverflow }">{{ totalTokens }}</span>
|
||||
<span
|
||||
class="value"
|
||||
:class="{ error: isOverflow }"
|
||||
>{{ totalTokens }}</span>
|
||||
<span class="label">Token 占用</span>
|
||||
</div>
|
||||
<div class="stat-divider"></div>
|
||||
<div class="stat-divider" />
|
||||
<div class="stat-item">
|
||||
<span class="value">${{ currentCost }}</span>
|
||||
<span class="label">本轮成本</span>
|
||||
@@ -61,11 +64,17 @@ const totalHeight = computed(() => systemHeight.value + historyHeight.value + in
|
||||
<!-- 上方预留空间给溢出提示 -->
|
||||
<div class="overflow-zone">
|
||||
<transition name="fade">
|
||||
<div v-if="isOverflow" class="overflow-badge">
|
||||
<div
|
||||
v-if="isOverflow"
|
||||
class="overflow-badge"
|
||||
>
|
||||
<span class="icon">🗑️</span>
|
||||
<span>溢出截断:前 {{ forgottenRounds }} 轮对话已被遗忘!</span>
|
||||
</div>
|
||||
<div v-else class="safe-badge">
|
||||
<div
|
||||
v-else
|
||||
class="safe-badge"
|
||||
>
|
||||
<span class="icon">✅</span>
|
||||
<span>记忆完整</span>
|
||||
</div>
|
||||
@@ -81,15 +90,23 @@ const totalHeight = computed(() => systemHeight.value + historyHeight.value + in
|
||||
<!-- 堆叠内容容器 -->
|
||||
<!-- 使用 flex-direction: column-reverse 让底部对齐 -->
|
||||
<div class="stack-container">
|
||||
|
||||
<!-- System (基座) -->
|
||||
<div class="block system" :style="{ height: `${systemHeight}%` }">
|
||||
<div
|
||||
class="block system"
|
||||
:style="{ height: `${systemHeight}%` }"
|
||||
>
|
||||
<span class="block-text">System Prompt ({{ systemPromptTokens }})</span>
|
||||
</div>
|
||||
|
||||
<!-- History (中间) -->
|
||||
<div class="block history" :style="{ height: `${historyHeight}%` }">
|
||||
<span class="block-text" v-if="historyHeight > 10">
|
||||
<div
|
||||
class="block history"
|
||||
:style="{ height: `${historyHeight}%` }"
|
||||
>
|
||||
<span
|
||||
v-if="historyHeight > 10"
|
||||
class="block-text"
|
||||
>
|
||||
History ({{ round - 1 }} rounds)
|
||||
</span>
|
||||
<!-- 溢出遮罩:当溢出时,History 的底部实际上是被“挤出去”的 -->
|
||||
@@ -103,10 +120,12 @@ const totalHeight = computed(() => systemHeight.value + historyHeight.value + in
|
||||
</div>
|
||||
|
||||
<!-- Input (最新) -->
|
||||
<div class="block input" :style="{ height: `${inputHeight}%` }">
|
||||
<div
|
||||
class="block input"
|
||||
:style="{ height: `${inputHeight}%` }"
|
||||
>
|
||||
<span class="block-text">New Input</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 溢出遮罩层:如果 totalHeight > 100%,显示一个红色的遮罩在顶部,表示这部分虽然生成了但塞不进去/或者旧的被挤走了 -->
|
||||
@@ -121,12 +140,12 @@ const totalHeight = computed(() => systemHeight.value + historyHeight.value + in
|
||||
<div class="slider-wrapper">
|
||||
<span class="slider-hint">拖动滑块增加对话轮次:</span>
|
||||
<input
|
||||
v-model.number="round"
|
||||
type="range"
|
||||
min="1"
|
||||
:max="maxRound"
|
||||
v-model.number="round"
|
||||
class="custom-slider"
|
||||
/>
|
||||
>
|
||||
<div class="slider-labels">
|
||||
<span>第 1 轮</span>
|
||||
<span>第 {{ maxRound }} 轮</span>
|
||||
@@ -137,7 +156,10 @@ const totalHeight = computed(() => systemHeight.value + historyHeight.value + in
|
||||
<p v-if="!isOverflow">
|
||||
💡 <strong>一切正常</strong>:当前 Token 数 ({{ totalTokens }}) 未超过窗口限制。模型能完美回忆起所有对话细节。
|
||||
</p>
|
||||
<p v-else class="warning-text">
|
||||
<p
|
||||
v-else
|
||||
class="warning-text"
|
||||
>
|
||||
⚠️ <strong>发生遗忘</strong>:Token 总量 ({{ totalTokens }}) 已超过窗口限制 ({{ windowLimit }})。
|
||||
为了放入新对话,系统被迫丢弃了最早的 <strong>{{ forgottenRounds }}</strong> 轮历史记录。
|
||||
</p>
|
||||
|
||||
+44
-15
@@ -57,7 +57,9 @@ const compress = async (mode) => {
|
||||
<div class="context-compression-demo">
|
||||
<!-- 1. Strategy Selection -->
|
||||
<div class="section control-panel">
|
||||
<div class="section-label">1. 选择压缩策略</div>
|
||||
<div class="section-label">
|
||||
1. 选择压缩策略
|
||||
</div>
|
||||
<div class="strategy-group">
|
||||
<button
|
||||
v-for="s in strategies"
|
||||
@@ -66,8 +68,12 @@ const compress = async (mode) => {
|
||||
:class="{ active: currentMode === s.id }"
|
||||
@click="compress(s.id)"
|
||||
>
|
||||
<div class="btn-label">{{ s.label }}</div>
|
||||
<div class="btn-desc">{{ s.desc }}</div>
|
||||
<div class="btn-label">
|
||||
{{ s.label }}
|
||||
</div>
|
||||
<div class="btn-desc">
|
||||
{{ s.desc }}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -82,49 +88,72 @@ const compress = async (mode) => {
|
||||
v-model="originalText"
|
||||
class="text-content original-input"
|
||||
placeholder="在此输入长文本..."
|
||||
></textarea>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Connector / Process -->
|
||||
<div class="flow-connector">
|
||||
<div class="line"></div>
|
||||
<div class="process-icon" :class="{ spinning: isCompressing }">
|
||||
<div class="line" />
|
||||
<div
|
||||
class="process-icon"
|
||||
:class="{ spinning: isCompressing }"
|
||||
>
|
||||
{{ isCompressing ? '⚙️' : '⬇️' }}
|
||||
</div>
|
||||
<div class="badge-container" v-if="compressedText && !isCompressing">
|
||||
<div
|
||||
v-if="compressedText && !isCompressing"
|
||||
class="badge-container"
|
||||
>
|
||||
<span class="ratio-badge">-{{ compressionRatio }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. Output Area -->
|
||||
<div class="section output-area" :class="{ 'has-result': compressedText }">
|
||||
<div
|
||||
class="section output-area"
|
||||
:class="{ 'has-result': compressedText }"
|
||||
>
|
||||
<div class="section-header">
|
||||
<span class="label">压缩后 (Compressed)</span>
|
||||
<span class="token-count" v-if="compressedText">{{ compressedTokens }} tokens</span>
|
||||
<span
|
||||
v-if="compressedText"
|
||||
class="token-count"
|
||||
>{{ compressedTokens }} tokens</span>
|
||||
</div>
|
||||
|
||||
<div class="text-content result-box">
|
||||
<div v-if="isCompressing" class="loading-state">
|
||||
<span class="spinner"></span> 正在压缩...
|
||||
<div
|
||||
v-if="isCompressing"
|
||||
class="loading-state"
|
||||
>
|
||||
<span class="spinner" /> 正在压缩...
|
||||
</div>
|
||||
<pre v-else-if="compressedText">{{ compressedText }}</pre>
|
||||
<div v-else class="placeholder">
|
||||
<div
|
||||
v-else
|
||||
class="placeholder"
|
||||
>
|
||||
请点击上方按钮开始压缩
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mini Metrics (Inside Output Area) -->
|
||||
<div class="mini-metrics" v-if="compressedText && !isCompressing">
|
||||
<div
|
||||
v-if="compressedText && !isCompressing"
|
||||
class="mini-metrics"
|
||||
>
|
||||
<div class="metric-item">
|
||||
<span class="metric-label">节省空间</span>
|
||||
<span class="metric-val highlight">{{ compressionRatio }}%</span>
|
||||
</div>
|
||||
<div class="metric-bar">
|
||||
<div class="bar-fill" :style="{ width: (100 - compressionRatio) + '%' }"></div>
|
||||
<div
|
||||
class="bar-fill"
|
||||
:style="{ width: (100 - compressionRatio) + '%' }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
+39
-11
@@ -17,10 +17,15 @@
|
||||
<div class="control-panel">
|
||||
<div class="stat-group">
|
||||
<div class="stat-item">
|
||||
<span class="value" :class="{ error: isOverflow }">{{ usedTokens }}</span>
|
||||
<span
|
||||
class="value"
|
||||
:class="{ error: isOverflow }"
|
||||
>{{ usedTokens }}</span>
|
||||
<span class="label">已经写了多少个 token</span>
|
||||
</div>
|
||||
<div class="stat-divider">/</div>
|
||||
<div class="stat-divider">
|
||||
/
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="value">{{ maxTokens }}</span>
|
||||
<span class="label">黑板最多能写几个 token</span>
|
||||
@@ -35,14 +40,19 @@
|
||||
width: `${Math.min(usagePercentage, 100)}%`,
|
||||
backgroundColor: progressBarColor
|
||||
}"
|
||||
></div>
|
||||
/>
|
||||
</div>
|
||||
<div class="percentage-label">
|
||||
{{ usagePercentage.toFixed(1) }}%
|
||||
</div>
|
||||
<div class="percentage-label">{{ usagePercentage.toFixed(1) }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="visualization-area">
|
||||
<div class="window-frame" :class="{ overflow: isOverflow }">
|
||||
<div
|
||||
class="window-frame"
|
||||
:class="{ overflow: isOverflow }"
|
||||
>
|
||||
<div class="window-header">
|
||||
<span class="icon">🧠</span>
|
||||
<span>模型能看到的“小黑板”(上下文窗口)</span>
|
||||
@@ -61,8 +71,11 @@
|
||||
</transition-group>
|
||||
</div>
|
||||
|
||||
<div v-if="isOverflow" class="overflow-indicator">
|
||||
<div class="overflow-line"></div>
|
||||
<div
|
||||
v-if="isOverflow"
|
||||
class="overflow-indicator"
|
||||
>
|
||||
<div class="overflow-line" />
|
||||
<span class="overflow-text">⚠️ 达到上下文上限 (已截断)</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -72,16 +85,31 @@
|
||||
<div class="input-header">
|
||||
<label>输入内容(看黑板怎么被一点点写满)</label>
|
||||
<div class="actions">
|
||||
<button class="action-btn" @click="fillLorem(10)">填一段短文本</button>
|
||||
<button class="action-btn" @click="fillLorem(60)">一下子塞满黑板</button>
|
||||
<button class="action-btn outline" @click="clear">清空</button>
|
||||
<button
|
||||
class="action-btn"
|
||||
@click="fillLorem(10)"
|
||||
>
|
||||
填一段短文本
|
||||
</button>
|
||||
<button
|
||||
class="action-btn"
|
||||
@click="fillLorem(60)"
|
||||
>
|
||||
一下子塞满黑板
|
||||
</button>
|
||||
<button
|
||||
class="action-btn outline"
|
||||
@click="clear"
|
||||
>
|
||||
清空
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="inputText"
|
||||
placeholder="在这里输入几句话,看看小黑板是怎么逐渐被写满的..."
|
||||
rows="4"
|
||||
></textarea>
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
|
||||
+9
-3
@@ -1,7 +1,9 @@
|
||||
<template>
|
||||
<div class="intro-prs">
|
||||
<div class="prs-item">
|
||||
<div class="prs-title">问题</div>
|
||||
<div class="prs-title">
|
||||
问题
|
||||
</div>
|
||||
<ul>
|
||||
<li><strong>上下文难以保持一致</strong>:对话一长,前后语义容易脱节。</li>
|
||||
<li><strong>关键事实容易丢失</strong>:早期给出的信息在后续轮次中难以被准确引用。</li>
|
||||
@@ -9,7 +11,9 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="prs-item">
|
||||
<div class="prs-title">可能的成因</div>
|
||||
<div class="prs-title">
|
||||
可能的成因
|
||||
</div>
|
||||
<ul>
|
||||
<li><strong>视野仅限当前调用</strong>:模型只能依赖这一轮提供的上下文。</li>
|
||||
<li><strong>信息缺乏结构化组织</strong>:重要信息与次要细节混在一起,难以形成稳定记忆。</li>
|
||||
@@ -17,7 +21,9 @@
|
||||
</ul>
|
||||
</div>
|
||||
<div class="prs-item">
|
||||
<div class="prs-title">带来的影响</div>
|
||||
<div class="prs-title">
|
||||
带来的影响
|
||||
</div>
|
||||
<ul>
|
||||
<li><strong>回答质量不稳定</strong>:对话越长,模型越难保持一致性和可追溯性。</li>
|
||||
<li><strong>成本难以预估</strong>:每轮上下文大小高度波动,调用费用不可控。</li>
|
||||
|
||||
@@ -3,8 +3,12 @@
|
||||
<div class="control-panel">
|
||||
<div class="control-group">
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" v-model="isCacheEnabled" :disabled="isProcessing">
|
||||
<span class="slider"></span>
|
||||
<input
|
||||
v-model="isCacheEnabled"
|
||||
type="checkbox"
|
||||
:disabled="isProcessing"
|
||||
>
|
||||
<span class="slider" />
|
||||
</label>
|
||||
<span class="label">开启“背课文加速”(前缀复用 / KV Cache)</span>
|
||||
</div>
|
||||
@@ -27,12 +31,18 @@
|
||||
<div class="block-header">
|
||||
<span class="icon">⚙️</span>
|
||||
<span>固定开场白(System Prompt)</span>
|
||||
<span class="badge" v-if="isCacheEnabled && hasCache">已背过</span>
|
||||
<span
|
||||
v-if="isCacheEnabled && hasCache"
|
||||
class="badge"
|
||||
>已背过</span>
|
||||
</div>
|
||||
<div class="block-content">
|
||||
你是一个乐于助人的 AI 助手... (大约 500 个 token)
|
||||
</div>
|
||||
<div class="process-indicator" v-if="processingStep === 'system'">
|
||||
<div
|
||||
v-if="processingStep === 'system'"
|
||||
class="process-indicator"
|
||||
>
|
||||
计算中...
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,7 +59,10 @@
|
||||
<div class="block-content">
|
||||
用户:你好... (大约 200 个 token)
|
||||
</div>
|
||||
<div class="process-indicator" v-if="processingStep === 'history'">
|
||||
<div
|
||||
v-if="processingStep === 'history'"
|
||||
class="process-indicator"
|
||||
>
|
||||
计算中...
|
||||
</div>
|
||||
</div>
|
||||
@@ -66,7 +79,10 @@
|
||||
<div class="block-content">
|
||||
{{ currentQuery }} (大约 50 个 token)
|
||||
</div>
|
||||
<div class="process-indicator" v-if="processingStep === 'query'">
|
||||
<div
|
||||
v-if="processingStep === 'query'"
|
||||
class="process-indicator"
|
||||
>
|
||||
计算中...
|
||||
</div>
|
||||
</div>
|
||||
@@ -75,19 +91,35 @@
|
||||
|
||||
<div class="metrics-panel">
|
||||
<div class="metric-card">
|
||||
<div class="metric-value">{{ metrics.ttft }}ms</div>
|
||||
<div class="metric-label">开口速度(首字延迟 TTFT)</div>
|
||||
<div class="metric-diff" :class="{ 'good': metrics.savedTime > 0 }" v-if="metrics.savedTime > 0">
|
||||
<div class="metric-value">
|
||||
{{ metrics.ttft }}ms
|
||||
</div>
|
||||
<div class="metric-label">
|
||||
开口速度(首字延迟 TTFT)
|
||||
</div>
|
||||
<div
|
||||
v-if="metrics.savedTime > 0"
|
||||
class="metric-diff"
|
||||
:class="{ 'good': metrics.savedTime > 0 }"
|
||||
>
|
||||
节省 {{ metrics.savedTime }}ms
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-value">{{ metrics.processedTokens }}</div>
|
||||
<div class="metric-label">这次一共算了多少个 token</div>
|
||||
<div class="metric-value">
|
||||
{{ metrics.processedTokens }}
|
||||
</div>
|
||||
<div class="metric-label">
|
||||
这次一共算了多少个 token
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-value">{{ metrics.cost }}</div>
|
||||
<div class="metric-label">大致算力消耗(越少越省钱)</div>
|
||||
<div class="metric-value">
|
||||
{{ metrics.cost }}
|
||||
</div>
|
||||
<div class="metric-label">
|
||||
大致算力消耗(越少越省钱)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+38
-13
@@ -4,8 +4,8 @@
|
||||
<div class="control-group">
|
||||
<label>关键信息大概在整段话的哪个位置:{{ needlePosition }}%</label>
|
||||
<input
|
||||
type="range"
|
||||
v-model.number="needlePosition"
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
step="1"
|
||||
@@ -17,25 +17,37 @@
|
||||
<div class="visualization-area">
|
||||
<!-- Context Window Bar -->
|
||||
<div class="context-bar">
|
||||
<div class="context-label start">Start (System)</div>
|
||||
<div class="context-label end">End (Query)</div>
|
||||
<div class="context-label start">
|
||||
Start (System)
|
||||
</div>
|
||||
<div class="context-label end">
|
||||
End (Query)
|
||||
</div>
|
||||
|
||||
<!-- Attention Heatmap Background -->
|
||||
<div class="attention-heatmap"></div>
|
||||
<div class="attention-heatmap" />
|
||||
|
||||
<!-- Needle Marker -->
|
||||
<div
|
||||
class="needle-marker"
|
||||
:style="{ left: `${needlePosition}%` }"
|
||||
>
|
||||
<div class="needle-icon">📍</div>
|
||||
<div class="needle-tooltip">关键事实</div>
|
||||
<div class="needle-icon">
|
||||
📍
|
||||
</div>
|
||||
<div class="needle-tooltip">
|
||||
关键事实
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Probability Curve Chart -->
|
||||
<div class="chart-container">
|
||||
<svg viewBox="0 0 100 60" preserveAspectRatio="none" class="chart-svg">
|
||||
<svg
|
||||
viewBox="0 0 100 60"
|
||||
preserveAspectRatio="none"
|
||||
class="chart-svg"
|
||||
>
|
||||
<!-- U-Curve Path -->
|
||||
<path
|
||||
d="M 0 5 Q 50 55 100 5"
|
||||
@@ -52,21 +64,34 @@
|
||||
fill="var(--vp-c-brand)"
|
||||
/>
|
||||
</svg>
|
||||
<div class="chart-label y-axis">被记住的概率</div>
|
||||
<div class="chart-label x-axis">在上下文里的位置</div>
|
||||
<div class="chart-label y-axis">
|
||||
被记住的概率
|
||||
</div>
|
||||
<div class="chart-label x-axis">
|
||||
在上下文里的位置
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metrics-panel">
|
||||
<div class="metric-card">
|
||||
<div class="metric-value" :class="getScoreClass(retrievalProb)">
|
||||
<div
|
||||
class="metric-value"
|
||||
:class="getScoreClass(retrievalProb)"
|
||||
>
|
||||
{{ retrievalProb.toFixed(1) }}%
|
||||
</div>
|
||||
<div class="metric-label">检索成功率</div>
|
||||
<div class="metric-label">
|
||||
检索成功率
|
||||
</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-value">{{ positionLabel }}</div>
|
||||
<div class="metric-label">位置描述</div>
|
||||
<div class="metric-value">
|
||||
{{ positionLabel }}
|
||||
</div>
|
||||
<div class="metric-label">
|
||||
位置描述
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+70
-17
@@ -172,23 +172,47 @@ const getLayerStyle = (layerId) => {
|
||||
<div class="demo-grid">
|
||||
<!-- Left: Chat Simulator -->
|
||||
<div class="chat-panel">
|
||||
<div class="panel-header">📱 用户视角 (Chat)</div>
|
||||
<div class="panel-header">
|
||||
📱 用户视角 (Chat)
|
||||
</div>
|
||||
<div class="chat-window">
|
||||
<div v-for="(msg, idx) in currentStep.layers.chat" :key="idx" class="chat-bubble" :class="msg.startsWith('User') ? 'user' : 'ai'">
|
||||
<div
|
||||
v-for="(msg, idx) in currentStep.layers.chat"
|
||||
:key="idx"
|
||||
class="chat-bubble"
|
||||
:class="msg.startsWith('User') ? 'user' : 'ai'"
|
||||
>
|
||||
{{ msg.split(': ')[1] || msg }}
|
||||
</div>
|
||||
<div v-if="currentStep.user && !currentStep.layers.chat.some(m => m.includes(currentStep.user))" class="chat-bubble user pending">
|
||||
<div
|
||||
v-if="currentStep.user && !currentStep.layers.chat.some(m => m.includes(currentStep.user))"
|
||||
class="chat-bubble user pending"
|
||||
>
|
||||
{{ currentStep.user }}...
|
||||
</div>
|
||||
<div v-if="currentStep.ai_thinking" class="chat-bubble thinking">
|
||||
<div
|
||||
v-if="currentStep.ai_thinking"
|
||||
class="chat-bubble thinking"
|
||||
>
|
||||
💭 {{ currentStep.ai_thinking }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<div class="step-info">步骤 {{ currentStepIndex + 1 }} / {{ currentScenario.steps.length }}</div>
|
||||
<div class="step-info">
|
||||
步骤 {{ currentStepIndex + 1 }} / {{ currentScenario.steps.length }}
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="nav-btn" @click="prevStep" :disabled="currentStepIndex === 0">⬅️ 上一步</button>
|
||||
<button class="nav-btn primary" @click="nextStep">
|
||||
<button
|
||||
class="nav-btn"
|
||||
:disabled="currentStepIndex === 0"
|
||||
@click="prevStep"
|
||||
>
|
||||
⬅️ 上一步
|
||||
</button>
|
||||
<button
|
||||
class="nav-btn primary"
|
||||
@click="nextStep"
|
||||
>
|
||||
{{ isLastStep ? '🔄 重新演示' : '下一步 ➡️' }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -197,9 +221,10 @@ const getLayerStyle = (layerId) => {
|
||||
|
||||
<!-- Right: Memory Palace Internals -->
|
||||
<div class="palace-panel">
|
||||
<div class="panel-header">🧠 AI 视角 (Context Construction)</div>
|
||||
<div class="panel-header">
|
||||
🧠 AI 视角 (Context Construction)
|
||||
</div>
|
||||
<div class="context-visualizer">
|
||||
|
||||
<!-- Layer 1: Base -->
|
||||
<div class="layer-box base">
|
||||
<div class="layer-label">
|
||||
@@ -207,7 +232,9 @@ const getLayerStyle = (layerId) => {
|
||||
<span class="title">Layer 1: 地基 (System)</span>
|
||||
<span class="badge">KV Cached</span>
|
||||
</div>
|
||||
<div class="layer-content">{{ currentStep.layers.base }}</div>
|
||||
<div class="layer-content">
|
||||
{{ currentStep.layers.base }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Layer 2: Task -->
|
||||
@@ -217,7 +244,9 @@ const getLayerStyle = (layerId) => {
|
||||
<span class="title">Layer 2: 支柱 (Task)</span>
|
||||
<span class="badge">Pinned</span>
|
||||
</div>
|
||||
<div class="layer-content">{{ currentStep.layers.task }}</div>
|
||||
<div class="layer-content">
|
||||
{{ currentStep.layers.task }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Layer 3: Chat -->
|
||||
@@ -228,24 +257,48 @@ const getLayerStyle = (layerId) => {
|
||||
<span class="badge">Sliding</span>
|
||||
</div>
|
||||
<div class="layer-content">
|
||||
<div v-for="(m, i) in currentStep.layers.chat" :key="i" class="mini-line">{{ m }}</div>
|
||||
<div v-if="currentStep.layers.chat.length === 0" class="empty-hint">(暂无对话历史)</div>
|
||||
<div
|
||||
v-for="(m, i) in currentStep.layers.chat"
|
||||
:key="i"
|
||||
class="mini-line"
|
||||
>
|
||||
{{ m }}
|
||||
</div>
|
||||
<div
|
||||
v-if="currentStep.layers.chat.length === 0"
|
||||
class="empty-hint"
|
||||
>
|
||||
(暂无对话历史)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Layer 4: RAG -->
|
||||
<div class="layer-box rag" :class="{ active: currentStep.layers.rag.length > 0 }">
|
||||
<div
|
||||
class="layer-box rag"
|
||||
:class="{ active: currentStep.layers.rag.length > 0 }"
|
||||
>
|
||||
<div class="layer-label">
|
||||
<span class="icon">📚</span>
|
||||
<span class="title">Layer 4: 图书馆 (RAG)</span>
|
||||
<span class="badge ephemeral">Temp</span>
|
||||
</div>
|
||||
<div class="layer-content">
|
||||
<div v-for="(r, i) in currentStep.layers.rag" :key="i" class="rag-item">{{ r }}</div>
|
||||
<div v-if="currentStep.layers.rag.length === 0" class="empty-hint">(当前无需检索)</div>
|
||||
<div
|
||||
v-for="(r, i) in currentStep.layers.rag"
|
||||
:key="i"
|
||||
class="rag-item"
|
||||
>
|
||||
{{ r }}
|
||||
</div>
|
||||
<div
|
||||
v-if="currentStep.layers.rag.length === 0"
|
||||
class="empty-hint"
|
||||
>
|
||||
(当前无需检索)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Explanation Footer -->
|
||||
|
||||
+80
-22
@@ -59,7 +59,6 @@ const isComplete = computed(() => currentStep.value === 4)
|
||||
|
||||
<template>
|
||||
<div class="memory-palace-demo">
|
||||
|
||||
<!-- Visual Area -->
|
||||
<div class="palace-container">
|
||||
<div class="palace-stack">
|
||||
@@ -71,11 +70,20 @@ const isComplete = computed(() => currentStep.value === 4)
|
||||
<div class="layer-content">
|
||||
<span class="icon">{{ steps[3].icon }}</span>
|
||||
<div class="text">
|
||||
<div class="layer-title">{{ steps[3].title }}</div>
|
||||
<div class="layer-desc">{{ steps[3].desc }}</div>
|
||||
<div class="layer-title">
|
||||
{{ steps[3].title }}
|
||||
</div>
|
||||
<div class="layer-desc">
|
||||
{{ steps[3].desc }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layer-detail" v-if="currentStep >= 4">{{ steps[3].detail }}</div>
|
||||
<div
|
||||
v-if="currentStep >= 4"
|
||||
class="layer-detail"
|
||||
>
|
||||
{{ steps[3].detail }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Layer 3: Chat -->
|
||||
@@ -86,11 +94,20 @@ const isComplete = computed(() => currentStep.value === 4)
|
||||
<div class="layer-content">
|
||||
<span class="icon">{{ steps[2].icon }}</span>
|
||||
<div class="text">
|
||||
<div class="layer-title">{{ steps[2].title }}</div>
|
||||
<div class="layer-desc">{{ steps[2].desc }}</div>
|
||||
<div class="layer-title">
|
||||
{{ steps[2].title }}
|
||||
</div>
|
||||
<div class="layer-desc">
|
||||
{{ steps[2].desc }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layer-detail" v-if="currentStep >= 3">{{ steps[2].detail }}</div>
|
||||
<div
|
||||
v-if="currentStep >= 3"
|
||||
class="layer-detail"
|
||||
>
|
||||
{{ steps[2].detail }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Layer 2: Task -->
|
||||
@@ -101,11 +118,20 @@ const isComplete = computed(() => currentStep.value === 4)
|
||||
<div class="layer-content">
|
||||
<span class="icon">{{ steps[1].icon }}</span>
|
||||
<div class="text">
|
||||
<div class="layer-title">{{ steps[1].title }}</div>
|
||||
<div class="layer-desc">{{ steps[1].desc }}</div>
|
||||
<div class="layer-title">
|
||||
{{ steps[1].title }}
|
||||
</div>
|
||||
<div class="layer-desc">
|
||||
{{ steps[1].desc }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layer-detail" v-if="currentStep >= 2">{{ steps[1].detail }}</div>
|
||||
<div
|
||||
v-if="currentStep >= 2"
|
||||
class="layer-detail"
|
||||
>
|
||||
{{ steps[1].detail }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Layer 1: Base -->
|
||||
@@ -116,15 +142,27 @@ const isComplete = computed(() => currentStep.value === 4)
|
||||
<div class="layer-content">
|
||||
<span class="icon">{{ steps[0].icon }}</span>
|
||||
<div class="text">
|
||||
<div class="layer-title">{{ steps[0].title }}</div>
|
||||
<div class="layer-desc">{{ steps[0].desc }}</div>
|
||||
<div class="layer-title">
|
||||
{{ steps[0].title }}
|
||||
</div>
|
||||
<div class="layer-desc">
|
||||
{{ steps[0].desc }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layer-detail" v-if="currentStep >= 1">{{ steps[0].detail }}</div>
|
||||
<div
|
||||
v-if="currentStep >= 1"
|
||||
class="layer-detail"
|
||||
>
|
||||
{{ steps[0].detail }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Empty State Placeholder -->
|
||||
<div class="empty-placeholder" v-if="currentStep === 0">
|
||||
<div
|
||||
v-if="currentStep === 0"
|
||||
class="empty-placeholder"
|
||||
>
|
||||
🚧 空地:点击下方按钮开始建造记忆宫殿
|
||||
</div>
|
||||
</div>
|
||||
@@ -135,28 +173,48 @@ const isComplete = computed(() => currentStep.value === 4)
|
||||
<div class="step-indicator">
|
||||
当前进度: {{ currentStep }}/4
|
||||
</div>
|
||||
<button class="build-btn" @click="nextStep" :class="{ 'reset-mode': isComplete }">
|
||||
<button
|
||||
class="build-btn"
|
||||
:class="{ 'reset-mode': isComplete }"
|
||||
@click="nextStep"
|
||||
>
|
||||
{{ isComplete ? '🔄 重置重建' : (currentStep === 0 ? '🏗️ 开始建造' : '➕ 添加下一层') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Explanation Box -->
|
||||
<div class="explanation-box" v-if="currentStep > 0">
|
||||
<div class="exp-title">为什么这样设计?</div>
|
||||
<div class="exp-content" v-if="currentStep === 1">
|
||||
<div
|
||||
v-if="currentStep > 0"
|
||||
class="explanation-box"
|
||||
>
|
||||
<div class="exp-title">
|
||||
为什么这样设计?
|
||||
</div>
|
||||
<div
|
||||
v-if="currentStep === 1"
|
||||
class="exp-content"
|
||||
>
|
||||
**地基最稳**:把 System Prompt 放在最前面,利用 KV Cache 机制,让 AI "背下来",后续请求**速度快且免费**。
|
||||
</div>
|
||||
<div class="exp-content" v-if="currentStep === 2">
|
||||
<div
|
||||
v-if="currentStep === 2"
|
||||
class="exp-content"
|
||||
>
|
||||
**目标明确**:无论聊得多嗨,任务目标(如“写一个 Python 爬虫”)必须**钉死**,防止 AI 聊偏了。
|
||||
</div>
|
||||
<div class="exp-content" v-if="currentStep === 3">
|
||||
<div
|
||||
v-if="currentStep === 3"
|
||||
class="exp-content"
|
||||
>
|
||||
**保持鲜活**:最近的对话最重要,用滑动窗口保留,**旧的自动忘掉**,给新信息腾地方。
|
||||
</div>
|
||||
<div class="exp-content" v-if="currentStep === 4">
|
||||
<div
|
||||
v-if="currentStep === 4"
|
||||
class="exp-content"
|
||||
>
|
||||
**无限外脑**:遇到不懂的,不要瞎编,去“图书馆”查资料。**用完即走**,不占宝贵的脑容量。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
+65
-25
@@ -86,7 +86,6 @@ const search = async () => {
|
||||
|
||||
<template>
|
||||
<div class="rag-demo">
|
||||
|
||||
<!-- Step 1: User Input -->
|
||||
<div class="step-section input-section">
|
||||
<div class="step-label">
|
||||
@@ -98,13 +97,13 @@ const search = async () => {
|
||||
v-model="query"
|
||||
type="text"
|
||||
placeholder="输入问题..."
|
||||
@keyup.enter="search"
|
||||
:disabled="isSearching"
|
||||
/>
|
||||
@keyup.enter="search"
|
||||
>
|
||||
<button
|
||||
class="action-btn"
|
||||
@click="search"
|
||||
:disabled="isSearching || !query"
|
||||
@click="search"
|
||||
>
|
||||
{{ isSearching ? '检索中...' : '🚀 开始检索' }}
|
||||
</button>
|
||||
@@ -112,18 +111,32 @@ const search = async () => {
|
||||
</div>
|
||||
|
||||
<!-- Arrow Connection -->
|
||||
<div class="flow-arrow" :class="{ active: currentStep >= 1 }">
|
||||
<div class="line"></div>
|
||||
<div class="icon">🔍</div>
|
||||
<div
|
||||
class="flow-arrow"
|
||||
:class="{ active: currentStep >= 1 }"
|
||||
>
|
||||
<div class="line" />
|
||||
<div class="icon">
|
||||
🔍
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 2: Library Scanning -->
|
||||
<div class="step-section library-section" :class="{ 'is-scanning': currentStep === 1 }">
|
||||
<div
|
||||
class="step-section library-section"
|
||||
:class="{ 'is-scanning': currentStep === 1 }"
|
||||
>
|
||||
<div class="step-label">
|
||||
<span class="step-num">2</span>
|
||||
<span class="step-text">图书馆检索 (Retrieval)</span>
|
||||
<span class="status-badge" v-if="currentStep === 1">正在扫描...</span>
|
||||
<span class="status-badge success" v-if="currentStep >= 2">命中 {{ retrievedDocs.length }} 条</span>
|
||||
<span
|
||||
v-if="currentStep === 1"
|
||||
class="status-badge"
|
||||
>正在扫描...</span>
|
||||
<span
|
||||
v-if="currentStep >= 2"
|
||||
class="status-badge success"
|
||||
>命中 {{ retrievedDocs.length }} 条</span>
|
||||
</div>
|
||||
|
||||
<div class="docs-grid">
|
||||
@@ -139,26 +152,42 @@ const search = async () => {
|
||||
<div class="doc-header">
|
||||
<span class="doc-icon">📄</span>
|
||||
<span class="doc-title">{{ doc.title }}</span>
|
||||
<span class="doc-score" v-if="currentStep >= 2 && doc.score > 0.6">
|
||||
<span
|
||||
v-if="currentStep >= 2 && doc.score > 0.6"
|
||||
class="doc-score"
|
||||
>
|
||||
{{ (doc.score * 100).toFixed(0) }}% 相关
|
||||
</span>
|
||||
</div>
|
||||
<div class="doc-content">{{ doc.content }}</div>
|
||||
<div class="doc-content">
|
||||
{{ doc.content }}
|
||||
</div>
|
||||
|
||||
<!-- Visual effect for scanning -->
|
||||
<div class="scan-line" v-if="currentStep === 1"></div>
|
||||
<div
|
||||
v-if="currentStep === 1"
|
||||
class="scan-line"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Arrow Connection -->
|
||||
<div class="flow-arrow" :class="{ active: currentStep >= 2 }">
|
||||
<div class="line"></div>
|
||||
<div class="icon">✂️ 复制粘贴</div>
|
||||
<div
|
||||
class="flow-arrow"
|
||||
:class="{ active: currentStep >= 2 }"
|
||||
>
|
||||
<div class="line" />
|
||||
<div class="icon">
|
||||
✂️ 复制粘贴
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Step 3: Context Assembly -->
|
||||
<div class="step-section context-section" :class="{ active: currentStep >= 3 }">
|
||||
<div
|
||||
class="step-section context-section"
|
||||
:class="{ active: currentStep >= 3 }"
|
||||
>
|
||||
<div class="step-label">
|
||||
<span class="step-num">3</span>
|
||||
<span class="step-text">最终上下文 (Final Prompt)</span>
|
||||
@@ -170,14 +199,26 @@ const search = async () => {
|
||||
你是一个专业的 AI 助手。请基于下方【检索到的资料】回答用户的提问。
|
||||
</div>
|
||||
|
||||
<div class="retrieved-block" v-if="currentStep >= 2">
|
||||
<div class="block-header">📚 检索到的资料 (Context)</div>
|
||||
<div v-if="retrievedDocs.length > 0">
|
||||
<div v-for="doc in retrievedDocs" :key="doc.id" class="retrieved-item">
|
||||
{{ doc.content }}
|
||||
</div>
|
||||
<div
|
||||
v-if="currentStep >= 2"
|
||||
class="retrieved-block"
|
||||
>
|
||||
<div class="block-header">
|
||||
📚 检索到的资料 (Context)
|
||||
</div>
|
||||
<div v-else class="empty-state">
|
||||
<div v-if="retrievedDocs.length > 0">
|
||||
<div
|
||||
v-for="doc in retrievedDocs"
|
||||
:key="doc.id"
|
||||
class="retrieved-item"
|
||||
>
|
||||
{{ doc.content }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="empty-state"
|
||||
>
|
||||
(未找到相关资料)
|
||||
</div>
|
||||
</div>
|
||||
@@ -188,7 +229,6 @@ const search = async () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
+39
-12
@@ -19,7 +19,9 @@
|
||||
<span class="value">{{ totalMessages }}</span>
|
||||
<span class="label">现在一共记了几条</span>
|
||||
</div>
|
||||
<div class="stat-divider">/</div>
|
||||
<div class="stat-divider">
|
||||
/
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span class="value">{{ maxSlots }}</span>
|
||||
<span class="label">黑板最多能记几条</span>
|
||||
@@ -30,7 +32,7 @@
|
||||
class="usage-fill"
|
||||
:style="{ width: `${(totalMessages / maxSlots) * 100}%` }"
|
||||
:class="{ full: totalMessages >= maxSlots }"
|
||||
></div>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -54,15 +56,17 @@
|
||||
<span class="role-badge">{{ msg.role }}</span>
|
||||
<button
|
||||
class="pin-btn active"
|
||||
@click="togglePin(msg)"
|
||||
:disabled="msg.role === 'System'"
|
||||
title="取消钉住"
|
||||
@click="togglePin(msg)"
|
||||
>
|
||||
<span v-if="msg.role === 'System'">🔒 系统信息固定在这</span>
|
||||
<span v-else>📌 取消钉住</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-content">{{ msg.content }}</div>
|
||||
<div class="card-content">
|
||||
{{ msg.content }}
|
||||
</div>
|
||||
</div>
|
||||
</transition-group>
|
||||
</div>
|
||||
@@ -85,14 +89,23 @@
|
||||
>
|
||||
<div class="card-header">
|
||||
<span class="role-badge">{{ msg.role }}</span>
|
||||
<button class="pin-btn" @click="togglePin(msg)" title="把这条钉在黑板上">
|
||||
<button
|
||||
class="pin-btn"
|
||||
title="把这条钉在黑板上"
|
||||
@click="togglePin(msg)"
|
||||
>
|
||||
📌 钉住这条
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-content">{{ msg.content }}</div>
|
||||
<div class="card-content">
|
||||
{{ msg.content }}
|
||||
</div>
|
||||
</div>
|
||||
</transition-group>
|
||||
<div v-if="scrollingMessages.length === 0" class="empty-state">
|
||||
<div
|
||||
v-if="scrollingMessages.length === 0"
|
||||
class="empty-state"
|
||||
>
|
||||
这里是“普通对话区”,暂时还空着
|
||||
</div>
|
||||
</div>
|
||||
@@ -103,16 +116,30 @@
|
||||
<div class="input-group">
|
||||
<input
|
||||
v-model="newMessage"
|
||||
@keyup.enter="sendMessage"
|
||||
placeholder="在这里输入一条新的信息,比如“我叫小明”"
|
||||
/>
|
||||
<button class="send-btn" @click="sendMessage" :disabled="!newMessage.trim()">
|
||||
@keyup.enter="sendMessage"
|
||||
>
|
||||
<button
|
||||
class="send-btn"
|
||||
:disabled="!newMessage.trim()"
|
||||
@click="sendMessage"
|
||||
>
|
||||
添加到黑板
|
||||
</button>
|
||||
</div>
|
||||
<div class="presets">
|
||||
<button class="preset-btn" @click="addPreset('我的名字叫 Alice。')">用户:我的名字叫 Alice</button>
|
||||
<button class="preset-btn" @click="addPreset('系统密码是 1234。')">用户:系统密码是 1234</button>
|
||||
<button
|
||||
class="preset-btn"
|
||||
@click="addPreset('我的名字叫 Alice。')"
|
||||
>
|
||||
用户:我的名字叫 Alice
|
||||
</button>
|
||||
<button
|
||||
class="preset-btn"
|
||||
@click="addPreset('系统密码是 1234。')"
|
||||
>
|
||||
用户:系统密码是 1234
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+43
-14
@@ -19,10 +19,17 @@
|
||||
<span class="value">最多 {{ windowSize }} 条</span>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="action-btn" @click="autoPlay" :disabled="isAutoPlaying">
|
||||
<button
|
||||
class="action-btn"
|
||||
:disabled="isAutoPlaying"
|
||||
@click="autoPlay"
|
||||
>
|
||||
▶ 自动演示
|
||||
</button>
|
||||
<button class="action-btn outline" @click="reset">
|
||||
<button
|
||||
class="action-btn outline"
|
||||
@click="reset"
|
||||
>
|
||||
↺ 重新开始
|
||||
</button>
|
||||
</div>
|
||||
@@ -42,14 +49,23 @@
|
||||
class="message-bubble history"
|
||||
:class="msg.role.toLowerCase()"
|
||||
>
|
||||
<div class="avatar">{{ msg.role === 'User' ? '👤' : '🤖' }}</div>
|
||||
<div class="avatar">
|
||||
{{ msg.role === 'User' ? '👤' : '🤖' }}
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="role-name">{{ msg.role }}</div>
|
||||
<div class="text">{{ msg.content }}</div>
|
||||
<div class="role-name">
|
||||
{{ msg.role }}
|
||||
</div>
|
||||
<div class="text">
|
||||
{{ msg.content }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition-group>
|
||||
<div v-if="historyMessages.length === 0" class="empty-placeholder">
|
||||
<div
|
||||
v-if="historyMessages.length === 0"
|
||||
class="empty-placeholder"
|
||||
>
|
||||
这里暂时还没有被“挤出去”的对话
|
||||
</div>
|
||||
</div>
|
||||
@@ -57,7 +73,7 @@
|
||||
<!-- Divider -->
|
||||
<div class="window-divider">
|
||||
<span>⬆ 窗口外(模型已经看不到)</span>
|
||||
<div class="divider-line"></div>
|
||||
<div class="divider-line" />
|
||||
<span>⬇ 窗口内(模型还能看到)</span>
|
||||
</div>
|
||||
|
||||
@@ -73,14 +89,23 @@
|
||||
class="message-bubble active"
|
||||
:class="msg.role.toLowerCase()"
|
||||
>
|
||||
<div class="avatar">{{ msg.role === 'User' ? '👤' : '🤖' }}</div>
|
||||
<div class="avatar">
|
||||
{{ msg.role === 'User' ? '👤' : '🤖' }}
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="role-name">{{ msg.role }}</div>
|
||||
<div class="text">{{ msg.content }}</div>
|
||||
<div class="role-name">
|
||||
{{ msg.role }}
|
||||
</div>
|
||||
<div class="text">
|
||||
{{ msg.content }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition-group>
|
||||
<div v-if="activeMessages.length === 0" class="empty-placeholder">
|
||||
<div
|
||||
v-if="activeMessages.length === 0"
|
||||
class="empty-placeholder"
|
||||
>
|
||||
从这里开始聊天,看看旧对话是怎么被“挤出去”的
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,11 +115,15 @@
|
||||
<div class="input-section">
|
||||
<input
|
||||
v-model="newMessage"
|
||||
@keyup.enter="sendMessage"
|
||||
placeholder="在这里输入一条消息,然后点发送"
|
||||
:disabled="isAutoPlaying"
|
||||
/>
|
||||
<button class="send-btn" @click="sendMessage" :disabled="!newMessage.trim() || isAutoPlaying">
|
||||
@keyup.enter="sendMessage"
|
||||
>
|
||||
<button
|
||||
class="send-btn"
|
||||
:disabled="!newMessage.trim() || isAutoPlaying"
|
||||
@click="sendMessage"
|
||||
>
|
||||
发送消息
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user