Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions src/renderer/src/components/SearchStatusIndicator.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div
:class="[
'search-status-indicator flex items-center gap-2 px-2 rounded-lg border text-xs leading-4 transition-colors duration-150',
'min-h-7 py-1.5 flex items-center gap-2 px-2 rounded-lg border text-xs leading-4 transition-colors duration-150',
containerClasses,
interactive
? 'cursor-pointer hover:bg-accent/40 focus-visible:outline focus-visible:outline-offset-2 focus-visible:outline-accent'
Expand Down Expand Up @@ -107,11 +107,3 @@ const iconClass = computed(() => {
const faviconList = computed(() => props.favicons.slice(0, 6))
const showFavicons = computed(() => faviconList.value.length > 0)
</script>

<style scoped>
.search-status-indicator {
min-height: 28px;
padding-top: 6px;
padding-bottom: 6px;
}
</style>
3 changes: 3 additions & 0 deletions src/renderer/src/components/markdown/MarkdownRenderer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ defineEmits(['copy'])
.mermaid-block-header img {
margin: 0 !important;
}
p {
@apply my-0;
}
li p {
padding-top: 0;
padding-bottom: 0;
Expand Down
44 changes: 18 additions & 26 deletions src/renderer/src/components/message/MessageBlockContent.vue
Original file line number Diff line number Diff line change
@@ -1,31 +1,25 @@
<!-- eslint-disable @typescript-eslint/no-explicit-any -->
<template>
<div ref="messageBlock" class="markdown-content-wrapper relative w-full">
<template v-for="(part, index) in processedContent" :key="index">
<!-- 使用结构化渲染器替代 v-html -->
<MarkdownRenderer
v-if="part.type === 'text'"
:content="part.content"
<template v-for="(part, index) in processedContent" :key="index">
<!-- 使用结构化渲染器替代 v-html -->
<MarkdownRenderer v-if="part.type === 'text'" :content="part.content" :loading="part.loading" />

Comment on lines +5 to +6
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Prop mismatch: MarkdownRenderer has no loading prop.

Strict template typing will flag this; at runtime it becomes an HTML attribute. Either stop passing it or add the prop to MarkdownRenderer.

Two options:

  • Remove the prop here:
-    <MarkdownRenderer v-if="part.type === 'text'" :content="part.content" :loading="part.loading" />
+    <MarkdownRenderer v-if="part.type === 'text'" :content="part.content" />
  • Or, add the prop to MarkdownRenderer.vue:
-defineProps<{
-  content: string
-  debug?: boolean
-}>()
+defineProps<{
+  content: string
+  debug?: boolean
+  loading?: boolean
+}>()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<MarkdownRenderer v-if="part.type === 'text'" :content="part.content" :loading="part.loading" />
<MarkdownRenderer v-if="part.type === 'text'" :content="part.content" />
🤖 Prompt for AI Agents
In src/renderer/src/components/message/MessageBlockContent.vue around lines 5-6,
the template passes a loading prop to <MarkdownRenderer> which does not declare
that prop, causing strict template typing errors and the prop being rendered as
an HTML attribute; either remove :loading="part.loading" from this usage, or add
a loading prop to MarkdownRenderer.vue (declare it in the script block with the
correct type—e.g., Boolean or a more specific type matching part.loading—and a
sensible default) and update its TypeScript/props interface so the component
accepts and uses the prop (and forward it internally if necessary).

<ArtifactThinking v-else-if="part.type === 'thinking' && part.loading" />
<div v-else-if="part.type === 'artifact' && part.artifact" class="my-1">
<ArtifactPreview
:block="{
content: part.content,
artifact: part.artifact
}"
:message-id="messageId"
:thread-id="threadId"
:loading="part.loading"
/>

<ArtifactThinking v-else-if="part.type === 'thinking' && part.loading" />
<div v-else-if="part.type === 'artifact' && part.artifact" class="my-1">
<ArtifactPreview
:block="{
content: part.content,
artifact: part.artifact
}"
:message-id="messageId"
:thread-id="threadId"
:loading="part.loading"
/>
</div>
<div v-else-if="part.type === 'tool_call' && part.tool_call" class="my-1">
<ToolCallPreview :block="part" :block-status="props.block.status" />
</div>
</template>
</div>
</div>
<div v-else-if="part.type === 'tool_call' && part.tool_call" class="my-1">
<ToolCallPreview :block="part" :block-status="props.block.status" />
</div>
</template>
</template>

<script setup lang="ts">
Expand All @@ -52,8 +46,6 @@ const props = defineProps<{
isSearchResult?: boolean
}>()

const messageBlock = ref<HTMLDivElement>()

const { processedContent } = useBlockContent(props)

// 修改 watch 函数
Expand Down
143 changes: 58 additions & 85 deletions src/renderer/src/components/message/MessageBlockToolCall.vue
Original file line number Diff line number Diff line change
@@ -1,71 +1,15 @@
<template>
<div class="">
<div class="flex flex-col w-full">
<div
class="flex flex-col h-min-[40px] hover:bg-accent/40 select-none cursor-pointer pt-3 overflow-hidden w-[380px] break-all shadow-sm items-start gap-3 rounded-lg border bg-accent text-card-foreground"
class="w-fit min-h-7 py-1.5 flex bg-accent hover:bg-accent/40 border rounded-lg flex-wrap items-center gap-2 px-2 text-xs leading-4 transition-colors duration-150 select-none cursor-pointer"
@click="toggleExpanded"
>
<div class="flex flex-row items-center gap-2 w-full">
<div class="grow w-0 pl-2">
<h4
class="text-xs font-medium leading-none text-accent-foreground flex flex-row gap-2 items-center"
>
<span v-if="block.tool_call?.server_icons" class="text-base leading-none">{{
`${block.tool_call?.server_icons} `
}}</span>
<Icon v-else icon="lucide:hammer" class="w-4 h-4 text-muted-foreground" />
{{ block.tool_call?.server_name ? `${block.tool_call?.server_name} · ` : ''
}}{{ block.tool_call?.name ?? '' }}
</h4>
</div>
<div class="text-xs text-muted-foreground">{{ getToolCallStatus() }}</div>
<div class="shrink-0 pr-2 rounded-lg rounded-l-none flex justify-center items-center">
<Icon
v-if="block.status === 'loading'"
icon="lucide:loader-2"
class="w-4 h-4 animate-spin text-muted-foreground"
/>
<Icon
v-else-if="block.status === 'success'"
icon="lucide:check"
class="w-4 h-4 bg-green-500 rounded-full text-white p-0.5 dark:bg-green-800"
/>
<Icon
v-else-if="block.status === 'error'"
icon="lucide:x"
class="w-4 h-4 text-white p-0.5 bg-red-500 rounded-full dark:bg-red-800"
/>
<Icon
v-else-if="showPermissionIcon()"
icon="lucide:hand"
class="w-4 h-4 p-0.5 bg-yellow-500 text-white rounded-full dark:bg-yellow-800"
/>
<Icon
v-else
:icon="isExpanded ? 'lucide:chevron-up' : 'lucide:chevron-down'"
class="w-4 h-4 text-muted-foreground"
/>
</div>
</div>
<!-- <transition
enter-active-class="transition-all duration-200"
enter-from-class="opacity-0 -translate-y-2"
enter-to-class="opacity-100 translate-y-0"
leave-active-class="transition-all duration-200"
leave-from-class="opacity-100 translate-y-0"
leave-to-class="opacity-0 -translate-y-2"
<Icon :icon="statusIconName" :class="['w-3.5 h-3.5 shrink-0', statusIconClass]" />
<div
class="flex items-center gap-2 font-mono font-medium tracking-tight text-foreground/80 truncate leading-none"
>
<div
v-if="simpleIn"
class="flex-row w-full gap-1 bg-muted dark:bg-background text-muted-foreground transition-colors duration-200 inline-flex max-w-132 items-center cursor-pointer select-none"
>
<div class="text-xs inline-flex px-2 py-1 flex-row gap-2 items-center max-w-64">
<Icon icon="lucide:arrow-up-from-dot" class="w-3 h-3 text-muted-foreground shrink-0" />
<span class="truncate">{{ simpleIn }}</span>
</div>
</div>
<div v-else class="h-0"></div>
</transition> -->
<div class="h-0"></div>
<span class="truncate text-xs">{{ primaryLabel }}.{{ functionLabel }}</span>
</div>
</div>

<!-- 详细内容区域 -->
Expand Down Expand Up @@ -115,7 +59,7 @@
import { Icon } from '@iconify/vue'
import { useI18n } from 'vue-i18n'
import { AssistantMessageBlock } from '@shared/chat'
import { ref } from 'vue'
import { computed, ref } from 'vue'
import { JsonObject } from '@/components/json-viewer'

// 创建一个安全的翻译函数
Expand Down Expand Up @@ -148,33 +92,56 @@ const props = defineProps<{

const isExpanded = ref(false)

const statusVariant = computed(() => {
if (props.block.status === 'error') return 'error'
if (props.block.status === 'success') return 'success'
if (props.block.status === 'loading') return 'running'
return 'neutral'
})

const primaryLabel = computed(() => {
if (!props.block.tool_call) return t('toolCall.title')
let serverName = props.block.tool_call.server_name
if (props.block.tool_call.server_name?.includes('/')) {
serverName = props.block.tool_call.server_name.split('/').pop()
}
return serverName || props.block.tool_call.name || t('toolCall.title')
})

const functionLabel = computed(() => {
const toolCall = props.block.tool_call
return toolCall?.name ?? ''
})

const toggleExpanded = () => {
isExpanded.value = !isExpanded.value
}

const getToolCallStatus = () => {
if (!props.block.tool_call) return ''

if (props.block.status === 'error') {
return t('toolCall.error')
const statusIconName = computed(() => {
if (!props.block.tool_call) return 'lucide:circle-small'
switch (statusVariant.value) {
case 'error':
return 'lucide:x'
case 'success':
case 'neutral':
return 'lucide:circle-small'
default:
return 'lucide:circle-small'
}

if (props.block.status === 'loading') {
return props.block.tool_call.response ? t('toolCall.response') : t('toolCall.calling')
})

const statusIconClass = computed(() => {
switch (statusVariant.value) {
case 'error':
return 'text-destructive'
case 'success':
return 'text-emerald-500'
case 'running':
return 'text-muted-foreground animate-pulse'
default:
return 'text-muted-foreground'
}

if (props.block.status === 'success') {
return t('toolCall.end')
}

return t('toolCall.title')
}

// 辅助函数,用于判断是否显示权限图标
const showPermissionIcon = () => {
// 这里保留原有逻辑,暂时默认不显示
return false
}
})

// 解析JSON为对象
const parseJson = (jsonStr: string) => {
Expand Down Expand Up @@ -208,6 +175,12 @@ const parseJson = (jsonStr: string) => {
</script>

<style scoped>
.message-tool-call {
min-height: 28px;
padding-top: 6px;
padding-bottom: 6px;
}

pre {
font-family:
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<div class="flex flex-col w-full space-y-1.5">
<MessageInfo :name="currentMessage.model_name" :timestamp="currentMessage.timestamp" />
<Spinner v-if="currentContent.length === 0" class="size-3 text-muted-foreground" />
<div v-else class="flex flex-col w-full space-y-2">
<div v-else class="flex flex-col w-full gap-1.5">
<template v-for="(block, idx) in currentContent" :key="`${message.id}-${idx}`">
<MessageBlockContent
v-if="block.type === 'content'"
Expand Down
7 changes: 5 additions & 2 deletions src/renderer/src/components/message/MessageMinimap.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
<template>
<div
class="absolute top-0 right-0 w-14 min-h-[148px] box-border rounded-[6px] py-4 px-3 flex flex-col items-stretch gap-0 pointer-events-auto z-[5] overflow-hidden"
class="absolute top-0 right-0 w-14 min-h-[148px] box-border rounded-[6px] py-4 px-3 flex flex-col items-stretch gap-0 pointer-events-none z-[5] overflow-hidden"
:style="containerStyle"
>
<div class="relative flex-none flex justify-end overflow-hidden w-full" :style="trackStyle">
<div
class="relative flex-none flex justify-end overflow-hidden w-full pointer-events-auto"
:style="trackStyle"
>
<div
class="flex flex-col items-end gap-1 w-full relative z-[2]"
role="list"
Expand Down
12 changes: 11 additions & 1 deletion src/renderer/src/components/think-content/ThinkContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<div v-show="expanded" class="w-full relative">
<NodeRenderer
v-if="content"
class="think-prose w-full max-w-full mt-[6px]"
class="think-prose w-full max-w-full"
:content="content"
:customId="customId"
/>
Expand Down Expand Up @@ -86,6 +86,16 @@ setCustomComponents(customId, {
</script>

<style scoped>
@reference '../../assets/style.css';
.think-prose :where(p, ul, li) {
@apply mb-1 mt-0;
}
.think-prose :where(ul) {
@apply my-1.5;
}
.think-prose :where(li) {
@apply my-1.5;
}
.think-prose :where(p, li, ol, ul) {
font-size: 12px;
line-height: 16px;
Expand Down