chore:优化书签样式
This commit is contained in:
parent
9fb32f1074
commit
a847d91886
|
|
@ -57,7 +57,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, shallowRef, computed, onMounted, defineAsyncComponent, triggerRef } from 'vue'
|
import { ref, shallowRef, computed, onMounted, defineAsyncComponent } from 'vue'
|
||||||
import Sidebar from './components/Sidebar.vue'
|
import Sidebar from './components/Sidebar.vue'
|
||||||
import ChatView from './components/ChatView.vue'
|
import ChatView from './components/ChatView.vue'
|
||||||
|
|
||||||
|
|
@ -249,6 +249,7 @@ function createStreamCallbacks(convId, { updateConvList = true } = {}) {
|
||||||
return {
|
return {
|
||||||
onThinkingStart() {
|
onThinkingStart() {
|
||||||
updateStreamField(convId, 'streamThinking', streamThinking, '')
|
updateStreamField(convId, 'streamThinking', streamThinking, '')
|
||||||
|
updateStreamField(convId, 'streamContent', streamContent, '')
|
||||||
},
|
},
|
||||||
onThinking(text) {
|
onThinking(text) {
|
||||||
updateStreamField(convId, 'streamThinking', streamThinking, prev => (prev || '') + text)
|
updateStreamField(convId, 'streamThinking', streamThinking, prev => (prev || '') + text)
|
||||||
|
|
|
||||||
|
|
@ -174,21 +174,12 @@ function scrollToMessage(msgId) {
|
||||||
if (!scrollContainer.value) return
|
if (!scrollContainer.value) return
|
||||||
const el = scrollContainer.value.querySelector(`[data-msg-id="${msgId}"]`)
|
const el = scrollContainer.value.querySelector(`[data-msg-id="${msgId}"]`)
|
||||||
if (el) {
|
if (el) {
|
||||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
el.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||||
activeMessageId.value = msgId
|
activeMessageId.value = msgId
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollToBottom(smooth = true) {
|
|
||||||
nextTick(() => {
|
|
||||||
const el = scrollContainer.value
|
|
||||||
if (el) {
|
|
||||||
el.scrollTo({ top: el.scrollHeight, behavior: smooth ? 'smooth' : 'instant' })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 流式时使用 instant 滚动,避免 smooth 动画与内容增长互相打架造成抖动
|
// 流式时使用 instant 滚动,避免 smooth 动画与内容增长互相打架造成抖动
|
||||||
watch([() => props.messages.length, () => props.streamingContent], () => {
|
watch([() => props.messages.length, () => props.streamingContent], () => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,31 @@
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
<div v-if="messages.length > 0" class="bookmark-rail">
|
<div v-if="messages.length > 0" class="bookmark-rail">
|
||||||
<div
|
<div
|
||||||
v-for="(msg, idx) in messages"
|
v-for="msg in userMessages"
|
||||||
:key="msg.id"
|
:key="msg.id"
|
||||||
class="bookmark"
|
class="bookmark"
|
||||||
:class="{ active: activeId === msg.id, user: msg.role === 'user' }"
|
:class="{ active: activeId === msg.id }"
|
||||||
@click="$emit('scrollTo', msg.id)"
|
@click="$emit('scrollTo', msg.id)"
|
||||||
>
|
>
|
||||||
<div class="bookmark-dot"></div>
|
<div class="bookmark-dot"></div>
|
||||||
<div class="bookmark-label">{{ msg.role === 'user' ? '用户' : 'Claw' }} · {{ preview(msg) }}</div>
|
<div class="bookmark-label">{{ preview(msg) }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
defineProps({
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
messages: { type: Array, required: true },
|
messages: { type: Array, required: true },
|
||||||
activeId: { type: String, default: null },
|
activeId: { type: String, default: null },
|
||||||
})
|
})
|
||||||
|
|
||||||
defineEmits(['scrollTo'])
|
defineEmits(['scrollTo'])
|
||||||
|
|
||||||
|
const userMessages = computed(() => props.messages.filter(m => m.role === 'user'))
|
||||||
|
|
||||||
function preview(msg) {
|
function preview(msg) {
|
||||||
if (!msg.text) return '...'
|
if (!msg.text) return '...'
|
||||||
const clean = msg.text.replace(/[#*`~>\-\[\]()]/g, '').replace(/\s+/g, ' ').trim()
|
const clean = msg.text.replace(/[#*`~>\-\[\]()]/g, '').replace(/\s+/g, ' ').trim()
|
||||||
|
|
@ -84,19 +88,15 @@ function preview(msg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookmark-dot {
|
.bookmark-dot {
|
||||||
width: 4px;
|
width: 5px;
|
||||||
height: 4px;
|
height: 5px;
|
||||||
border-radius: 50%;
|
border-radius: 1.5px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
background: var(--text-tertiary);
|
background: #3b82f6;
|
||||||
opacity: 0.35;
|
opacity: 0.35;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bookmark.user .bookmark-dot {
|
|
||||||
background: #3b82f6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bookmark.active .bookmark-dot,
|
.bookmark.active .bookmark-dot,
|
||||||
.bookmark-rail:hover .bookmark-dot {
|
.bookmark-rail:hover .bookmark-dot {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
|
||||||
|
|
@ -158,10 +158,10 @@ const processItems = computed(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 流式中追加正在增长的文本(仅当最后步骤不是 text 类型时)
|
// 流式中追加正在增长的文本(仅当还没有 text 类型的步骤时)
|
||||||
if (props.streaming && props.streamingContent) {
|
if (props.streaming && props.streamingContent) {
|
||||||
const lastStep = items[items.length - 1]
|
const hasTextStep = items.some(it => it.type === 'text')
|
||||||
if (!lastStep || lastStep.type !== 'text') {
|
if (!hasTextStep) {
|
||||||
items.push({
|
items.push({
|
||||||
type: 'text',
|
type: 'text',
|
||||||
content: props.streamingContent,
|
content: props.streamingContent,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue