feat(web): chat views (Home/Detail/CreateModal) + Bubble/Uploader/ModelSelector + markdown-it

This commit is contained in:
2026-05-04 20:09:11 +08:00
parent d06ff1bf8c
commit f294ac649c
8 changed files with 641 additions and 0 deletions
@@ -0,0 +1,90 @@
<template>
<div class="flex gap-2 items-center flex-wrap">
<NUpload
:custom-request="upload"
:show-file-list="false"
multiple
>
<NButton size="small">
添加附件
</NButton>
</NUpload>
<div
v-for="a in attachments"
:key="a.id"
class="text-xs px-2 py-1 bg-neutral-100 rounded flex items-center gap-1"
>
<span>{{ a.filename }} ({{ Math.round(a.size_bytes / 1024) }} KB)</span>
<NButton
text
size="tiny"
@click="remove(a.id)"
>
×
</NButton>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { NUpload, NButton, useMessage } from 'naive-ui'
import type { UploadCustomRequestOptions } from 'naive-ui'
import { chatApi, type Attachment } from '@/api/chat'
const props = defineProps<{
modelCapabilities?: { image: boolean; pdf: boolean; audio?: boolean }
}>()
const emit = defineEmits<{ change: [ids: string[]] }>()
const attachments = ref<Attachment[]>([])
const msg = useMessage()
async function upload(opts: UploadCustomRequestOptions) {
const f = opts.file.file
if (!f) {
opts.onError()
return
}
if (f.type.startsWith('image/') && !props.modelCapabilities?.image) {
msg.error('当前模型不支持图片')
opts.onError()
return
}
if (f.type === 'application/pdf' && !props.modelCapabilities?.pdf) {
msg.error('当前模型不支持 PDF')
opts.onError()
return
}
if (f.size > 20 * 1024 * 1024) {
msg.error('单文件不能超过 20 MB')
opts.onError()
return
}
try {
const a = await chatApi.uploadAttachment(f)
attachments.value.push(a)
opts.onFinish()
} catch (e) {
msg.error(e instanceof Error ? e.message : String(e))
opts.onError()
}
}
function remove(id: string) {
attachments.value = attachments.value.filter((a) => a.id !== id)
}
watch(
attachments,
() => emit('change', attachments.value.map((a) => a.id)),
{ deep: true },
)
defineExpose({
clear: () => {
attachments.value = []
},
})
</script>