feat: 添加AI摘要功能并优化博客详情页布局
This commit is contained in:
1
auto-imports.d.ts
vendored
1
auto-imports.d.ts
vendored
@ -70,7 +70,6 @@ declare global {
|
||||
const useCssVars: typeof import('vue')['useCssVars']
|
||||
const useDialog: typeof import('naive-ui')['useDialog']
|
||||
const useId: typeof import('vue')['useId']
|
||||
const useLink: typeof import('vue-router')['useLink']
|
||||
const useLoadingBar: typeof import('naive-ui')['useLoadingBar']
|
||||
const useMessage: typeof import('naive-ui')['useMessage']
|
||||
const useModel: typeof import('vue')['useModel']
|
||||
|
||||
4
components.d.ts
vendored
4
components.d.ts
vendored
@ -17,8 +17,12 @@ declare module 'vue' {
|
||||
Mask: typeof import('./src/components/mask.vue')['default']
|
||||
MenuH: typeof import('./src/components/menuH.vue')['default']
|
||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
||||
NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
|
||||
NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
|
||||
NButton: typeof import('naive-ui')['NButton']
|
||||
NCard: typeof import('naive-ui')['NCard']
|
||||
NCollapse: typeof import('naive-ui')['NCollapse']
|
||||
NCollapseItem: typeof import('naive-ui')['NCollapseItem']
|
||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
|
||||
NDropdown: typeof import('naive-ui')['NDropdown']
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"b": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build"
|
||||
@ -41,7 +41,7 @@
|
||||
"naive-ui": "^2.43.2",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"nprogress": "^0.2.0",
|
||||
"typescript": "~5.8.0",
|
||||
"typescript": "^5.9.3",
|
||||
"vfonts": "^0.0.3",
|
||||
"vite": "^7.0.6",
|
||||
"vite-plugin-vue-devtools": "^8.0.0",
|
||||
|
||||
3
src/icon/star.svg
Normal file
3
src/icon/star.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
||||
<path fill-rule="evenodd" d="M9 4.5a.75.75 0 0 1 .721.544l.813 2.846a3.75 3.75 0 0 0 2.576 2.576l2.846.813a.75.75 0 0 1 0 1.442l-2.846.813a3.75 3.75 0 0 0-2.576 2.576l-.813 2.846a.75.75 0 0 1-1.442 0l-.813-2.846a3.75 3.75 0 0 0-2.576-2.576l-2.846-.813a.75.75 0 0 1 0-1.442l2.846-.813A3.75 3.75 0 0 0 7.466 7.89l.813-2.846A.75.75 0 0 1 9 4.5ZM18 1.5a.75.75 0 0 1 .728.568l.258 1.036c.236.94.97 1.674 1.91 1.91l1.036.258a.75.75 0 0 1 0 1.456l-1.036.258c-.94.236-1.674.97-1.91 1.91l-.258 1.036a.75.75 0 0 1-1.456 0l-.258-1.036a2.625 2.625 0 0 0-1.91-1.91l-1.036-.258a.75.75 0 0 1 0-1.456l1.036-.258a2.625 2.625 0 0 0 1.91-1.91l.258-1.036A.75.75 0 0 1 18 1.5ZM16.5 15a.75.75 0 0 1 .712.513l.394 1.183c.15.447.5.799.948.948l1.183.395a.75.75 0 0 1 0 1.422l-1.183.395c-.447.15-.799.5-.948.948l-.395 1.183a.75.75 0 0 1-1.422 0l-.395-1.183a1.5 1.5 0 0 0-.948-.948l-1.183-.395a.75.75 0 0 1 0-1.422l1.183-.395c.447-.15.799-.5.948-.948l.395-1.183A.75.75 0 0 1 16.5 15Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@ -6,20 +6,56 @@
|
||||
<MdCatalog :editorId="blogData.aid" class="my-cata" :scrollElement="scrollElement" />
|
||||
</div>
|
||||
</div>
|
||||
<div ref="" class="flex-1">
|
||||
<div class="w-full px-1/20 rounded-md">
|
||||
<div class="text-center text-[40px] text-[700] text-black` mt-10">{{ blogData.title }}</div>
|
||||
<div class="flex flex-wrap gap-4 justify-center mt-4">
|
||||
<div class="flex-1">
|
||||
<n-breadcrumb class="m-2" separator="|">
|
||||
<n-breadcrumb-item href="/blog/blog">
|
||||
文章
|
||||
</n-breadcrumb-item>
|
||||
<n-breadcrumb-item>
|
||||
{{ blogData.title }}
|
||||
</n-breadcrumb-item>
|
||||
</n-breadcrumb>
|
||||
<div class="w-full px-1/20">
|
||||
<!-- <div class="text-center text-[40px] font-bold text-black` mt-10">{{ blogData.title }}</div> -->
|
||||
<div class="my-4 bg-white shadow-lg p-4 rounded-md">
|
||||
<n-collapse @item-header-click="AISum" arrow-placement="right">
|
||||
<n-collapse-item>
|
||||
<template #arrow>
|
||||
<div class=" text-primary ml-2">
|
||||
<!-- <icon-star class="w-6" /> -->
|
||||
</div>
|
||||
</template>
|
||||
<template #header>
|
||||
<div class="text-primary flex">
|
||||
<icon-star class="w-6 mx-2" />
|
||||
<div class="text-center text-[20px] font-bold">AI 摘要</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #header-extra>
|
||||
<div class="text-primary flex">
|
||||
<icon-star class="w-6 mx-2" />
|
||||
</div>
|
||||
</template>
|
||||
<div class="indent-lg text-gray-500">
|
||||
{{ msg.replaceAll('\\n', '<br>') }}
|
||||
</div>
|
||||
</n-collapse-item>
|
||||
</n-collapse>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 justify-center my-4">
|
||||
<em class="text-primary">
|
||||
{{ blogData.nickname }}</em>
|
||||
<em class="time">{{ formatTime(blogData.updated_at, "YYYY年MM月DD日hh时") }}</em>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-4 justify-center my-4">
|
||||
<div v-for="item in tags" :key="item.tid" class="flex items-center gap-1 text-primary cursor-pointer">
|
||||
<n-icon><icon-tag /></n-icon>
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 justify-end my-4">
|
||||
<em class="text-primary">
|
||||
{{ blogData.nickname }}</em>
|
||||
<em class="time">{{ formatTime(blogData.updated_at, "YYYY年MM月DD日hh时") }}</em>
|
||||
</div>
|
||||
|
||||
<MdPreview ref="mdp" theme="light" class="relative " :editorId="blogData.aid" previewTheme="my"
|
||||
:modelValue="markdown" />
|
||||
</div>
|
||||
@ -28,17 +64,6 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import { formatTime } from '@/util';
|
||||
import type { ExposeParam } from 'md-editor-v3';
|
||||
import { MdCatalog, MdPreview } from 'md-editor-v3';
|
||||
|
||||
|
||||
const editorRef = ref<ExposeParam>();
|
||||
const scrollElement: any = document.querySelector('.n-scrollbar .n-scrollbar-container');
|
||||
|
||||
const tags = ref<any[]>([])
|
||||
const route: any = useRoute()
|
||||
definePage({
|
||||
name: 'blog/:bid',
|
||||
path: '/blog/:bid',
|
||||
@ -46,6 +71,20 @@ definePage({
|
||||
title: '文章详情',
|
||||
}
|
||||
})
|
||||
|
||||
import { formatTime } from '@/util';
|
||||
import type { ExposeParam } from 'md-editor-v3';
|
||||
import { MdCatalog, MdPreview } from 'md-editor-v3';
|
||||
|
||||
const editorRef = ref<ExposeParam>();
|
||||
const scrollElement: HTMLElement = document.querySelector('.n-scrollbar .n-scrollbar-container') as HTMLElement;
|
||||
|
||||
const tags = ref<any[]>([])
|
||||
const route: any = useRoute()
|
||||
|
||||
const msg = ref<any>('...')
|
||||
const aimask = ref(false)
|
||||
|
||||
const markdown = ref('');
|
||||
const blogData = ref<any>({
|
||||
aid: 'preview-only',
|
||||
@ -62,8 +101,46 @@ async function getBlogDetail() {
|
||||
tags.value = ts.split(',')
|
||||
editorRef.value?.toggleCatalog(true)
|
||||
markdown.value = res.data.cont
|
||||
// AISum()
|
||||
}
|
||||
|
||||
// ai总结
|
||||
|
||||
async function AISum() {
|
||||
console.log('>>> --> AISum --> AISum:', AISum)
|
||||
if (aimask.value) return
|
||||
const response = await fetch('https://api.siliconflow.cn/v1/chat/completions', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: 'Bearer sk-jwwmhmxsjtseyekknqmamlvzmrkmwfvuacnssbwfufogrkdg'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "Qwen/Qwen3-8B",
|
||||
messages: [{ role: 'user', content: "用一段话对以下内容做一个摘要(语气可爱带表情):" + blogData.value.cont }],
|
||||
stream: true
|
||||
})
|
||||
})
|
||||
console.log('>>> --> AISum --> response:', response)
|
||||
const reader = response.body?.getReader()
|
||||
const decoder = new TextDecoder('utf-8')
|
||||
if (!reader) return
|
||||
msg.value = ''
|
||||
let done = false
|
||||
while (!done) {
|
||||
const { value, done: doneReading } = await reader.read()
|
||||
done = doneReading
|
||||
const chunk = decoder.decode(value)
|
||||
const match = chunk.match(/"content":"(.*?)"/)
|
||||
if (match && match[1]) {
|
||||
msg.value += match[1]
|
||||
aimask.value = true
|
||||
}
|
||||
}
|
||||
// const data = await response.json()
|
||||
|
||||
// console.log('>>> --> AISum --> data:', data)
|
||||
}
|
||||
onMounted(() => {
|
||||
getBlogDetail()
|
||||
})
|
||||
@ -106,7 +183,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.md-editor-catalog-link {
|
||||
padding-block:1px !important;
|
||||
padding-block: 1px !important;
|
||||
}
|
||||
|
||||
.md-editor-catalog-wrapper span {
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"target": "es2021",
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"types":["unplugin-vue-router/client"],
|
||||
"paths": {
|
||||
|
||||
@ -11,7 +11,8 @@
|
||||
"compilerOptions": {
|
||||
"noEmit": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
|
||||
"target": "es2021",
|
||||
"lib": ["es2021.string", "dom"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
|
||||
@ -4020,10 +4020,10 @@ tslib@^2.1.0, tslib@^2.3.0:
|
||||
resolved "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
|
||||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||
|
||||
typescript@~5.8.0:
|
||||
version "5.8.3"
|
||||
resolved "https://registry.npmmirror.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e"
|
||||
integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==
|
||||
typescript@^5.9.3:
|
||||
version "5.9.3"
|
||||
resolved "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
|
||||
integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
|
||||
|
||||
uc.micro@^1.0.1, uc.micro@^1.0.5:
|
||||
version "1.0.6"
|
||||
|
||||
Reference in New Issue
Block a user