d35211071a
- standardize border-radius from 8px to 6px for consistent styling - adjust padding values from 1rem to 0.75rem for better visual hierarchy - remove redundant overflow-y properties for cleaner code
284 lines
6.3 KiB
Vue
284 lines
6.3 KiB
Vue
<template>
|
||
<div class="lost-in-middle-demo">
|
||
<div class="control-panel">
|
||
<div class="control-group">
|
||
<label>关键信息大概在整段话的哪个位置:{{ needlePosition }}%</label>
|
||
<input
|
||
type="range"
|
||
v-model.number="needlePosition"
|
||
min="0"
|
||
max="100"
|
||
step="1"
|
||
class="slider-input"
|
||
>
|
||
</div>
|
||
</div>
|
||
|
||
<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>
|
||
|
||
<!-- Attention Heatmap Background -->
|
||
<div class="attention-heatmap"></div>
|
||
|
||
<!-- Needle Marker -->
|
||
<div
|
||
class="needle-marker"
|
||
:style="{ left: `${needlePosition}%` }"
|
||
>
|
||
<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">
|
||
<!-- U-Curve Path -->
|
||
<path
|
||
d="M 0 5 Q 50 55 100 5"
|
||
fill="none"
|
||
stroke="var(--vp-c-divider)"
|
||
stroke-width="2"
|
||
stroke-dasharray="4"
|
||
/>
|
||
<!-- Active Dot -->
|
||
<circle
|
||
:cx="needlePosition"
|
||
:cy="60 - (retrievalProb * 0.5 + 5)"
|
||
r="3"
|
||
fill="var(--vp-c-brand)"
|
||
/>
|
||
</svg>
|
||
<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)">
|
||
{{ retrievalProb.toFixed(1) }}%
|
||
</div>
|
||
<div class="metric-label">检索成功率</div>
|
||
</div>
|
||
<div class="metric-card">
|
||
<div class="metric-value">{{ positionLabel }}</div>
|
||
<div class="metric-label">位置描述</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="info-box">
|
||
<p>
|
||
<span class="icon">🔍</span>
|
||
<strong>实验观察:</strong>当关键信息藏在整段话的<strong>中间位置</strong>时,模型最容易“漏看掉”(Lost in the Middle)。
|
||
<br>
|
||
最靠谱的做法:把重要指令放在<strong>最前面的 System Prompt</strong>,或者<strong>最后的用户问题里</strong>。
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed } from 'vue'
|
||
|
||
const needlePosition = ref(50) // 0 to 100
|
||
|
||
// Parabolic curve calculation: Vertex at (50, 40), passing through (0, 95) and (100, 95)
|
||
// y = a(x-h)^2 + k
|
||
// a = 0.022
|
||
const retrievalProb = computed(() => {
|
||
const x = needlePosition.value
|
||
const prob = 0.022 * Math.pow(x - 50, 2) + 40
|
||
return Math.min(99.9, Math.max(0, prob))
|
||
})
|
||
|
||
const positionLabel = computed(() => {
|
||
const p = needlePosition.value
|
||
if (p < 20) return '偏开头'
|
||
if (p > 80) return '偏结尾'
|
||
return '中间区域(最危险)'
|
||
})
|
||
|
||
const getScoreClass = (score) => {
|
||
if (score > 85) return 'text-success'
|
||
if (score > 60) return 'text-warning'
|
||
return 'text-danger'
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.lost-in-middle-demo {
|
||
border: 1px solid var(--vp-c-divider);
|
||
border-radius: 6px;
|
||
background-color: var(--vp-c-bg-soft);
|
||
padding: 0.75rem;
|
||
margin: 0.5rem 0;
|
||
font-family: var(--vp-font-family-mono);
|
||
}
|
||
|
||
.control-panel {
|
||
margin-bottom: 1rem;
|
||
background: var(--vp-c-bg);
|
||
padding: 0.75rem;
|
||
border-radius: 6px;
|
||
border: 1px solid var(--vp-c-divider);
|
||
}
|
||
|
||
.control-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.control-group label {
|
||
font-weight: bold;
|
||
font-size: 0.85rem;
|
||
}
|
||
|
||
.slider-input {
|
||
width: 100%;
|
||
accent-color: var(--vp-c-brand);
|
||
}
|
||
|
||
.visualization-area {
|
||
margin-bottom: 1rem;
|
||
position: relative;
|
||
}
|
||
|
||
.context-bar {
|
||
height: 40px;
|
||
border: 2px solid var(--vp-c-divider);
|
||
border-radius: 6px;
|
||
position: relative;
|
||
margin-bottom: 0.75rem;
|
||
background: var(--vp-c-bg);
|
||
overflow: visible; /* Allow needle to stick out */
|
||
}
|
||
|
||
.attention-heatmap {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
border-radius: 6px;
|
||
background: linear-gradient(
|
||
90deg,
|
||
rgba(16, 185, 129, 0.2) 0%,
|
||
rgba(239, 68, 68, 0.1) 50%,
|
||
rgba(16, 185, 129, 0.2) 100%
|
||
);
|
||
opacity: 0.6;
|
||
}
|
||
|
||
.context-label {
|
||
position: absolute;
|
||
top: -18px;
|
||
font-size: 0.7rem;
|
||
color: var(--vp-c-text-2);
|
||
}
|
||
|
||
.context-label.start { left: 0; }
|
||
.context-label.end { right: 0; }
|
||
|
||
.needle-marker {
|
||
position: absolute;
|
||
top: 50%;
|
||
transform: translate(-50%, -50%);
|
||
z-index: 10;
|
||
cursor: grab;
|
||
transition: left 0.1s ease;
|
||
}
|
||
|
||
.needle-icon {
|
||
font-size: 1.25rem;
|
||
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.2));
|
||
}
|
||
|
||
.needle-tooltip {
|
||
position: absolute;
|
||
bottom: 100%;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
background: var(--vp-c-text-1);
|
||
color: var(--vp-c-bg);
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
font-size: 0.7rem;
|
||
white-space: nowrap;
|
||
pointer-events: none;
|
||
opacity: 0;
|
||
transition: opacity 0.2s;
|
||
}
|
||
|
||
.needle-marker:hover .needle-tooltip {
|
||
opacity: 1;
|
||
}
|
||
|
||
.chart-container {
|
||
height: 60px;
|
||
position: relative;
|
||
border-bottom: 1px solid var(--vp-c-divider);
|
||
margin-top: 0.75rem;
|
||
}
|
||
|
||
.chart-svg {
|
||
width: 100%;
|
||
height: 100%;
|
||
overflow: visible;
|
||
}
|
||
|
||
.chart-label {
|
||
position: absolute;
|
||
font-size: 0.7rem;
|
||
color: var(--vp-c-text-3);
|
||
}
|
||
|
||
.chart-label.y-axis { top: 0; left: 0; }
|
||
.chart-label.x-axis { bottom: -1rem; width: 100%; text-align: center; }
|
||
|
||
.metrics-panel {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
margin-bottom: 0.75rem;
|
||
}
|
||
|
||
.metric-card {
|
||
flex: 1;
|
||
background: var(--vp-c-bg-alt);
|
||
padding: 0.75rem;
|
||
border-radius: 6px;
|
||
text-align: center;
|
||
}
|
||
|
||
.metric-value {
|
||
font-size: 1.25rem;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.metric-label {
|
||
font-size: 0.75rem;
|
||
color: var(--vp-c-text-2);
|
||
margin-top: 0.2rem;
|
||
}
|
||
|
||
.text-success { color: var(--vp-c-success-1); }
|
||
.text-warning { color: var(--vp-c-warning-1); }
|
||
.text-danger { color: var(--vp-c-danger-1); }
|
||
|
||
.info-box {
|
||
background-color: var(--vp-c-bg-alt);
|
||
padding: 0.75rem;
|
||
border-radius: 6px;
|
||
font-size: 0.85rem;
|
||
color: var(--vp-c-text-2);
|
||
}
|
||
|
||
.info-box .icon {
|
||
margin-right: 0.5rem;
|
||
}
|
||
</style>
|