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 useCssVars: typeof import('vue')['useCssVars']
|
||||||
const useDialog: typeof import('naive-ui')['useDialog']
|
const useDialog: typeof import('naive-ui')['useDialog']
|
||||||
const useId: typeof import('vue')['useId']
|
const useId: typeof import('vue')['useId']
|
||||||
const useLink: typeof import('vue-router')['useLink']
|
|
||||||
const useLoadingBar: typeof import('naive-ui')['useLoadingBar']
|
const useLoadingBar: typeof import('naive-ui')['useLoadingBar']
|
||||||
const useMessage: typeof import('naive-ui')['useMessage']
|
const useMessage: typeof import('naive-ui')['useMessage']
|
||||||
const useModel: typeof import('vue')['useModel']
|
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']
|
Mask: typeof import('./src/components/mask.vue')['default']
|
||||||
MenuH: typeof import('./src/components/menuH.vue')['default']
|
MenuH: typeof import('./src/components/menuH.vue')['default']
|
||||||
NAvatar: typeof import('naive-ui')['NAvatar']
|
NAvatar: typeof import('naive-ui')['NAvatar']
|
||||||
|
NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
|
||||||
|
NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
|
||||||
NButton: typeof import('naive-ui')['NButton']
|
NButton: typeof import('naive-ui')['NButton']
|
||||||
NCard: typeof import('naive-ui')['NCard']
|
NCard: typeof import('naive-ui')['NCard']
|
||||||
|
NCollapse: typeof import('naive-ui')['NCollapse']
|
||||||
|
NCollapseItem: typeof import('naive-ui')['NCollapseItem']
|
||||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||||
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
|
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
|
||||||
NDropdown: typeof import('naive-ui')['NDropdown']
|
NDropdown: typeof import('naive-ui')['NDropdown']
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "run-p type-check \"build-only {@}\" --",
|
"b": "run-p type-check \"build-only {@}\" --",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"build-only": "vite build",
|
"build-only": "vite build",
|
||||||
"type-check": "vue-tsc --build"
|
"type-check": "vue-tsc --build"
|
||||||
@ -41,7 +41,7 @@
|
|||||||
"naive-ui": "^2.43.2",
|
"naive-ui": "^2.43.2",
|
||||||
"npm-run-all2": "^8.0.4",
|
"npm-run-all2": "^8.0.4",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"typescript": "~5.8.0",
|
"typescript": "^5.9.3",
|
||||||
"vfonts": "^0.0.3",
|
"vfonts": "^0.0.3",
|
||||||
"vite": "^7.0.6",
|
"vite": "^7.0.6",
|
||||||
"vite-plugin-vue-devtools": "^8.0.0",
|
"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" />
|
<MdCatalog :editorId="blogData.aid" class="my-cata" :scrollElement="scrollElement" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ref="" class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="w-full px-1/20 rounded-md">
|
<n-breadcrumb class="m-2" separator="|">
|
||||||
<div class="text-center text-[40px] text-[700] text-black` mt-10">{{ blogData.title }}</div>
|
<n-breadcrumb-item href="/blog/blog">
|
||||||
<div class="flex flex-wrap gap-4 justify-center mt-4">
|
文章
|
||||||
|
</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">
|
<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>
|
<n-icon><icon-tag /></n-icon>
|
||||||
{{ item }}
|
{{ item }}
|
||||||
</div>
|
</div>
|
||||||
</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"
|
<MdPreview ref="mdp" theme="light" class="relative " :editorId="blogData.aid" previewTheme="my"
|
||||||
:modelValue="markdown" />
|
:modelValue="markdown" />
|
||||||
</div>
|
</div>
|
||||||
@ -28,17 +64,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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({
|
definePage({
|
||||||
name: 'blog/:bid',
|
name: 'blog/:bid',
|
||||||
path: '/blog/:bid',
|
path: '/blog/:bid',
|
||||||
@ -46,6 +71,20 @@ definePage({
|
|||||||
title: '文章详情',
|
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 markdown = ref('');
|
||||||
const blogData = ref<any>({
|
const blogData = ref<any>({
|
||||||
aid: 'preview-only',
|
aid: 'preview-only',
|
||||||
@ -62,8 +101,46 @@ async function getBlogDetail() {
|
|||||||
tags.value = ts.split(',')
|
tags.value = ts.split(',')
|
||||||
editorRef.value?.toggleCatalog(true)
|
editorRef.value?.toggleCatalog(true)
|
||||||
markdown.value = res.data.cont
|
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(() => {
|
onMounted(() => {
|
||||||
getBlogDetail()
|
getBlogDetail()
|
||||||
})
|
})
|
||||||
@ -106,7 +183,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.md-editor-catalog-link {
|
.md-editor-catalog-link {
|
||||||
padding-block:1px !important;
|
padding-block: 1px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.md-editor-catalog-wrapper span {
|
.md-editor-catalog-wrapper span {
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
],
|
],
|
||||||
"exclude": ["src/**/__tests__/*"],
|
"exclude": ["src/**/__tests__/*"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"target": "es2021",
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
"types":["unplugin-vue-router/client"],
|
"types":["unplugin-vue-router/client"],
|
||||||
"paths": {
|
"paths": {
|
||||||
|
|||||||
@ -11,7 +11,8 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||||
|
"target": "es2021",
|
||||||
|
"lib": ["es2021.string", "dom"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "Bundler",
|
||||||
"types": ["node"]
|
"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"
|
resolved "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
|
||||||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||||
|
|
||||||
typescript@~5.8.0:
|
typescript@^5.9.3:
|
||||||
version "5.8.3"
|
version "5.9.3"
|
||||||
resolved "https://registry.npmmirror.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e"
|
resolved "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f"
|
||||||
integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==
|
integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==
|
||||||
|
|
||||||
uc.micro@^1.0.1, uc.micro@^1.0.5:
|
uc.micro@^1.0.1, uc.micro@^1.0.5:
|
||||||
version "1.0.6"
|
version "1.0.6"
|
||||||
|
|||||||
Reference in New Issue
Block a user