-
Notifications
You must be signed in to change notification settings - Fork 625
feat(theme): add markdown layout style and table style #1039
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,85 +15,75 @@ | |
| :context-length="contextLength" | ||
| @send="handleSend" | ||
| > | ||
| <template #addon-buttons> | ||
| <div | ||
| key="newThread-model-select" | ||
| class="new-thread-model-select overflow-hidden flex items-center h-7 rounded-lg shadow-sm border border-input transition-all duration-300" | ||
| :dir="langStore.dir" | ||
| > | ||
| <Popover v-model:open="modelSelectOpen"> | ||
| <PopoverTrigger as-child> | ||
| <Button | ||
| <template #addon-actions> | ||
| <Popover v-model:open="modelSelectOpen"> | ||
| <PopoverTrigger as-child> | ||
| <Button | ||
| variant="ghost" | ||
| class="flex items-center gap-1.5 h-7 px-2 rounded-md text-xs font-semibold text-muted-foreground hover:bg-muted/60 hover:text-foreground dark:text-white/70 dark:hover:bg-white/10 dark:hover:text-white" | ||
| size="sm" | ||
| > | ||
| <ModelIcon | ||
| class="w-4 h-4" | ||
| :model-id="activeModel.providerId" | ||
| :is-dark="themeStore.isDark" | ||
| ></ModelIcon> | ||
| <span class="text-xs font-semibold truncate max-w-[140px] text-foreground">{{ | ||
| name | ||
| }}</span> | ||
| <Badge | ||
| v-for="tag in activeModel.tags" | ||
| :key="tag" | ||
| variant="outline" | ||
| class="flex border-none rounded-none shadow-none items-center gap-1.5 px-2 h-full" | ||
| size="sm" | ||
| class="py-0 px-1 rounded-lg text-[10px]" | ||
| > | ||
| <ModelIcon | ||
| class="w-4 h-4" | ||
| :model-id="activeModel.providerId" | ||
| :is-dark="themeStore.isDark" | ||
| ></ModelIcon> | ||
| <!-- <Icon icon="lucide:message-circle" class="w-5 h-5 text-muted-foreground" /> --> | ||
| <h2 class="text-xs font-bold max-w-[150px] truncate">{{ name }}</h2> | ||
| <Badge | ||
| v-for="tag in activeModel.tags" | ||
| :key="tag" | ||
| variant="outline" | ||
| class="py-0 rounded-lg" | ||
| size="sm" | ||
| > | ||
| {{ t(`model.tags.${tag}`) }}</Badge | ||
| > | ||
| <Icon icon="lucide:chevron-right" class="w-4 h-4" /> | ||
| </Button> | ||
| </PopoverTrigger> | ||
| <PopoverContent align="start" class="p-0 w-80"> | ||
| <ModelSelect | ||
| :type="[ModelType.Chat, ModelType.ImageGeneration]" | ||
| @update:model="handleModelUpdate" | ||
| /> | ||
| </PopoverContent> | ||
| </Popover> | ||
| <ScrollablePopover | ||
| v-model:open="settingsPopoverOpen" | ||
| @update:open="handleSettingsPopoverUpdate" | ||
| align="start" | ||
| content-class="w-80" | ||
| :enable-scrollable="true" | ||
| > | ||
| <template #trigger> | ||
| <Button | ||
| class="w-7 h-full rounded-none border-none shadow-none transition-all duration-300" | ||
| :class="{ | ||
| 'w-0 opacity-0 p-0 overflow-hidden': !showSettingsButton && !isHovering, | ||
| 'w-7 opacity-100': showSettingsButton || isHovering | ||
| }" | ||
| size="icon" | ||
| variant="outline" | ||
| {{ t(`model.tags.${tag}`) }}</Badge | ||
| > | ||
| <Icon icon="lucide:settings-2" class="w-4 h-4" /> | ||
| </Button> | ||
| </template> | ||
| <ChatConfig | ||
| v-model:temperature="temperature" | ||
| v-model:context-length="contextLength" | ||
| v-model:max-tokens="maxTokens" | ||
| v-model:system-prompt="systemPrompt" | ||
| v-model:artifacts="artifacts" | ||
| v-model:thinking-budget="thinkingBudget" | ||
| v-model:enable-search="enableSearch" | ||
| v-model:forced-search="forcedSearch" | ||
| v-model:search-strategy="searchStrategy" | ||
| v-model:reasoning-effort="reasoningEffort" | ||
| v-model:verbosity="verbosity" | ||
| :context-length-limit="contextLengthLimit" | ||
| :max-tokens-limit="maxTokensLimit" | ||
| :model-id="activeModel?.id" | ||
| :provider-id="activeModel?.providerId" | ||
| :model-type="activeModel?.type" | ||
| <Icon icon="lucide:chevron-right" class="w-4 h-4 text-muted-foreground" /> | ||
| </Button> | ||
| </PopoverTrigger> | ||
| <PopoverContent align="end" class="w-80 p-0"> | ||
| <ModelSelect | ||
| :type="[ModelType.Chat, ModelType.ImageGeneration]" | ||
| @update:model="handleModelUpdate" | ||
| /> | ||
| </ScrollablePopover> | ||
| </div> | ||
| </PopoverContent> | ||
| </Popover> | ||
|
|
||
| <ScrollablePopover | ||
| v-model:open="settingsPopoverOpen" | ||
| align="end" | ||
| content-class="w-80" | ||
| :enable-scrollable="true" | ||
| > | ||
| <template #trigger> | ||
| <Button | ||
| class="h-7 w-7 rounded-md border border-border/60 hover:border-border dark:border-white/10 dark:bg-white/[0.04] dark:text-white/70 dark:hover:border-white/25 dark:hover:bg-white/15 dark:hover:text-white" | ||
| size="icon" | ||
| variant="outline" | ||
| > | ||
| <Icon icon="lucide:settings-2" class="w-4 h-4" /> | ||
| </Button> | ||
| </template> | ||
| <ChatConfig | ||
| v-model:temperature="temperature" | ||
| v-model:context-length="contextLength" | ||
| v-model:max-tokens="maxTokens" | ||
| v-model:system-prompt="systemPrompt" | ||
| v-model:artifacts="artifacts" | ||
| v-model:thinking-budget="thinkingBudget" | ||
| v-model:enable-search="enableSearch" | ||
| v-model:forced-search="forcedSearch" | ||
| v-model:search-strategy="searchStrategy" | ||
| v-model:reasoning-effort="reasoningEffort" | ||
| v-model:verbosity="verbosity" | ||
| :context-length-limit="contextLengthLimit" | ||
| :max-tokens-limit="maxTokensLimit" | ||
| :model-id="activeModel?.id" | ||
| :provider-id="activeModel?.providerId" | ||
| :model-type="activeModel?.type" | ||
| /> | ||
| </ScrollablePopover> | ||
|
Comment on lines
+53
to
+86
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add accessible label for the settings trigger button. The icon-only settings button lacks an accessible label for screen readers. Apply this diff: <Button
class="h-7 w-7 rounded-md border border-border/60 hover:border-border dark:border-white/10 dark:bg-white/[0.04] dark:text-white/70 dark:hover:border-white/25 dark:hover:bg-white/15 dark:hover:text-white"
size="icon"
variant="outline"
+ :aria-label="t('settings.trigger')"
>Add the corresponding i18n key. As per coding guidelines
🤖 Prompt for AI Agents |
||
| </template> | ||
| </ChatInput> | ||
| <div class="h-12"></div> | ||
|
|
@@ -118,14 +108,11 @@ import { computed, nextTick, ref, watch, onMounted } from 'vue' | |
| import { UserMessageContent } from '@shared/chat' | ||
| import ChatConfig from './ChatConfig.vue' | ||
| import { usePresenter } from '@/composables/usePresenter' | ||
| import { useEventListener } from '@vueuse/core' | ||
| import { useThemeStore } from '@/stores/theme' | ||
| import { useLanguageStore } from '@/stores/language' | ||
| import { ModelType } from '@shared/model' | ||
|
|
||
| const configPresenter = usePresenter('configPresenter') | ||
| const themeStore = useThemeStore() | ||
| const langStore = useLanguageStore() | ||
| // 定义偏好模型的类型 | ||
| interface PreferredModel { | ||
| modelId: string | ||
|
|
@@ -306,17 +293,7 @@ watch( | |
|
|
||
| const modelSelectOpen = ref(false) | ||
| const settingsPopoverOpen = ref(false) | ||
| const showSettingsButton = ref(false) | ||
| const isHovering = ref(false) | ||
| const chatInputRef = ref<InstanceType<typeof ChatInput> | null>(null) | ||
| // 监听鼠标悬停 | ||
| const handleMouseEnter = () => { | ||
| isHovering.value = true | ||
| } | ||
|
|
||
| const handleMouseLeave = () => { | ||
| isHovering.value = false | ||
| } | ||
|
|
||
| const handleModelUpdate = (model: MODEL_META, providerId: string) => { | ||
| activeModel.value = { | ||
|
|
@@ -405,41 +382,13 @@ watch( | |
| ) | ||
|
|
||
| onMounted(async () => { | ||
| const groupElement = document.querySelector('.new-thread-model-select') | ||
| configPresenter.getDefaultSystemPrompt().then((prompt) => { | ||
| systemPrompt.value = prompt | ||
| }) | ||
| // 组件激活时初始化一次默认模型 | ||
| await initActiveModel() | ||
| if (groupElement) { | ||
| useEventListener(groupElement, 'mouseenter', handleMouseEnter) | ||
| useEventListener(groupElement, 'mouseleave', handleMouseLeave) | ||
| } | ||
| }) | ||
|
|
||
| const handleSettingsPopoverUpdate = (isOpen: boolean) => { | ||
| if (isOpen) { | ||
| // 如果打开,立即显示按钮 | ||
| showSettingsButton.value = true | ||
| } else { | ||
| // 如果关闭,延迟隐藏按钮,等待动画完成 | ||
| setTimeout(() => { | ||
| showSettingsButton.value = false | ||
| }, 300) // 300ms是一个常见的动画持续时间,可以根据实际情况调整 | ||
| } | ||
| } | ||
|
|
||
| // 初始化时设置showSettingsButton的值与settingsPopoverOpen一致 | ||
| watch( | ||
| settingsPopoverOpen, | ||
| (value) => { | ||
| if (value) { | ||
| showSettingsButton.value = true | ||
| } | ||
| }, | ||
| { immediate: true } | ||
| ) | ||
|
|
||
| const handleSend = async (content: UserMessageContent) => { | ||
| const threadId = await chatStore.createThread(content.text, { | ||
| providerId: activeModel.value.providerId, | ||
|
|
@@ -461,14 +410,3 @@ const handleSend = async (content: UserMessageContent) => { | |
| chatStore.sendMessage(content) | ||
| } | ||
| </script> | ||
|
|
||
| <style scoped> | ||
| .transition-all { | ||
| transition-property: all; | ||
| transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | ||
| } | ||
|
|
||
| .duration-300 { | ||
| transition-duration: 300ms; | ||
| } | ||
| </style> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -125,7 +125,7 @@ defineEmits(['copy']) | |
| } | ||
|
|
||
| p { | ||
| @apply my-0; | ||
| @apply my-2; | ||
| } | ||
|
|
||
| li p { | ||
|
|
@@ -134,12 +134,32 @@ defineEmits(['copy']) | |
| margin-top: 0; | ||
| margin-bottom: 0; | ||
| } | ||
| h1 { | ||
| @apply text-2xl font-bold my-3 py-0; | ||
| } | ||
| h2 { | ||
| @apply text-xl font-medium my-3 py-0; | ||
| } | ||
| h3 { | ||
| @apply text-base font-medium my-2 py-0; | ||
| } | ||
| h4 { | ||
| @apply text-sm font-medium my-2 py-0; | ||
| } | ||
| h5 { | ||
| @apply text-sm my-1.5 py-0; | ||
| } | ||
| h6 { | ||
| @apply text-sm my-1.5 py-0; | ||
| } | ||
|
|
||
| ul, | ||
| ol { | ||
| @apply my-1.5; | ||
| } | ||
|
|
||
| hr { | ||
| margin-block-start: 0.5em; | ||
| margin-block-end: 0.5em; | ||
| margin-inline-start: auto; | ||
| margin-inline-end: auto; | ||
| @apply my-8; | ||
| } | ||
|
|
||
| /* | ||
|
|
@@ -150,5 +170,30 @@ defineEmits(['copy']) | |
| a .markdown-renderer { | ||
| display: inline; | ||
| } | ||
|
|
||
| .table-node-wrapper { | ||
| @apply border border-border rounded-lg py-0 my-0 overflow-hidden shadow-sm; | ||
| } | ||
|
|
||
| table { | ||
| @apply py-0 my-0; | ||
| /* @apply bg-card border block rounded-lg my-0 py-0 overflow-hidden; */ | ||
| border-collapse: collapse; | ||
| } | ||
|
|
||
| thead, | ||
| thead tr, | ||
| thead th { | ||
| @apply bg-muted; | ||
| } | ||
|
|
||
| th, | ||
| td { | ||
| @apply border-b not-last:border-r border-border; | ||
|
Comment on lines
+190
to
+192
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The new table styling uses Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| tbody tr:last-child td { | ||
| @apply border-b-0; | ||
| } | ||
| } | ||
| </style> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,15 +6,22 @@ | |
| <Icon icon="lucide:refresh-cw-off"></Icon> | ||
| <span>{{ t(block.content || '') }}</span> | ||
| </div> | ||
| <div | ||
| v-else | ||
| class="text-xs bg-red-100 text-red-700 rounded-lg border border-red-400 flex flex-col gap-2 px-2 py-2" | ||
| > | ||
| <div class="flex flex-row gap-2 items-center cursor-pointer"> | ||
| <Icon icon="lucide:info" class="w-4 h-4 text-red-700" /> | ||
| <span class="grow">{{ t('common.error.requestFailed') }}</span> | ||
| <div v-else class="cursor-default select-none"> | ||
| <div | ||
| class="text-xs text-red-500 flex flex-row items-center group" | ||
| @click="isExpanded = !isExpanded" | ||
| > | ||
| {{ t('common.error.requestFailed') | ||
| }}<Icon | ||
| class="hidden group-hover:block ml-2 transition-all" | ||
| :class="[isExpanded ? ' rotate-90' : '']" | ||
| icon="lucide:chevron-right" | ||
| ></Icon> | ||
| </div> | ||
|
Comment on lines
+9
to
20
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add accessibility support for the collapsible error header. The collapsible header should support keyboard navigation and include proper ARIA attributes for screen readers. Apply this diff to improve accessibility: <div
- class="text-xs text-red-500 flex flex-row items-center group"
+ role="button"
+ tabindex="0"
+ :aria-expanded="isExpanded"
+ aria-controls="error-content"
+ class="text-xs text-red-500 flex flex-row items-center group cursor-pointer"
@click="isExpanded = !isExpanded"
+ @keydown.enter="isExpanded = !isExpanded"
+ @keydown.space.prevent="isExpanded = !isExpanded"
>And add an <div
v-if="isExpanded"
+ id="error-content"
class="text-xs max-w-full break-all whitespace-pre-wrap leading-7 text-red-400"
>
🤖 Prompt for AI Agents |
||
| <div class="prose prose-sm max-w-full break-all whitespace-pre-wrap leading-7"> | ||
| <div | ||
| v-if="isExpanded" | ||
| class="text-xs max-w-full break-all whitespace-pre-wrap leading-7 text-red-400" | ||
| > | ||
| {{ t(block.content || '') }} | ||
| </div> | ||
| <div v-if="errorExplanation" class="mt-2 text-red-400 font-medium"> | ||
|
|
@@ -26,14 +33,16 @@ | |
| <script setup lang="ts"> | ||
| import { useI18n } from 'vue-i18n' | ||
| import { Icon } from '@iconify/vue' | ||
| import { computed } from 'vue' | ||
| import { computed, ref } from 'vue' | ||
| import { AssistantMessageBlock } from '@shared/chat' | ||
| const { t } = useI18n() | ||
|
|
||
| const props = defineProps<{ | ||
| block: AssistantMessageBlock | ||
| }>() | ||
|
|
||
| const isExpanded = ref(false) | ||
|
|
||
| const errorExplanation = computed(() => { | ||
| const content = props.block.content || '' | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add accessible labels for the model selection trigger.
The model selection button should include an
aria-labelfor screen readers, especially important for keyboard navigation users.Apply this diff:
<Button variant="ghost" class="flex items-center gap-1.5 h-7 px-2 rounded-md text-xs font-semibold text-muted-foreground hover:bg-muted/60 hover:text-foreground dark:text-white/70 dark:hover:bg-white/10 dark:hover:text-white" size="sm" + :aria-label="t('model.select.trigger')" >You'll need to add the corresponding i18n key. As per coding guidelines
📝 Committable suggestion
🤖 Prompt for AI Agents