@ -83,12 +83,13 @@ import {
getPageContent ,
getUserConfigFromBackend ,
saveData ,
// markdown2html,
html2markdown
markdown2html ,
html2markdown ,
getUserAILayoutConfig
} from './utils' ;
import mitt from 'mitt' ;
// 导出为docx插件
function exportWord ( ) {
function exportWord ( ) {
const pageContent = getPageContent ( ) ;
const style = getStyle ( ) ;
const page = '<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><style>@media print{@page{size:A4 portrait;margin:0cm 3cm}@page:left{margin-left:2.5cm;margin-right:2.7cm;}@page:right{margin-left:2.7cm;margin-right:2.5cm;}}</style>' + style + '</head><body>' + pageContent + '</body></html>'
@ -120,7 +121,7 @@ class Export2Word extends Plugin {
button . on ( 'execute' , ( ) => {
exportWord ( ) ;
} ) ;
// 添加快捷键 Ctrl+W 导出为docx
editor . keystrokes . set ( 'Ctrl+W' , ( event , cancel ) => {
exportWord ( ) ;
@ -202,53 +203,53 @@ class Export2PDF extends Plugin {
// 智能润色发送消息
function sendDORMsg ( type , fullText ) {
// 获取选中的文本,用来进行后续操作
const selectionText = window . getSelection ( ) . toString ( ) ;
if ( selectionText . trim ( ) === '' ) return ;
const formData = new FormData ( ) ;
formData . append ( 'doc' , selectionText ) ;
formData . append ( 'background' , fullText ) ;
formData . append ( 'type' , type ) ;
const requestOptions = {
method : 'POST' ,
body : formData
} ;
console . log ( "formData:" , formData ) ;
const store = window . store ;
const res = { oldContent : selectionText , newContent : '...' } ;
store . commit ( 'setCurrentTag' , type ) ;
store . commit ( 'addContentToTag' , { tag : type , newContent : res } ) ;
const index = store . getters . getCurrentindex ;
console . log ( "index" , index ) ;
emitter . emit ( 'show-refine-doc-sidebar' , type ) ;
fetch ( "/web_api/admin/ai_doc/doc_refine" , requestOptions )
. then ( response => {
if ( ! response . ok ) {
throw new Error ( "请求出错" ) ;
}
// 根据返回的数据格式进行相应处理, 这里假设返回的数据是JSON格式, 所以使用response.json()解析
return response . json ( ) ;
} )
. then ( data => {
// console.log("useStore", store);
const newRes = { oldContent : selectionText , newContent : data . data . new _doc } ;
store . commit ( 'changeContentForTag' , { tag : type , index : index , newContent : newRes } ) ;
emitter . emit ( 'show-refine-doc-sidebar' , type ) ;
} )
. catch ( error => {
console . error ( "POST请求出错: " , error ) ;
} ) ;
// 获取选中的文本,用来进行后续操作
const selectionText = window . getSelection ( ) . toString ( ) ;
if ( selectionText . trim ( ) === '' ) return ;
const formData = new FormData ( ) ;
formData . append ( 'doc' , selectionText ) ;
formData . append ( 'background' , fullText ) ;
formData . append ( 'type' , type ) ;
const requestOptions = {
method : 'POST' ,
body : formData
} ;
console . log ( "formData:" , formData ) ;
const store = window . store ;
const res = { oldContent : selectionText , newContent : '...' } ;
store . commit ( 'setCurrentTag' , type ) ;
store . commit ( 'addContentToTag' , { tag : type , newContent : res } ) ;
const index = store . getters . getCurrentindex ;
console . log ( "index" , index ) ;
emitter . emit ( 'show-refine-doc-sidebar' , type ) ;
fetch ( "/web_api/admin/ai_doc/doc_refine" , requestOptions )
. then ( response => {
if ( ! response . ok ) {
throw new Error ( "请求出错" ) ;
}
// 根据返回的数据格式进行相应处理, 这里假设返回的数据是JSON格式, 所以使用response.json()解析
return response . json ( ) ;
} )
. then ( data => {
// console.log("useStore", store);
const newRes = { oldContent : selectionText , newContent : data . data . new _doc } ;
store . commit ( 'changeContentForTag' , { tag : type , index : index , newContent : newRes } ) ;
emitter . emit ( 'show-refine-doc-sidebar' , type ) ;
} )
. catch ( error => {
console . error ( "POST请求出错: " , error ) ;
} ) ;
}
class RefineDoc extends Plugin {
init ( ) {
init ( ) {
// console.log('Translation initialized!');
this . editor . ui . componentFactory . add ( 'RefineDoc' , ( locale ) => {
@ -302,14 +303,14 @@ class RefineDoc extends Plugin {
addListToDropdown ( dropdownView , items ) ;
dropdownView . on ( 'execute' , ( eventInfo ) => {
const id = eventInfo . source . id ;
sendDORMsg ( id , '' ) ;
} ) ;
const id = eventInfo . source . id ;
sendDORMsg ( id , '' ) ;
} ) ;
return dropdownView ;
} ) ;
}
}
return dropdownView ;
} ) ;
}
}
// 侧边栏按钮
class ToggleSideBar extends Plugin {
@ -384,22 +385,162 @@ class SaveButton extends Plugin {
}
}
// AI 自动排版
function aiformat ( ) {
async function aiformat ( ) {
console . log ( "ai formatting" )
const editor = window . editor ;
cons t doc _content = editor . getData ( )
le t 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 {
@ -438,66 +579,66 @@ class AiFormat extends Plugin {
// 智能识别发送消息
function sendRecMsg ( type ) {
const selection = window . getSelection ( ) ;
const formData = new FormData ( ) ;
var src = '' ;
if ( type === 'pic_recognition' ) {
const range = selection . getRangeAt ( 0 ) ;
const imageElements = range . commonAncestorContainer . parentNode . querySelectorAll ( 'img' ) ;
if ( imageElements . length > 0 ) {
const selectedImage = imageElements [ 0 ] ;
src = selectedImage . getAttribute ( 'src' ) ;
const prefix = "data:image/png;base64," ;
if ( src . startsWith ( prefix ) ) {
src = src . substring ( prefix . length ) ;
}
}
if ( src . trim ( ) === '' ) return ;
formData . append ( 'pic' , src ) ;
}
else if ( type === 'voc_recognition' ) {
return ;
}
else {
return ;
}
const requestOptions = {
method : 'POST' ,
body : formData
} ;
const store = window . store ;
const res = { oldContent : '' , newContent : '...' } ;
store . commit ( 'setCurrentTag' , type ) ;
store . commit ( 'addContentToTag' , { tag : type , newContent : res } ) ;
const index = store . getters . getCurrentindex ;
emitter . emit ( 'show-ai-recg-sidebar' , type ) ;
fetch ( '/web_api/admin/ai_recognition/' + type , requestOptions )
. then ( response => {
if ( ! response . ok ) {
throw new Error ( "请求出错" ) ;
}
// 根据返回的数据格式进行相应处理, 这里假设返回的数据是JSON格式, 所以使用response.json()解析
return response . json ( ) ;
} )
. then ( data => {
// console.log("useStore", store);
const newRes = { oldContent : '' , newContent : data . data . words } ;
store . commit ( 'changeContentForTag' , { tag : type , index : index , newContent : newRes } ) ;
emitter . emit ( 'show-ai-recg-sidebar' , type ) ;
} )
. catch ( error => {
console . error ( "POST请求出错: " , error ) ;
} ) ;
const selection = window . getSelection ( ) ;
const formData = new FormData ( ) ;
var src = '' ;
if ( type === 'pic_recognition' ) {
const range = selection . getRangeAt ( 0 ) ;
const imageElements = range . commonAncestorContainer . parentNode . querySelectorAll ( 'img' ) ;
if ( imageElements . length > 0 ) {
const selectedImage = imageElements [ 0 ] ;
src = selectedImage . getAttribute ( 'src' ) ;
const prefix = "data:image/png;base64," ;
if ( src . startsWith ( prefix ) ) {
src = src . substring ( prefix . length ) ;
}
}
if ( src . trim ( ) === '' ) return ;
formData . append ( 'pic' , src ) ;
}
else if ( type === 'voc_recognition' ) {
return ;
}
else {
return ;
}
const requestOptions = {
method : 'POST' ,
body : formData
} ;
const store = window . store ;
const res = { oldContent : '' , newContent : '...' } ;
store . commit ( 'setCurrentTag' , type ) ;
store . commit ( 'addContentToTag' , { tag : type , newContent : res } ) ;
const index = store . getters . getCurrentindex ;
emitter . emit ( 'show-ai-recg-sidebar' , type ) ;
fetch ( '/web_api/admin/ai_recognition/' + type , requestOptions )
. then ( response => {
if ( ! response . ok ) {
throw new Error ( "请求出错" ) ;
}
// 根据返回的数据格式进行相应处理, 这里假设返回的数据是JSON格式, 所以使用response.json()解析
return response . json ( ) ;
} )
. then ( data => {
// console.log("useStore", store);
const newRes = { oldContent : '' , newContent : data . data . words } ;
store . commit ( 'changeContentForTag' , { tag : type , index : index , newContent : newRes } ) ;
emitter . emit ( 'show-ai-recg-sidebar' , type ) ;
} )
. catch ( error => {
console . error ( "POST请求出错: " , error ) ;
} ) ;
}
class PicRecog extends Plugin {
init ( ) {
init ( ) {
// console.log('Translation initialized!');
this . editor . ui . componentFactory . add ( 'PicRecog' , ( ) => {
@ -516,8 +657,8 @@ class PicRecog extends Plugin {
return button ;
} ) ;
}
}
}
}
// 配置CKEditor5
@ -554,7 +695,7 @@ function setConfig() {
'numberedList' ,
'outdent' ,
'indent' ,
'|' , 'ExportToWord' , 'ExportToPDF' , 'RefineDoc' , 'SideBar' , 'SaveButton' , 'AiFormat'
'|' , 'ExportToWord' , 'ExportToPDF' , 'RefineDoc' , 'SideBar' , 'SaveButton' , 'AiFormat'
] ,
shouldNotGroupWhenFull : true
} ,
@ -630,7 +771,7 @@ function setConfig() {
Undo ,
Export2Word , RefineDoc , Export2PDF , ToggleSideBar , SaveButton , AiFormat , PicRecog
] ,
balloonToolbar : [ 'bold' , 'italic' , '|' , 'link' , 'insertImage' , '|' , 'bulletedList' , 'numberedList' , '|' , 'AiFormat' ] ,
balloonToolbar : [ 'bold' , 'italic' , '|' , 'link' , 'insertImage' , '|' , 'bulletedList' , 'numberedList' , '|' , 'AiFormat' ] ,
//自定义设置字体
fontFamily : {
// 自定义字体
@ -768,4 +909,4 @@ function setConfig() {
}
const emitter = new mitt ( ) ;
export default emitter ;
export { Export2Word , Export2PDF , RefineDoc , ToggleSideBar , PicRecog , setConfig } ;
export { Export2Word , Export2PDF , RefineDoc , ToggleSideBar , PicRecog , setConfig } ;