From ded097e4193fc0439a34a4f998ae8a7bd28a99d3 Mon Sep 17 00:00:00 2001 From: pipipi-pikachu Date: Fri, 10 Jan 2025 10:17:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=BAAIPPT=20hook=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E9=85=8D=E5=9B=BE=E7=9A=84=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useAIPPT.ts | 69 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/src/hooks/useAIPPT.ts b/src/hooks/useAIPPT.ts index eb98510a..8f1a486c 100644 --- a/src/hooks/useAIPPT.ts +++ b/src/hooks/useAIPPT.ts @@ -1,15 +1,25 @@ +import { ref } from 'vue' import { nanoid } from 'nanoid' -import type { PPTElement, PPTShapeElement, PPTTextElement, Slide, TextType } from '@/types/slides' +import type { ImageClipDataRange, PPTElement, PPTImageElement, PPTShapeElement, PPTTextElement, Slide, TextType } from '@/types/slides' import type { AIPPTSlide } from '@/types/AIPPT' import { useSlidesStore } from '@/store' import useAddSlidesOrElements from './useAddSlidesOrElements' import useSlideHandler from './useSlideHandler' +interface PexelsImage { + id: string + src: string + width: number + height: number +} + export default () => { const slidesStore = useSlidesStore() const { addSlidesFromData } = useAddSlidesOrElements() const { isEmptySlide } = useSlideHandler() + const imgPool = ref([]) + const checkTextType = (el: PPTElement, type: TextType) => { return (el.type === 'text' && el.textType === type) || (el.type === 'shape' && el.text && el.text.type === type) } @@ -156,6 +166,50 @@ export default () => { return el.type === 'text' ? { ...el, content, lineHeight: size < 15 ? 1.2 : el.lineHeight } : { ...el, text: { ...el.text!, content } } } + + const getUseableImage = (el: PPTImageElement): PexelsImage | null => { + let img: PexelsImage | null = null + + let imgs = [] + + if (el.width === el.height) imgs = imgPool.value.filter(img => img.width === img.height) + else if (el.width > el.height) imgs = imgPool.value.filter(img => img.width > img.height) + else imgs = imgPool.value.filter(img => img.width <= img.height) + if (!imgs.length) imgs = imgPool.value + + img = imgs[Math.floor(Math.random() * imgs.length)] + imgPool.value = imgPool.value.filter(item => item.id !== img!.id) + + return img + } + + const getNewImgElement = (el: PPTImageElement): PPTImageElement => { + const img = getUseableImage(el) + if (!img) return el + + let scale = 1 + let w = el.width + let h = el.height + let range: ImageClipDataRange = [[0, 0], [0, 0]] + const radio = el.width / el.height + if (img.width / img.height >= radio) { + scale = img.height / el.height + w = img.width / scale + const diff = (w - el.width) / 2 / w * 100 + range = [[diff, 0], [100 - diff, 100]] + } + else { + scale = img.width / el.width + h = img.height / scale + const diff = (h - el.height) / 2 / h * 100 + range = [[0, diff], [100, 100 - diff]] + } + const clipShape = (el.clip && el.clip.shape) ? el.clip.shape : 'rect' + const clip = { range, shape: clipShape } + const src = img.src + + return { ...el, src, clip } + } const getMdContent = (content: string) => { const regex = /```markdown([^```]*)```/ @@ -164,7 +218,9 @@ export default () => { return content.replace('```markdown', '').replace('```', '') } - const AIPPT = (templateSlides: Slide[], _AISlides: AIPPTSlide[]) => { + const AIPPT = (templateSlides: Slide[], _AISlides: AIPPTSlide[], imgs?: PexelsImage[]) => { + if (imgs) imgPool.value = imgs + const AISlides: AIPPTSlide[] = [] for (const template of _AISlides) { if (template.type === 'content') { @@ -255,6 +311,7 @@ export default () => { for (const item of AISlides) { if (item.type === 'cover') { const elements = coverTemplate.elements.map(el => { + if (el.type === 'image' && el.imageType && imgPool.value.length) return getNewImgElement(el) if (el.type !== 'text' && el.type !== 'shape') return el if (checkTextType(el, 'title') && item.data.title) { return getNewTextElement({ el, text: item.data.title, maxLine: 1 }) @@ -289,6 +346,7 @@ export default () => { const longestText = item.data.items.reduce((longest, current) => current.length > longest.length ? current : longest, '') const elements = contentsTemplate.elements.map(el => { + if (el.type === 'image' && el.imageType && imgPool.value.length) return getNewImgElement(el) if (el.type !== 'text' && el.type !== 'shape') return el if (checkTextType(el, 'item')) { const index = sortedItemIds.findIndex(id => id === el.id) @@ -311,6 +369,7 @@ export default () => { else if (item.type === 'transition') { transitionIndex++ const elements = transitionTemplate.elements.map(el => { + if (el.type === 'image' && el.imageType && imgPool.value.length) return getNewImgElement(el) if (el.type !== 'text' && el.type !== 'shape') return el if (checkTextType(el, 'title') && item.data.title) { return getNewTextElement({ el, text: item.data.title, maxLine: 1 }) @@ -362,6 +421,7 @@ export default () => { const longestText = itemTexts.reduce((longest, current) => current.length > longest.length ? current : longest, '') const elements = contentTemplate.elements.map(el => { + if (el.type === 'image' && el.imageType && imgPool.value.length) return getNewImgElement(el) if (el.type !== 'text' && el.type !== 'shape') return el if (item.data.items.length === 1) { const contentItem = item.data.items[0] @@ -402,9 +462,14 @@ export default () => { }) } else if (item.type === 'end') { + const elements = endTemplate.elements.map(el => { + if (el.type === 'image' && el.imageType && imgPool.value.length) return getNewImgElement(el) + return el + }) slides.push({ ...endTemplate, id: nanoid(10), + elements, }) } }