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
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,11 @@ export class KnowledgeStorePresenter {
}
} as KnowledgeFileMessage

fileId
? await this.vectorP.updateFile(fileMessage)
: await this.vectorP.insertFile(fileMessage)
if (fileId) {
await this.vectorP.updateFile(fileMessage)
} else {
await this.vectorP.insertFile(fileMessage)
}

this.processFileAsync(fileMessage)

Expand Down
6 changes: 4 additions & 2 deletions src/renderer/src/assets/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -276,11 +276,13 @@
}

* {
@apply border-border outline-ring/50;
border-color: var(--border);
outline-color: color-mix(in oklab, var(--ring) 50%, transparent);
}

body {
@apply bg-background text-foreground;
background-color: var(--background);
color: var(--foreground);
font-weight: var(--text-weight);
}

Expand Down
68 changes: 25 additions & 43 deletions src/renderer/src/components/message/MessageBlockThink.vue
Original file line number Diff line number Diff line change
@@ -1,50 +1,20 @@
<!-- eslint-disable vue/no-v-html -->
<template>
<div
class="text-xs text-secondary-foreground bg-muted rounded-lg border border-input flex flex-col gap-2 px-2 py-2"
>
<div class="flex flex-row gap-2 items-center cursor-pointer" @click="collapse = !collapse">
<Button variant="ghost" size="icon" class="w-4 h-4 text-muted-foreground">
<Icon icon="lucide:chevrons-up-down" class="w-4 h-4" />
</Button>
<span class="grow"
>{{
block.status === 'loading'
? t('chat.features.deepThinkingProgress')
: t('chat.features.deepThinking')
}}
<span>{{
reasoningDuration > 0 ? t('chat.features.thinkingDuration', [reasoningDuration]) : ''
}}</span>
</span>
</div>
<div
v-show="!collapse"
ref="messageBlock"
class="w-full relative prose prose-sm dark:prose-invert max-w-full leading-7 break-all"
>
<NodeRenderer
:renderCodeBlocksAsPre="true"
:content="props.block.content || ''"
></NodeRenderer>
</div>

<Icon
v-if="block.status === 'loading'"
icon="lucide:loader-circle"
class="w-4 h-4 text-muted-foreground animate-spin"
/>
</div>
<ThinkContent
:label="headerText"
:expanded="!collapse"
:thinking="block.status === 'loading'"
:content-html="renderedContent"
@toggle="collapse = !collapse"
/>
</template>

<script setup lang="ts">
import { Button } from '@shadcn/components/ui/button'
import { useI18n } from 'vue-i18n'
import { ThinkContent } from '@/components/think-content'
import { computed, onMounted, ref, watch } from 'vue'
import { usePresenter } from '@/composables/usePresenter'
import { Icon } from '@iconify/vue'
import { renderMarkdown, getCommonMarkdown } from 'vue-renderer-markdown'
import { AssistantMessageBlock } from '@shared/chat'
import { computed, onMounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import NodeRenderer from 'vue-renderer-markdown'
const props = defineProps<{
block: AssistantMessageBlock
usage: {
Expand All @@ -56,10 +26,9 @@ const { t } = useI18n()

const configPresenter = usePresenter('configPresenter')

const messageBlock = ref<HTMLDivElement | null>(null)
// kept for potential future scroll anchoring; currently unused

const collapse = ref(false)

const reasoningDuration = computed(() => {
let duration: number
if (props.block.reasoning_time) {
Expand All @@ -71,6 +40,19 @@ const reasoningDuration = computed(() => {
return parseFloat(duration.toFixed(2))
})

const md = getCommonMarkdown()
const renderedContent = computed(() => {
return renderMarkdown(md, props.block.content || '')
})

const headerText = computed(() => {
// Format: "Thought for 20s" (localized)
const seconds = Math.max(0, Math.floor(reasoningDuration.value))
return props.block.status === 'loading'
? t('chat.features.thoughtForSecondsLoading', { seconds })
: t('chat.features.thoughtForSeconds', { seconds })
})

watch(
() => collapse.value,
() => {
Expand Down
8 changes: 6 additions & 2 deletions src/renderer/src/components/message/MessageItemAssistant.vue
Original file line number Diff line number Diff line change
Expand Up @@ -295,10 +295,14 @@ const handleAction = (action: HandleActionType) => {
} else if (action === 'prev' || action === 'next') {
switch (action) {
case 'prev':
currentVariantIndex.value > 0 && currentVariantIndex.value--
if (currentVariantIndex.value > 0) {
currentVariantIndex.value--
}
break
case 'next':
currentVariantIndex.value < totalVariants.value - 1 && currentVariantIndex.value++
if (currentVariantIndex.value < totalVariants.value - 1) {
currentVariantIndex.value++
}
break
}

Expand Down
25 changes: 15 additions & 10 deletions src/renderer/src/components/settings/ShortcutSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
</Button>
</div>
</div>
<ScrollArea class="px-4 flex-1 w-full h-full">
<ScrollArea class="px-4 flex-1 w-full h-full pb-8">
<div class="w-full h-full flex flex-col gap-1.5">
<!-- 快捷键列表 -->
<div class="flex flex-col gap-2">
Expand All @@ -31,7 +31,7 @@
<div class="relative w-full group">
<Button
variant="outline"
class="h-10 min-w-[200px] justify-end relative px-2"
class="h-10 min-w-[140px] justify-end relative px-2"
:class="{
'ring-2 ring-primary': recordingShortcutId === shortcut.id && !shortcutError,
'ring-2 ring-destructive': recordingShortcutId === shortcut.id && shortcutError
Expand Down Expand Up @@ -444,25 +444,30 @@ const clearShortcut = async (shortcutId: string) => {
<style scoped>
.tw-keycap {
display: inline-flex;
min-width: 2rem;
height: 2rem;
min-width: 1.6rem;
height: 1.6rem;
align-items: center;
justify-content: center;
padding: 0.125rem 0.5rem;
padding: 0.125rem 0.375rem;
margin: 0 0.125rem;
border-radius: 0.375rem;
font-size: 0.875rem;
line-height: 1.25rem;
border-radius: 0.3rem;
font-size: 0.75rem;
line-height: 1rem;
font-weight: 500;
letter-spacing: 0.01em;
vertical-align: middle;
border-width: 1px;
border-style: solid;
box-shadow: 0 1px 2px rgb(15 23 42 / 0.08);
box-shadow:
inset 0 -1px 0 rgb(15 23 42 / 0.08),
0 1px 2px rgb(15 23 42 / 0.06);
transition:
color 150ms ease,
background-color 150ms ease,
border-color 150ms ease;
user-select: none;
background-color: hsl(var(--background));
background-color: hsl(var(--muted));
border-color: hsl(var(--border));
color: hsl(var(--foreground));
}
</style>
75 changes: 75 additions & 0 deletions src/renderer/src/components/think-content/ThinkContent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<!-- eslint-disable vue/no-v-html -->
<template>
<div
class="text-xs leading-4 text-[rgba(37,37,37,0.5)] dark:text-white/50 flex flex-col gap-[6px]"
>
<div
class="inline-flex items-center gap-[10px] cursor-pointer select-none self-start"
@click="$emit('toggle')"
>
<span class="whitespace-nowrap">{{ label }}</span>
<Icon
v-if="thinking && !expanded"
icon="lucide:ellipsis"
class="w-[14px] h-[14px] text-[rgba(37,37,37,0.5)] dark:text-white/50 animate-pulse"
/>
<Icon
v-else-if="expanded"
icon="lucide:chevron-down"
class="w-[14px] h-[14px] text-[rgba(37,37,37,0.5)] dark:text-white/50"
/>
<Icon
v-else
icon="lucide:chevron-right"
class="w-[14px] h-[14px] text-[rgba(37,37,37,0.5)] dark:text-white/50"
/>
</div>

Comment on lines +6 to +27
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make the toggle control keyboard accessible
The header is a <div> with a click handler, so it can’t be focused or activated via keyboard. Users depending on keyboard or assistive tech won’t be able to expand/collapse the block. Please use a semantic button (or add the necessary role/tabindex/keyboard handlers).

-    <div
-      class="inline-flex items-center gap-[10px] cursor-pointer select-none self-start"
-      @click="$emit('toggle')"
-    >
+    <button
+      type="button"
+      class="inline-flex items-center gap-[10px] cursor-pointer select-none self-start focus:outline-none"
+      @click="$emit('toggle')"
+      :aria-expanded="expanded"
+    >
       <span class="whitespace-nowrap">{{ label }}</span>
 ...
-    </div>
+    </button>
📝 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
<div
class="inline-flex items-center gap-[10px] cursor-pointer select-none self-start"
@click="$emit('toggle')"
>
<span class="whitespace-nowrap">{{ label }}</span>
<Icon
v-if="thinking && !expanded"
icon="lucide:ellipsis"
class="w-[14px] h-[14px] text-[rgba(37,37,37,0.5)] dark:text-white/50 animate-pulse"
/>
<Icon
v-else-if="expanded"
icon="lucide:chevron-down"
class="w-[14px] h-[14px] text-[rgba(37,37,37,0.5)] dark:text-white/50"
/>
<Icon
v-else
icon="lucide:chevron-right"
class="w-[14px] h-[14px] text-[rgba(37,37,37,0.5)] dark:text-white/50"
/>
</div>
<button
type="button"
class="inline-flex items-center gap-[10px] cursor-pointer select-none self-start focus:outline-none"
@click="$emit('toggle')"
:aria-expanded="expanded"
>
<span class="whitespace-nowrap">{{ label }}</span>
<Icon
v-if="thinking && !expanded"
icon="lucide:ellipsis"
class="w-[14px] h-[14px] text-[rgba(37,37,37,0.5)] dark:text-white/50 animate-pulse"
/>
<Icon
v-else-if="expanded"
icon="lucide:chevron-down"
class="w-[14px] h-[14px] text-[rgba(37,37,37,0.5)] dark:text-white/50"
/>
<Icon
v-else
icon="lucide:chevron-right"
class="w-[14px] h-[14px] text-[rgba(37,37,37,0.5)] dark:text-white/50"
/>
</button>
🤖 Prompt for AI Agents
In src/renderer/src/components/think-content/ThinkContent.vue around lines 6 to
27, the clickable header is a <div> which is not focusable/activatable by
keyboard; change it to a semantic control or add keyboard handlers: replace the
<div> with a <button> (preserving all classes and the @click="$emit('toggle')"),
add aria-expanded binding (aria-expanded="expanded") and aria-controls if there
is an associated region id, and ensure the inner label remains accessible (keep
the <span>). If you must keep a non-button element, add tabindex="0",
role="button", and a keydown handler that calls the same toggle on Enter/Space,
and update aria-expanded similarly so assistive tech and keyboard users can
operate the toggle.

<div v-show="expanded" class="w-full relative">
<div class="think-prose w-full max-w-full mt-[6px]" v-html="contentHtml"></div>
</div>

<Icon
v-if="thinking && expanded"
icon="lucide:ellipsis"
class="w-[14px] h-[14px] text-[rgba(37,37,37,0.5)] dark:text-white/50 animate-pulse"
/>
</div>
</template>

<script setup lang="ts">
import { Icon } from '@iconify/vue'

defineProps<{
label: string
expanded: boolean
thinking: boolean
contentHtml?: string
}>()

defineEmits<{
(e: 'toggle'): void
}>()
</script>

<style scoped>
.think-prose :where(p, li, ol, ul) {
font-size: 12px;
line-height: 16px;
letter-spacing: 0;
color: rgba(37, 37, 37, 0.5);
}
.think-prose :where(ol, ul) {
padding-left: 1.5em;
}
.think-prose :where(p, li, ol, ul) :where(a) {
color: rgba(37, 37, 37, 0.6);
text-decoration: underline;
}
.dark .think-prose :where(p, li, ol, ul) {
color: rgba(255, 255, 255, 0.5);
}
.dark .think-prose :where(p, li, ol, ul) :where(a) {
color: rgba(255, 255, 255, 0.6);
}
</style>
1 change: 1 addition & 0 deletions src/renderer/src/components/think-content/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as ThinkContent } from './ThinkContent.vue'
5 changes: 2 additions & 3 deletions src/renderer/src/i18n/en-US/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@
"rateLimitWaitingTooltip": "Wait {seconds}s more, {interval}s interval"
},
"features": {
"deepThinking": "Deep Thinking",
"webSearch": "Web Search",
"deepThinkingProgress": "Deep Thinking in progress...",
"thinkingDuration": "(Took {0} seconds)",
"thoughtForSeconds": "Thought for {seconds}s",
"thoughtForSecondsLoading": "Thinking for {seconds}s...",
"artifactThinking": "Artifact Thinking"
},
"search": {
Expand Down
7 changes: 3 additions & 4 deletions src/renderer/src/i18n/fa-IR/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@
"rateLimitWaitingTooltip": "باید {seconds} ثانیه دیگر منتظر بمانید، فاصله {interval} ثانیه"
},
"features": {
"deepThinking": "تفکر عمیق",
"webSearch": "جستجوی وب",
"deepThinkingProgress": "در حال تفکر عمیق...",
"thinkingDuration": "({0} ثانیه طول کشید)",
"artifactThinking": "تفکر مصنوعی"
"artifactThinking": "تفکر مصنوعی",
"thoughtForSeconds": "تفکر به مدت {seconds} ثانیه",
"thoughtForSecondsLoading": "در حال تفکر ({seconds} ثانیه)"
},
"search": {
"results": "{0} صفحه وب یافت شد",
Expand Down
7 changes: 3 additions & 4 deletions src/renderer/src/i18n/fr-FR/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@
"rateLimitWaitingTooltip": "Attendre encore {seconds} secondes, intervalle {interval} secondes"
},
"features": {
"deepThinking": "Réflexion approfondie",
"webSearch": "Recherche web",
"deepThinkingProgress": "Réflexion approfondie en cours...",
"thinkingDuration": "(A pris {0} secondes)",
"artifactThinking": "Réflexion sur les artefacts"
"artifactThinking": "Réflexion sur les artefacts",
"thoughtForSeconds": "Réflexion pendant {seconds}s",
"thoughtForSecondsLoading": "Réflexion en cours ({seconds}s)"
},
"search": {
"results": "{0} pages web trouvées",
Expand Down
7 changes: 3 additions & 4 deletions src/renderer/src/i18n/ja-JP/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@
"rateLimitWaitingTooltip": "あと{seconds}秒待機、間隔{interval}秒"
},
"features": {
"deepThinking": "深い思考",
"webSearch": "ウェブ検索",
"deepThinkingProgress": "深い思考中...",
"thinkingDuration": "({0}秒かかりました)",
"artifactThinking": "アーティファクト思考"
"artifactThinking": "アーティファクト思考",
"thoughtForSeconds": "{seconds}秒間思考しました",
"thoughtForSecondsLoading": "{seconds}秒間思考中..."
},
"search": {
"results": "{0}件のウェブページが見つかりました",
Expand Down
7 changes: 3 additions & 4 deletions src/renderer/src/i18n/ko-KR/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@
"rateLimitWaitingTooltip": "{seconds}초 더 대기, 간격 {interval}초"
},
"features": {
"deepThinking": "심층 사고",
"webSearch": "웹 검색",
"deepThinkingProgress": "심층 사고 진행 중...",
"thinkingDuration": "({0}초 소요)",
"artifactThinking": "아티팩트 추론"
"artifactThinking": "아티팩트 추론",
"thoughtForSeconds": "{seconds}초 동안 생각했습니다",
"thoughtForSecondsLoading": "{seconds}초째 생각 중..."
},
Comment on lines 22 to 26
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix placeholder keys for new “thought” strings
Both strings use {초} instead of {seconds}, so the UI will literally render {초} and never substitute the counter. Align the placeholder with the other locales and typings.

-    "artifactThinking": "아티팩트 추론",
-    "thoughtForSeconds": "생각 {초} 초",
-    "thoughtForSecondsLoading": "사고 ({초} 초)"
+    "artifactThinking": "아티팩트 추론",
+    "thoughtForSeconds": "생각 {seconds}초",
+    "thoughtForSecondsLoading": "생각 중 ({seconds}초)"
📝 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
"webSearch": "웹 검색",
"deepThinkingProgress": "심층 사고 진행 중...",
"thinkingDuration": "({0}초 소요)",
"artifactThinking": "아티팩트 추론"
"artifactThinking": "아티팩트 추론",
"thoughtForSeconds": "생각 {초} 초",
"thoughtForSecondsLoading": "사고 ({초} 초)"
},
{
"webSearch": "웹 검색",
"artifactThinking": "아티팩트 추론",
"thoughtForSeconds": "생각 {seconds}초",
"thoughtForSecondsLoading": "생각 중 ({seconds}초)"
},
🤖 Prompt for AI Agents
In src/renderer/src/i18n/ko-KR/chat.json around lines 22 to 26, the two
"thought" strings use the locale-specific placeholder "{초}" which prevents the
runtime from substituting the seconds counter; replace "{초}" with the canonical
"{seconds}" in both "thoughtForSeconds" and "thoughtForSecondsLoading" so they
match other locales and the typings, keeping the surrounding Korean text intact.

"search": {
"results": "{0}개의 웹 페이지를 찾았습니다",
Expand Down
7 changes: 3 additions & 4 deletions src/renderer/src/i18n/ru-RU/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@
"rateLimitWaitingTooltip": "Ещё {seconds} секунд ожидания, интервал {interval} секунд"
},
"features": {
"deepThinking": "Глубокое мышление",
"webSearch": "Поиск в интернете",
"deepThinkingProgress": "Глубокое мышление...",
"thinkingDuration": "(Заняло {0} секунд)",
"artifactThinking": "Артефакт мышление"
"artifactThinking": "Артефакт мышление",
"thoughtForSeconds": "Мышление {seconds} секунд",
"thoughtForSecondsLoading": "Мышление ({seconds} секунд)"
},
Comment thread
zerob13 marked this conversation as resolved.
"search": {
"results": "Найдено {0} веб-страниц",
Expand Down
5 changes: 2 additions & 3 deletions src/renderer/src/i18n/zh-CN/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@
"rateLimitWaitingTooltip": "还需等待 {seconds} 秒,间隔 {interval} 秒"
},
"features": {
"deepThinking": "深度思考",
"webSearch": "联网搜索",
"deepThinkingProgress": "深度思考中...",
"thinkingDuration": "(用时 {0} 秒)",
"thoughtForSeconds": "思考了 {seconds} 秒",
"thoughtForSecondsLoading": "正在思考(第 {seconds} 秒)",
"artifactThinking": "artifact 思考"
},
"search": {
Expand Down
7 changes: 3 additions & 4 deletions src/renderer/src/i18n/zh-HK/chat.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@
"rateLimitWaitingTooltip": "還需等待 {seconds} 秒,間隔 {interval} 秒"
},
"features": {
"deepThinking": "深度思考",
"webSearch": "網絡搜索",
"deepThinkingProgress": "深度思考中...",
"thinkingDuration": "(用時 {0} 秒",
"artifactThinking": "artifact 思考"
"artifactThinking": "artifact 思考",
"thoughtForSeconds": "思考了 {seconds} 秒",
"thoughtForSecondsLoading": "正在思考(第 {seconds} 秒)"
},
"search": {
"results": "已搜索到{0}個網頁",
Expand Down
Loading