完善ai排版,可以将文件内容使用大模型进行初步排版后,应用用户定义的样式规则,如代码块、块引用、文本样式等,可以设置每级标题的样式如(一) 一、 I. 等并自动计数,不会改变原有的图片样式,仅对文本进行处理,不会修改原有文本的内容。

master
joefalmko 9 months ago
parent 8f29601051
commit 022d7e16c3

@ -1,4 +1,5 @@
请将以下文章进行重新排版,使其格式更加美观且井然有序,同时保留文章的所有原始内容和文字,不要对内容进行任何修改。
请你调整标题样式,将所有标题改为清晰的分级结构。文章标题使用#,一级标题使用##,以此类推。
正文部分的段落需适当分段,避免过长或过短。 请确保整体排版整洁、易于阅读。
所有的图片已被替换为tag“图片1”、“图片2”等在处理时不要考虑图片本身不要删除这些tag。
不要对文章的原始内容进行修改。只返回重新排版后的内容,不要返回其他的额外内容。

@ -83,8 +83,9 @@ import {
getPageContent,
getUserConfigFromBackend,
saveData,
// markdown2html,
html2markdown
markdown2html,
html2markdown,
getUserAILayoutConfig
} from './utils';
import mitt from 'mitt';
// 导出为docx插件
@ -384,22 +385,162 @@ class SaveButton extends Plugin {
}
}
// AI 自动排版
function aiformat(){
async function aiformat() {
console.log("ai formatting")
const editor = window.editor;
const doc_content = editor.getData()
let doc_content = editor.getData()
console.log(doc_content);
// TODO 处理html文件
// step 1 - split images and insert text tag
// match <figure ... </figure>
const img_tag = /<figure.*?>(.*?)<\/figure>/g
const img_list = doc_content.match(img_tag)
console.log(img_list)
// replace img tag with text tag
if (img_list) {
console.log("replace img tag")
for (let i = 0; i < img_list.length; i++) {
const img = img_list[i]
const text = `<text>图片${i + 1}</text>`
doc_content = doc_content.replace(img, text)
}
}
const markdown_content = html2markdown(doc_content)
console.log(markdown_content)
// TODO 请求大模型
// step 2 - convert markdown response to html text and setData
// 向后端调用API并接受response
var result = ''
try {
// const response = await fetch('/web_api/admin/ai_layout/layout_generate', {
const response = await fetch('http://localhost:14514/admin/ai_layout/layout_generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
doc_content: markdown_content,
})
});
if (!response.body) {
throw new Error('No response body');
}
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
/* eslint-disable no-constant-condition */
let i = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
const slice = decoder.decode(value, { stream: true });
result += slice;
i++;
if (i % 10 == 0) {
// 流式展示ai排版后的内容
const html_content = markdown2html(result)
editor.setData(html_content)
}
}
/* eslint-enable no-constant-condition */
} catch (error) {
console.error('Error:', error);
}
// step 3 - recover original images
const markdown_response = result
let html_content = markdown2html(markdown_response)
console.log("html_content:\n\n",html_content)
// insert original img tag
if (img_list) {
console.log("insert img tag")
for (let i = 0; i < img_list.length; i++) {
const img = img_list[i]
const text = `图片${i + 1}`
html_content = html_content.replace(text, img)
}
}
editor.setData(html_content)
// step 4 - fetch users config
const user_config = getUserAILayoutConfig()
// step 5 - apply title styles of user
// step 6 - apply others styles of user
// get document element
const pageContent = document.querySelector("#app > div > div > div.editor-container.editor-container_document-editor.editor-container_include-style > div.editor-container__editor-wrapper > div > div > div")
// 处理title样式
let title = pageContent.querySelector("h1")
if (title) {
title.classList.add(user_config.titleStyle.option)
}
// 处理一二三级heading样式
for (let i = 0; i < 3; i++) {
const heading = pageContent.querySelectorAll(`h${i + 2}`);
if (heading.length > 0) {
const headingTag = user_config.headingStyle.option[i][0];
const headingClass = user_config.headingStyle.option[i][1];
console.log("headingTag:\n\n",headingTag)
console.log("headingClass:\n\n",headingClass)
// for each element
heading.forEach((element) => {
// reset counter for heading
const parentNode = element.parentNode;
let currentCounterReset = parentNode.style.counterReset;
if (currentCounterReset) {
// currentCounterReset 不存在该counterreset
if (currentCounterReset.indexOf(headingClass + "counter") == -1) {
currentCounterReset += " " + headingClass + "counter";
}
} else {
currentCounterReset = headingClass + "counter";
}
parentNode.style.setProperty('counter-reset', currentCounterReset);
element.classList.add(headingTag,headingClass);
})
}
}
// 处理正文样式
const paragraph = pageContent.querySelectorAll("p")
if (paragraph.length > 0) {
for (let i = 0; i < paragraph.length; i++) {
const element = paragraph[i];
for(let i = 0; i < user_config.bodyStyle.option.length; i++) {
element.classList.add(user_config.bodyStyle.option[i]);
}
}
}
// 处理块引用样式
const blockquote = pageContent.querySelectorAll("blockquote")
if (blockquote.length > 0) {
for (let i = 0; i < blockquote.length; i++) {
const element = blockquote[i];
for(let i = 0; i < user_config.blockquote.option.length; i++) {
element.classList.add(user_config.blockquote.option[i]);
}
}
}
// 处理代码块样式
const pre = pageContent.querySelectorAll("pre")
if (pre.length > 0) {
for (let i = 0; i < pre.length; i++) {
const element = pre[i];
for(let i = 0; i < user_config.codeBlockStyle.option.length; i++) {
element.classList.add(user_config.codeBlockStyle.option[i]);
}
}
}
// 处理列表样式
const ul = pageContent.querySelectorAll("ul")
if (ul.length > 0) {
for (let i = 0; i < ul.length; i++) {
ul[i].classList.add(user_config.listStyle.option);
}
}
}
class AiFormat extends Plugin {

@ -155,3 +155,47 @@ export function html2markdown(htmlString){
const markdownString = htmltomarkdown.parse(htmlString);
return markdownString
}
// 请求用户AI生成的样式配置
export function getUserAILayoutConfig() {
// TODO 请求用户AI生成的样式配置
const options = {};
// 对应的样式定义在`generatedStyle.css`中
const {
// title
titleStyleOption = 'title1',
// 标题样式配置 如 一、二 12 1. 2. I. II. A. B. (一)、(二)
// 每级标题按顺序应用 [标题大小,标题样式] 大小也可自定义
headingStyleOption = [['h3', 'heading1'], ['h4', 'heading2'], ['h5', 'heading3']], // limit 3
// 列表样式配置
listStyleOption = 'square',
// 下面的各项样式每一个对应的均会应用
// 正文样式配置 小四宋体
bodyStyleOption = ['normal-text'],
// 块引用样式配置
blockquoteStyleOption = ['side-quote'],
// 代码块样式配置
codeBlockStyleOption = ['fancy-code', 'fancy-code-bright'],
} = options;
// 如果传入的options没有对应项使用默认值
return {
titleStyle: {
option: titleStyleOption
},
headingStyle: {
option: headingStyleOption
},
bodyStyle: {
option: bodyStyleOption
},
blockquoteStyle: {
option: blockquoteStyleOption
},
codeBlockStyle: {
option: codeBlockStyleOption
},
listStyle: {
option: listStyleOption
}
};
}

@ -0,0 +1,205 @@
/* default */
/* title style */
/* 标题样式一 黑体二号粗体 */
.ck-content .title1{
font-family: '黑体';
font-size: 29.3px;
font-weight: 'bold';
}
/* heading style */
/* 使用前必须在对应元素的父元素中将对应计数器重置 */
.ck-content .heading1::before{
counter-increment: heading1counter;
/* (一)(二) */
content: "" counter(heading1counter, cjk-ideographic) " ";
}
.ck-content .heading2::before{
counter-increment: heading2counter;
/* 一、 二、 */
content: counter(heading2counter, cjk-ideographic) "、 ";
}
.ck-content .heading3::before{
counter-increment: heading3counter;
/* 1. 2. */
content: counter(heading3counter) ". ";
}
.ck-content .heading4::before{
counter-increment: heading4counter;
/* 1) 2) */
content: counter(heading4counter) ") ";
}
.ck-content .heading5::before{
counter-increment: heading5counter;
/* 第一章 第二章 */
content: "第" counter(heading5counter, cjk-ideographic) "章 ";
}
.ck-content .heading6::before{
counter-increment: heading6counter;
/* 第一小节 第二小节 */
content: "第" counter(heading6counter, cjk-ideographic) "小节 ";
}
.ck-content .heading7::before{
counter-increment: heading7counter;
/* I. II. */
content: counter(heading7counter, upper-roman) ". ";
}
.ck-content .heading8::before{
counter-increment: heading8counter;
/* A. B. */
content: counter(heading8counter,upper-alpha) ". ";
}
.ck-content .heading9::before{
counter-increment: heading9counter;
/* a. b. */
content: counter(heading9counter,lower-alpha) ". ";
}
.ck-content .heading10::before{
counter-increment: heading10counter;
/* i. ii. */
content: counter(heading10counter, lower-roman) ". ";
}
/*style插件样式*/
.ck-content h3.category {
font-family: 'Oswald';
font-size: 20px;
font-weight: bold;
color: #555;
letter-spacing: 10px;
margin: 0;
padding: 0;
}
.ck-content h2.document-title {
font-family: 'Oswald';
font-size: 50px;
font-weight: bold;
margin: 0;
padding: 0;
border: 0;
}
.ck-content h3.document-subtitle {
font-family: 'Oswald';
font-size: 20px;
color: #555;
margin: 0 0 1em;
font-weight: bold;
padding: 0;
}
.ck-content p.info-box {
--background-size: 30px;
--background-color: #e91e63;
padding: 1.2em 2em;
border: 1px solid var(--background-color);
background: linear-gradient(135deg,
var(--background-color) 0%,
var(--background-color) var(--background-size),
transparent var(--background-size)),
linear-gradient(135deg,
transparent calc(100% - var(--background-size)),
var(--background-color) calc(100% - var(--background-size)),
var(--background-color));
border-radius: 10px;
margin: 1.5em 2em;
box-shadow: 5px 5px 0 #ffe6ef;
}
.ck-content blockquote.side-quote {
font-family: 'Oswald';
font-style: normal;
float: right;
width: 35%;
position: relative;
border: 0;
overflow: visible;
z-index: 1;
margin-left: 1em;
}
.ck-content blockquote.side-quote::before {
content: '“';
position: absolute;
top: -37px;
left: -10px;
display: block;
font-size: 200px;
color: #e7e7e7;
z-index: -1;
line-height: 1;
}
.ck-content blockquote.side-quote p {
font-size: 2em;
line-height: 1;
}
.ck-content blockquote.side-quote p:last-child:not(:first-child) {
font-size: 1.3em;
text-align: right;
color: #555;
}
.ck-content span.marker {
background: yellow;
}
.ck-content span.spoiler {
background: #000;
color: #000;
}
.ck-content span.spoiler:hover {
background: #000;
color: #fff;
}
.ck-content pre.fancy-code {
border: 0;
margin-left: 2em;
margin-right: 2em;
border-radius: 10px;
}
.ck-content pre.fancy-code::before {
content: '';
display: block;
height: 13px;
background: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1NCAxMyI+CiAgPGNpcmNsZSBjeD0iNi41IiBjeT0iNi41IiByPSI2LjUiIGZpbGw9IiNGMzZCNUMiLz4KICA8Y2lyY2xlIGN4PSIyNi41IiBjeT0iNi41IiByPSI2LjUiIGZpbGw9IiNGOUJFNEQiLz4KICA8Y2lyY2xlIGN4PSI0Ny41IiBjeT0iNi41IiByPSI2LjUiIGZpbGw9IiM1NkM0NTMiLz4KPC9zdmc+Cg==);
margin-bottom: 8px;
background-repeat: no-repeat;
}
.ck-content pre.fancy-code-dark {
background: #272822;
color: #fff;
box-shadow: 5px 5px 0 #0000001f;
}
.ck-content pre.fancy-code-bright {
background: #dddfe0;
color: #000;
box-shadow: 5px 5px 0 #b3b3b3;
}
/* list style */
.ck-content ul.disc {
list-style-type: disc;
}
.ck-content ul.circle {
list-style-type: circle;
}
.ck-content ul.square {
list-style-type: square;
}
/* body style */
.ck-content p.normal-text {
font-family: '宋体';
font-size: 16px;
line-height: 1.6;
word-break: break-word;
white-space: pre-wrap;
overflow-wrap: break-word;
text-indent: 2em;
margin: 0;
padding: 0;
}

@ -92,190 +92,3 @@
transition: margin-left 0.3s ease;
margin-left: 350px;
}
/*用户定义的style插件样式*/
.ck-content h3.category {
font-family: 'Oswald';
font-size: 20px;
font-weight: bold;
color: #555;
letter-spacing: 10px;
margin: 0;
padding: 0;
}
.ck-content h2.document-title {
font-family: 'Oswald';
font-size: 50px;
font-weight: bold;
margin: 0;
padding: 0;
border: 0;
}
.ck-content h3.document-subtitle {
font-family: 'Oswald';
font-size: 20px;
color: #555;
margin: 0 0 1em;
font-weight: bold;
padding: 0;
}
.ck-content p.info-box {
--background-size: 30px;
--background-color: #e91e63;
padding: 1.2em 2em;
border: 1px solid var(--background-color);
background: linear-gradient(135deg,
var(--background-color) 0%,
var(--background-color) var(--background-size),
transparent var(--background-size)),
linear-gradient(135deg,
transparent calc(100% - var(--background-size)),
var(--background-color) calc(100% - var(--background-size)),
var(--background-color));
border-radius: 10px;
margin: 1.5em 2em;
box-shadow: 5px 5px 0 #ffe6ef;
}
.ck-content blockquote.side-quote {
font-family: 'Oswald';
font-style: normal;
float: right;
width: 35%;
position: relative;
border: 0;
overflow: visible;
z-index: 1;
margin-left: 1em;
}
.ck-content blockquote.side-quote::before {
content: '“';
position: absolute;
top: -37px;
left: -10px;
display: block;
font-size: 200px;
color: #e7e7e7;
z-index: -1;
line-height: 1;
}
.ck-content blockquote.side-quote p {
font-size: 2em;
line-height: 1;
}
.ck-content blockquote.side-quote p:last-child:not(:first-child) {
font-size: 1.3em;
text-align: right;
color: #555;
}
.ck-content span.marker {
background: yellow;
}
.ck-content span.spoiler {
background: #000;
color: #000;
}
.ck-content span.spoiler:hover {
background: #000;
color: #fff;
}
.ck-content pre.fancy-code {
border: 0;
margin-left: 2em;
margin-right: 2em;
border-radius: 10px;
}
.ck-content pre.fancy-code::before {
content: '';
display: block;
height: 13px;
background: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1NCAxMyI+CiAgPGNpcmNsZSBjeD0iNi41IiBjeT0iNi41IiByPSI2LjUiIGZpbGw9IiNGMzZCNUMiLz4KICA8Y2lyY2xlIGN4PSIyNi41IiBjeT0iNi41IiByPSI2LjUiIGZpbGw9IiNGOUJFNEQiLz4KICA8Y2lyY2xlIGN4PSI0Ny41IiBjeT0iNi41IiByPSI2LjUiIGZpbGw9IiM1NkM0NTMiLz4KPC9zdmc+Cg==);
margin-bottom: 8px;
background-repeat: no-repeat;
}
.ck-content pre.fancy-code-dark {
background: #272822;
color: #fff;
box-shadow: 5px 5px 0 #0000001f;
}
.ck-content pre.fancy-code-bright {
background: #dddfe0;
color: #000;
box-shadow: 5px 5px 0 #b3b3b3;
}
.ck-content p.gradientborder {
--borderWidth: 12px;
--bRadius: 5px;
width: 60%;
height: 60%;
position: relative;
z-index: 0;
overflow: hidden;
padding: 2rem;
z-index: 0;
border-radius: --bRadius;
&::after,
&::before {
box-sizing: border-box;
}
&::before {
content: '';
position: absolute;
left: -50%;
top: -50%;
width: 200%;
height: 200%;
z-index: -2;
background-repeat: no-repeat;
background-size: 50% 50%, 50% 50%;
background-position: 0 0, 100% 0, 100% 100%, 0 100%;
background-image: linear-gradient(#399953, #399953), linear-gradient(#fbb300, #fbb300), linear-gradient(#d53e33, #d53e33), linear-gradient(#377af5, #377af5);
animation: rotate 4s linear infinite;
@keyframes rotate {
100% {
transform: rotate(1turn);
}
}
}
&::after {
content: '';
position: absolute;
z-index: -1;
left: calc(var(--borderWidth) / 2);
top: calc(var(--borderWidth) / 2);
width: calc(100% - var(--borderWidth));
height: calc(100% - var(--borderWidth));
background: white;
border-radius: --bRadius;
/* 这一行是为了方便查看原来的样子的 */
animation: opacityChange 3s infinite alternate;
}
@keyframes opacityChange {
50% {
opacity: 1;
}
100% {
opacity: .5;
}
}
}

@ -308,6 +308,7 @@ import {
} from 'ckeditor5';
import 'ckeditor5/ckeditor5.css';
import '../public/sidebar.css';
import '../public/generatedStyle.css';
import { ElButton, ElInput, ElSelect, ElOption, ElForm, ElFormItem, ElMenu, ElMenuItem, ElColorPicker, ElSubMenu } from 'element-plus';
import emitter, { setConfig } from '../components/plugins'
// import {getUserConfigFromBackend,saveData,getPageContent,getAndApplyUserStyles} from './components/utils';

Loading…
Cancel
Save