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
2 changes: 1 addition & 1 deletion ui/src/components/global/mk-collapse/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const expanded = computed({
class="transition-transform text-N600!"
:class="{ '-rotate-90': !expanded }"
/>
<MkIcon v-else name="icon_down_outlined" :size="16" class="transition-transform text-N600!" :class="{ '-rotate-180': expanded }" />
<MkIcon v-else name="icon_down_outlined" class="transition-transform text-N600!" :class="{ '-rotate-180': expanded }" />
</button>
<slot name="label">
<span>{{ title }}</span>
Expand Down
2 changes: 1 addition & 1 deletion ui/src/components/global/mk-form-list/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ function removeRow(index: number) {
<slot :index="index" :item="item" />
<el-form-item class="shrink-0" :class="firstRowHasLabel ? (index === 0 ? 'mt-8' : 'mt-0.5') : 'mt-1'">
<el-button :disabled="formRows.length <= minRows" text @click="removeRow(index)">
<MkIcon name="icon_delete-trash_outlined" class="text-N600" />
<MkIcon name="icon_delete-trash_outlined" />
</el-button>
</el-form-item>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ function handleSelect(value: TableFilterValue) {
<!-- 打开表头筛选 -->
<el-button text>
<span class="mr-1 font-semibold">{{ label }}</span>
<MkIcon name="icon-filter" :class="hasSelection ? 'text-primary!' : 'text-N600'" />
<MkIcon name="icon-filter" :class="{ 'text-primary!': hasSelection }" />
</el-button>
</component>
</template>
2 changes: 1 addition & 1 deletion ui/src/components/mk-drag-upload/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ defineExpose({ clearFiles })
<slot name="download" :file="selectedFile" />
<!-- 删除文件 -->
<el-button :disabled="disabled" text @click="handleRemove">
<MkIcon name="icon_delete-trash_outlined" :size="16" class="text-N600" />
<MkIcon name="icon_delete-trash_outlined" />
</el-button>
</div>
</div>
Expand Down
9 changes: 8 additions & 1 deletion ui/src/conversation-panel/components/message-input/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,14 @@ export function createMessageInputStore(deps: MessageInputDeps) {
const isFirstMessage = messages.value.length === 0
const abstract = questionText.substring(0, 256)
if (isFirstMessage && !conversations.value.some((conversation) => conversation.id === chatId)) {
conversations.value.unshift({ id: chatId, abstract: questionText ? abstract : '新对话' })
// 本地新会话尚未由历史接口返回,使用首次发送时间展示相对时间。
const createTime = new Date().toISOString()
conversations.value.unshift({
id: chatId,
abstract: questionText ? abstract : '新对话',
create_time: createTime,
update_time: createTime,
})
}

// 一次遍历完成附件分类,仅提交有附件的字段。
Expand Down
86 changes: 7 additions & 79 deletions ui/src/conversation-panel/core/layout/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ const props = withDefaults(
{
leftMode: 'push',
rightMode: 'push',
leftWidth: 260,
rightWidth: 320,
leftWidth: 280,
rightWidth: 420,
},
)

Expand All @@ -31,97 +31,25 @@ const showMask = computed(() => (props.leftMode === 'drawer' && props.leftOpen)
<!-- 布局壳(纯展示):左/主/右三区 + 挤压/抽屉 + 遮罩。状态由外层传入,自身不含业务。 -->
<div class="relative flex h-full overflow-hidden">
<aside
class="side-panel left shrink-0 overflow-hidden bg-N100"
class="mk-conversation-side-panel left shrink-0 overflow-hidden"
:class="[leftMode, { open: leftOpen }]"
:style="{ '--mk-chat-sidebar-width': leftWidth + 'px' }"
>
<div class="side-panel__inner"><slot name="left" /></div>
<div class="mk-conversation-side-panel__inner"><slot name="left" /></div>
</aside>

<slot name="main" />

<aside
v-if="hasRight"
class="side-panel right shrink-0 overflow-hidden bg-N100"
class="mk-conversation-side-panel right shrink-0 overflow-hidden"
:class="[rightMode, { open: rightOpen }]"
:style="{ '--side-w': rightWidth + 'px' }"
:style="{ '--mk-chat-right-width': rightWidth + 'px' }"
>
<div class="side-panel__inner"><slot name="right" /></div>
<div class="mk-conversation-side-panel__inner"><slot name="right" /></div>
</aside>

<!-- 点击遮罩收起抽屉侧栏 -->
<div v-if="showMask" class="mk-conversation-mask" @click="$emit('mask-click')" />
</div>
</template>
<style scoped lang="scss">
.mk-conversation-mask {
position: absolute;
inset: 0;
z-index: 30;
background: var(--el-overlay-color-lighter);
}

.side-panel {
/* 固定侧栏内容宽度,避免展开和收起时内容变形。 */
&__inner {
height: 100%;
overflow-x: hidden;
overflow-y: auto;
width: var(--mk-chat-sidebar-width);
}

/* 挤压侧栏参与布局,仅展开时显示分隔线。 */
&.push {
min-width: 0;
transition: width 0.25s cubic-bezier(0.4, 0, 0.2, 1);
width: 0;

&.open {
width: var(--mk-chat-sidebar-width);

&.left {
border-right: 1px solid var(--mk-N350);
}

&.right {
border-left: 1px solid var(--mk-N350);
}
}
}

/* 抽屉侧栏覆盖主区,层级高于遮罩。 */
&.drawer {
bottom: 0;
position: absolute;
top: 0;
transition:
transform 0.25s cubic-bezier(0.4, 0, 0.2, 1),
box-shadow 0.25s;
width: var(--mk-chat-sidebar-width);
z-index: 40;

&.left {
border-right: 1px solid var(--mk-N350);
left: 0;

&:not(.open) {
transform: translateX(-100%);
}
}

&.open {
box-shadow: var(--mk-box-shadow-lg);
transform: translateX(0);
}

&.right {
border-left: 1px solid var(--mk-N350);
right: 0;

&:not(.open) {
transform: translateX(100%);
}
}
}
}
</style>
83 changes: 83 additions & 0 deletions ui/src/conversation-panel/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,89 @@
position: relative;
}

/* 对话左右两侧 */
.mk-conversation-side-panel {
/* 固定侧栏内容宽度,避免展开和收起时内容变形。 */
&__inner {
height: 100%;
width: var(--mk-conversation-side-panel-width);
}

/* 方向只负责宽度来源和外观,内容与展开模式共用同一宽度。 */
&.left {
--mk-conversation-side-panel-width: var(--mk-chat-sidebar-width);

background: color-mix(in srgb, var(--mk-primary) 5%, var(--mk-N100));
}

&.right {
background: white;
--mk-conversation-side-panel-width: var(--mk-chat-right-width);
}

/* 抽屉侧栏覆盖主区,层级高于遮罩。 */
&.drawer {
bottom: 0;
position: absolute;
top: 0;
transition:
transform 0.25s cubic-bezier(0.4, 0, 0.2, 1),
box-shadow 0.25s;
width: var(--mk-conversation-side-panel-width);
z-index: 40;

&.left {
left: 0;

&:not(.open) {
transform: translateX(-100%);
}
}

&.open {
box-shadow: var(--mk-box-shadow-lg);
transform: translateX(0);
}

&.right {
right: 0;

&:not(.open) {
transform: translateX(100%);
}
}
}
/* 挤压侧栏通过宽度动画参与布局。 */
&.push {
min-width: 0;
transition: width 0.25s cubic-bezier(0.4, 0, 0.2, 1);
width: 0;

&.open {
width: var(--mk-conversation-side-panel-width);
}
}
/* 抽屉保留分隔线,挤压模式仅展开时显示。 */
&.drawer,
&.push.open {
&.left {
border-right: 1px solid var(--mk-N350);
}

&.right {
border-left: 1px solid var(--mk-N350);
}
}
}

/* 对话主抽屉遮罩层 */
.mk-conversation-mask {
position: absolute;
inset: 0;
z-index: 30;
background: var(--el-overlay-color-lighter);
}

/* 对话标题栏:在中性底色上叠加浅主题色。 */
.mk-conversation-header {
background: color-mix(in srgb, var(--mk-primary) 5%, var(--mk-N100));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useStore } from '@/stores'
import { formatTokenNumber } from '@/utils/number'
import { MsgInfo } from '@/utils/message'

defineOptions({ name: 'ConversationAccountMenu' })

const props = defineProps<{ usedTokens?: number; totalTokens?: number }>()
const { user } = useStore()
const menuVisible = ref(false)
const displayName = computed(() => user.userInfo?.nick_name || user.userInfo?.username || '用户')

// 配额尚未接入时保留空值展示,不将缺失数据表示为零用量。
const tokenPercentage = computed(() => {
const { usedTokens, totalTokens } = props
if (usedTokens === undefined || totalTokens === undefined || !Number.isFinite(usedTokens) || !Number.isFinite(totalTokens) || totalTokens <= 0) {
return 0
}
return Math.min(100, Math.max(0, (usedTokens / totalTokens) * 100))
})

// 账户接口后续接入,当前仅提供菜单布局与明确的操作反馈。
const handleAccountAction = (action: string) => MsgInfo(`${action}功能暂未接入`)
</script>

<template>
<MkDropdown class="w-full" trigger="click" placement="top-start" @visible-change="menuVisible = $event">
<!-- 展开账户菜单 -->
<button
type="button"
class="flex-align-center w-full cursor-pointer gap-2 rounded-md py-1 px-2 hover:bg-N900/10"
:class="{ 'bg-N900/10': menuVisible }"
>
<el-avatar :size="32" class="bg-primary-gradient!">
<img src="@/assets/mk_icon_user_gradient.svg" alt="" style="width: 54%" />
</el-avatar>
<span class="truncate" :title="displayName">{{ displayName }}</span>
</button>
<template #dropdown>
<div class="w-60">
<div class="p-3">
<div class="mb-2 flex-align-center gap-2">
<el-avatar :size="40" class="shrink-0 bg-primary-gradient!">
<img src="@/assets/mk_icon_user_gradient.svg" alt="" style="width: 54%" />
</el-avatar>
<div class="min-w-0">
<h4 class="truncate" :title="displayName">{{ displayName }}</h4>
<div class="truncate text-N600" :title="user.userInfo?.username">{{ user.userInfo?.username || '-' }}</div>
</div>
</div>
<div class="mb-1 flex-align-center gap-3">
<span>Tokens</span>
<!-- // TODO UI后期加 超过80%变黄 -->
<span class="text-primary">{{ formatTokenNumber(usedTokens) }}</span>
/
<span class="text-N500">{{ formatTokenNumber(totalTokens) }}</span>
</div>
<el-progress :percentage="tokenPercentage" :show-text="false" :stroke-width="6" />
</div>
<el-divider />
<MkDropdownMenu>
<!-- 修改密码 -->
<MkDropdownItem @click="handleAccountAction('修改密码')">
<template #icon><MkIcon name="icon-key_outlined" /></template>
<span>修改密码</span>
</MkDropdownItem>
<!-- 管理 API Key -->
<MkDropdownItem @click="handleAccountAction('API Key')">
<template #icon><MkIcon name="icon_passkeys_outlined" /></template>
<span>API Key</span>
</MkDropdownItem>
<!-- 退出登录 -->
<MkDropdownItem divided @click="handleAccountAction('退出登录')">
<template #icon><MkIcon name="icon_logout_outlined" /></template>
<span>退出登录</span>
</MkDropdownItem>
</MkDropdownMenu>
</div>
</template>
</MkDropdown>
</template>

<style scoped lang="scss"></style>
Loading
Loading