mirror of
https://github.com/open-webui/open-webui.git
synced 2026-08-28 05:27:35 +00:00
refac
This commit is contained in:
parent
b3a5fd3875
commit
f96b717566
3 changed files with 110 additions and 47 deletions
|
|
@ -161,7 +161,7 @@
|
|||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="mr-1 flex flex-none items-center gap-2 self-center">
|
||||
<div class="lg:mr-1 flex flex-none items-center gap-2 self-center">
|
||||
<!-- <div class="md:hidden flex self-center w-[0.0625rem] h-5 mx-2 bg-gray-300 dark:bg-stone-700" /> -->
|
||||
|
||||
{#if $user?.role === 'user' ? ($user?.permissions?.chat?.temporary ?? true) && !($user?.permissions?.chat?.temporary_enforced ?? false) : true}
|
||||
|
|
|
|||
|
|
@ -261,7 +261,7 @@
|
|||
<div
|
||||
class="absolute bottom-3 {hideThumbs
|
||||
? 'left-1/2'
|
||||
: 'left-[calc(160px+(100%-160px)/2)]'} -translate-x-1/2 z-10 flex items-center gap-0.5 rounded-lg bg-white/90 dark:bg-gray-850/90 backdrop-blur-sm shadow-lg border border-gray-200/60 dark:border-gray-700/60 px-1 py-0.5"
|
||||
: 'left-[calc(144px+(100%-144px)/2)]'} -translate-x-1/2 z-10 flex items-center gap-0.5 rounded-lg bg-white/90 dark:bg-gray-850/90 backdrop-blur-sm shadow-lg border border-gray-200/60 dark:border-gray-700/60 px-1 py-0.5"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ type Placeholder = {
|
|||
|
||||
type TextToken = {
|
||||
text: string;
|
||||
fontFace: string;
|
||||
fontPt: number;
|
||||
bold: boolean;
|
||||
italic: boolean;
|
||||
|
|
@ -45,6 +46,8 @@ type TextToken = {
|
|||
width: number;
|
||||
};
|
||||
|
||||
type TextStyle = Omit<TextToken, 'text' | 'width'>;
|
||||
|
||||
const getTag = (el: Element) => el.tagName.split(':').pop();
|
||||
|
||||
const normalizePath = (path: string) => {
|
||||
|
|
@ -263,25 +266,70 @@ const renderBackground = (
|
|||
ctx.fillRect(0, 0, slideW, slideH);
|
||||
};
|
||||
|
||||
const fontString = ({ italic, bold, fontPt }: Pick<TextToken, 'italic' | 'bold' | 'fontPt'>) =>
|
||||
`${italic ? 'italic ' : ''}${bold ? 'bold ' : ''}${fontPt}pt Calibri, Arial, sans-serif`;
|
||||
const directChild = (el: Element, tag: string) =>
|
||||
Array.from(el.children).find((child) => getTag(child) === tag);
|
||||
|
||||
const readRunStyle = (run: Element, defaultFontSize: number) => {
|
||||
const rPr = run.getElementsByTagName('a:rPr')[0];
|
||||
let fontPt = defaultFontSize;
|
||||
let bold = false;
|
||||
let italic = false;
|
||||
let color = '#000000';
|
||||
const defaultTextStyle = (fontPt: number): TextStyle => ({
|
||||
fontFace: 'Calibri',
|
||||
fontPt,
|
||||
bold: false,
|
||||
italic: false,
|
||||
color: '#000000'
|
||||
});
|
||||
|
||||
if (rPr) {
|
||||
if (rPr.getAttribute('b') === '1') bold = true;
|
||||
if (rPr.getAttribute('i') === '1') italic = true;
|
||||
const sz = rPr.getAttribute('sz');
|
||||
if (sz) fontPt = parseInt(sz, 10) / 100;
|
||||
color = solidFillColor(rPr) ?? color;
|
||||
}
|
||||
const fontString = ({ italic, bold, fontPt, fontFace }: TextStyle) => {
|
||||
const family = fontFace.includes(' ') ? `"${fontFace}"` : fontFace;
|
||||
return `${italic ? 'italic ' : ''}${bold ? 'bold ' : ''}${fontPt}pt ${family}, Calibri, Arial, sans-serif`;
|
||||
};
|
||||
|
||||
return { fontPt, bold, italic, color };
|
||||
const readTextStyle = (rPr: Element | undefined, base: TextStyle): TextStyle => {
|
||||
if (!rPr) return base;
|
||||
|
||||
const latin = rPr.getElementsByTagName('a:latin')[0];
|
||||
const typeface = latin?.getAttribute('typeface');
|
||||
const sz = rPr.getAttribute('sz');
|
||||
|
||||
return {
|
||||
fontFace: typeface && !typeface.startsWith('+') ? typeface : base.fontFace,
|
||||
fontPt: sz ? parseInt(sz, 10) / 100 : base.fontPt,
|
||||
bold: rPr.getAttribute('b') === '1' ? true : rPr.getAttribute('b') === '0' ? false : base.bold,
|
||||
italic:
|
||||
rPr.getAttribute('i') === '1' ? true : rPr.getAttribute('i') === '0' ? false : base.italic,
|
||||
color: solidFillColor(rPr) ?? base.color
|
||||
};
|
||||
};
|
||||
|
||||
const readParagraphStyle = (para: Element, defaultFontSize: number) => {
|
||||
const pPr = directChild(para, 'pPr');
|
||||
const defRPr = pPr?.getElementsByTagName('a:defRPr')[0];
|
||||
const endParaRPr = para.getElementsByTagName('a:endParaRPr')[0];
|
||||
return readTextStyle(defRPr ?? endParaRPr, defaultTextStyle(defaultFontSize));
|
||||
};
|
||||
|
||||
const readRunStyle = (run: Element, base: TextStyle) => {
|
||||
const rPr = directChild(run, 'rPr') as Element | undefined;
|
||||
return readTextStyle(rPr, base);
|
||||
};
|
||||
|
||||
const textBodyInsets = (txBody: Element) => {
|
||||
const bodyPr = txBody.getElementsByTagName('a:bodyPr')[0];
|
||||
return {
|
||||
left: emuToPx(parseEmu(bodyPr?.getAttribute('lIns') ?? '91440')),
|
||||
right: emuToPx(parseEmu(bodyPr?.getAttribute('rIns') ?? '91440')),
|
||||
top: emuToPx(parseEmu(bodyPr?.getAttribute('tIns') ?? '45720')),
|
||||
bottom: emuToPx(parseEmu(bodyPr?.getAttribute('bIns') ?? '45720'))
|
||||
};
|
||||
};
|
||||
|
||||
const paragraphBullet = (para: Element) => {
|
||||
const pPr = directChild(para, 'pPr');
|
||||
if (!pPr || pPr.getElementsByTagName('a:buNone')[0]) return '';
|
||||
|
||||
const buChar = pPr.getElementsByTagName('a:buChar')[0]?.getAttribute('char');
|
||||
if (buChar) return `${buChar} `;
|
||||
|
||||
if (pPr.getElementsByTagName('a:buAutoNum')[0]) return '1. ';
|
||||
return '';
|
||||
};
|
||||
|
||||
const drawTextLine = (
|
||||
|
|
@ -293,9 +341,9 @@ const drawTextLine = (
|
|||
w: number
|
||||
) => {
|
||||
const lineWidth = line.reduce((sum, token) => sum + token.width, 0);
|
||||
let cursorX = x + 4;
|
||||
let cursorX = x;
|
||||
if (align === 'center') cursorX = x + Math.max(0, (w - lineWidth) / 2);
|
||||
if (align === 'right') cursorX = x + w - lineWidth - 4;
|
||||
if (align === 'right') cursorX = x + w - lineWidth;
|
||||
|
||||
for (const token of line) {
|
||||
ctx.font = fontString(token);
|
||||
|
|
@ -452,32 +500,42 @@ export async function pptxToImages(
|
|||
ctx.clip();
|
||||
|
||||
const paragraphs = txBody.getElementsByTagName('a:p');
|
||||
let cursorY = y;
|
||||
const insets = textBodyInsets(txBody);
|
||||
const textX = x + insets.left;
|
||||
const textY = y + insets.top;
|
||||
const textW = Math.max(1, w - insets.left - insets.right);
|
||||
const textBottom = y + h - insets.bottom;
|
||||
let cursorY = textY;
|
||||
const defaultFontSize = placeholderFontSize(ph ?? placeholder ?? null);
|
||||
|
||||
for (let pi = 0; pi < paragraphs.length; pi++) {
|
||||
const para = paragraphs[pi];
|
||||
const runs = para.getElementsByTagName('a:r');
|
||||
const align = paragraphAlign(para, ph ?? placeholder ?? null);
|
||||
const paragraphStyle = readParagraphStyle(para, defaultFontSize);
|
||||
const textParts: Array<{ text: string; style: TextStyle } | { newline: true }> = [];
|
||||
const bullet = paragraphBullet(para);
|
||||
|
||||
if (runs.length === 0) {
|
||||
cursorY += defaultFontSize * 1.5;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate max font size in this paragraph for line height
|
||||
let maxFontPt = defaultFontSize;
|
||||
for (let ri = 0; ri < runs.length; ri++) {
|
||||
const rPr = runs[ri].getElementsByTagName('a:rPr')[0];
|
||||
if (rPr) {
|
||||
const sz = rPr.getAttribute('sz');
|
||||
if (sz) {
|
||||
const pt = parseInt(sz, 10) / 100;
|
||||
if (pt > maxFontPt) maxFontPt = pt;
|
||||
}
|
||||
for (const child of Array.from(para.children)) {
|
||||
const tag = getTag(child);
|
||||
if (tag === 'br') {
|
||||
textParts.push({ newline: true });
|
||||
continue;
|
||||
}
|
||||
if (tag !== 'r' && tag !== 'fld') continue;
|
||||
|
||||
const style = tag === 'r' ? readRunStyle(child, paragraphStyle) : paragraphStyle;
|
||||
const text = child.getElementsByTagName('a:t')[0]?.textContent ?? '';
|
||||
if (text) textParts.push({ text, style });
|
||||
}
|
||||
|
||||
if (bullet && textParts.some((part) => 'text' in part && part.text.trim())) {
|
||||
textParts.unshift({ text: bullet, style: paragraphStyle });
|
||||
}
|
||||
|
||||
const maxFontPt = textParts.reduce(
|
||||
(max, part) => ('text' in part ? Math.max(max, part.style.fontPt) : max),
|
||||
paragraphStyle.fontPt
|
||||
);
|
||||
const lineHeight = maxFontPt * 1.4;
|
||||
cursorY += maxFontPt; // baseline offset
|
||||
|
||||
|
|
@ -485,22 +543,27 @@ export async function pptxToImages(
|
|||
let lineWidth = 0;
|
||||
const flushLine = () => {
|
||||
if (line.length === 0) return;
|
||||
if (cursorY <= y + h) {
|
||||
drawTextLine(ctx, line, align, x, cursorY, w);
|
||||
if (cursorY <= textBottom) {
|
||||
drawTextLine(ctx, line, align, textX, cursorY, textW);
|
||||
}
|
||||
line = [];
|
||||
lineWidth = 0;
|
||||
cursorY += lineHeight;
|
||||
};
|
||||
|
||||
for (let ri = 0; ri < runs.length; ri++) {
|
||||
const run = runs[ri];
|
||||
const text = run.getElementsByTagName('a:t')[0]?.textContent ?? '';
|
||||
if (!text) continue;
|
||||
if (textParts.length === 0) {
|
||||
cursorY += lineHeight;
|
||||
continue;
|
||||
}
|
||||
|
||||
const style = readRunStyle(run, defaultFontSize);
|
||||
for (const part of textParts) {
|
||||
if ('newline' in part) {
|
||||
if (line.length > 0) flushLine();
|
||||
else cursorY += lineHeight;
|
||||
continue;
|
||||
}
|
||||
const { text, style } = part;
|
||||
ctx.font = fontString(style);
|
||||
|
||||
ctx.textBaseline = 'alphabetic';
|
||||
|
||||
// Simple word-wrap within the shape bounds
|
||||
|
|
@ -511,10 +574,10 @@ export async function pptxToImages(
|
|||
flushLine();
|
||||
continue;
|
||||
}
|
||||
if (cursorY > y + h) break;
|
||||
if (cursorY > textBottom) break;
|
||||
|
||||
const width = ctx.measureText(word).width;
|
||||
if (lineWidth + width > w - 8 && line.length > 0) {
|
||||
if (lineWidth + width > textW && line.length > 0) {
|
||||
flushLine();
|
||||
if (word.trim() === '') continue;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue