diff --git a/GinSkeleton/api_doc.md b/GinSkeleton/api_doc.md index 1dc94ea..6d08c97 100644 --- a/GinSkeleton/api_doc.md +++ b/GinSkeleton/api_doc.md @@ -229,7 +229,7 @@ voc|form-data|string|必填|"" (示例内容在 storage/app/test/A13_221.txt 中 ---|---|---|---|--- doc|form-data|string|必填|"" background|form-data|string||"" -type|form-data|string|必填|"abstract"/"decorate"/"sequel_writing"/"rewrite_wrong"/"translate" +type|form-data|string|必填|"summary"/"decoration"/"correction"/"extension"/"translation" > 返回示例: ```json { diff --git a/GinSkeleton/app/global/consts/consts.go b/GinSkeleton/app/global/consts/consts.go index fad639d..c8e36c8 100644 --- a/GinSkeleton/app/global/consts/consts.go +++ b/GinSkeleton/app/global/consts/consts.go @@ -87,4 +87,6 @@ const ( DocRefineFailCode int = -400452 StyleGenerateFailMsg string = "样式生成失败" StyleGenerateFailCode int = -400453 + LayoutGenerateFailMsg string = "排版生成失败" + LayoutGenerateFailCode int = -400454 ) diff --git a/GinSkeleton/app/http/controller/web/ai_layout_controller.go b/GinSkeleton/app/http/controller/web/ai_layout_controller.go index 64c945a..ab3baa1 100644 --- a/GinSkeleton/app/http/controller/web/ai_layout_controller.go +++ b/GinSkeleton/app/http/controller/web/ai_layout_controller.go @@ -9,6 +9,8 @@ import ( type StyleGenerate struct { } +type LayoutGenerate struct { +} // ai生成样式 func (s *StyleGenerate) StyleGenerate(c *gin.Context) { @@ -29,3 +31,15 @@ func (s *StyleGenerate) StyleGenerate(c *gin.Context) { } } + +// ai排版 +func (l *LayoutGenerate) LayoutGenerate(c *gin.Context) { + // 流式传输 + // 设置 HTTP 头部为 SSE + c.Writer.Header().Set("Content-Type", "text/event-stream") + c.Writer.Header().Set("Cache-Control", "no-cache") + c.Writer.Header().Set("Connection", "keep-alive") + if err := ai_model_cli.RequestLayout(c); err != nil { + response.Fail(c, consts.LayoutGenerateFailCode, consts.LayoutGenerateFailMsg, err) + } +} diff --git a/GinSkeleton/app/http/validator/web/ai_doc.go/doc_refine.go b/GinSkeleton/app/http/validator/web/ai_doc.go/doc_refine.go index afc83f4..95a1aa7 100644 --- a/GinSkeleton/app/http/validator/web/ai_doc.go/doc_refine.go +++ b/GinSkeleton/app/http/validator/web/ai_doc.go/doc_refine.go @@ -16,7 +16,7 @@ type DocRefine struct { Background string `form:"background" json:"background" ` } -var Types = []string{"abstract", "decorate", "sequel_writing", "rewrite_wrong", "translate"} +var Types = []string{"summary", "decoration", "extension", "correction", "translation"} func (d DocRefine) CheckParams(context *gin.Context) { if err := context.ShouldBind(&d); err != nil { @@ -24,6 +24,7 @@ func (d DocRefine) CheckParams(context *gin.Context) { response.ValidatorError(context, err) return } + // 判断是否在类型中 t := d.Type isExit := false diff --git a/GinSkeleton/app/http/validator/web/ai_layout/layout_generate.go b/GinSkeleton/app/http/validator/web/ai_layout/layout_generate.go index 60df902..ab21c95 100644 --- a/GinSkeleton/app/http/validator/web/ai_layout/layout_generate.go +++ b/GinSkeleton/app/http/validator/web/ai_layout/layout_generate.go @@ -1,12 +1,23 @@ package ai_layout import( "github.com/gin-gonic/gin" -// "goskeleton/app/utils/response" -// "goskeleton/app/http/controller/web" -// "goskeleton/app/http/validator/core/data_transfer" -// "goskeleton/app/global/consts" + "goskeleton/app/utils/response" + "goskeleton/app/http/controller/web" + "goskeleton/app/http/validator/core/data_transfer" + "goskeleton/app/global/consts" ) type LayoutGenerate struct { + DocContent string `form:"doc_content" json:"doc_content" binging:"required"` } -func (s LayoutGenerate) CheckParams(context *gin.Context) { +func (l LayoutGenerate) CheckParams(context *gin.Context) { + if err:=context.ShouldBind(&l);err!=nil{ + response.ValidatorError(context,err) + return + } + extraAddBindDataContext := data_transfer.DataAddContext(l, consts.ValidatorPrefix, context) + if extraAddBindDataContext == nil { + response.ErrorSystem(context, "LayoutGenerate表单参数验证器json化失败", "") + return + } + (&web.LayoutGenerate{}).LayoutGenerate(extraAddBindDataContext) } \ No newline at end of file diff --git a/GinSkeleton/app/service/ai_model_cli/dor_cli.go b/GinSkeleton/app/service/ai_model_cli/dor_cli.go index e72c042..326e650 100644 --- a/GinSkeleton/app/service/ai_model_cli/dor_cli.go +++ b/GinSkeleton/app/service/ai_model_cli/dor_cli.go @@ -2,7 +2,6 @@ package ai_model_cli import ( "context" - "fmt" "goskeleton/app/global/variable" "strings" @@ -25,18 +24,17 @@ func RequestQianFan(cont *gin.Context) (r bool, c interface{}) { b := strings.TrimSpace(cont.PostForm("background")) d := strings.TrimSpace(cont.PostForm("doc")) switch t { - case "abstract": + case "summary": message = "请在这个背景下:" + b + "\n对下文进行概括,不要改变原有语言:" + d - case "decorate": + case "decoration": message = "请在这个背景下:" + b + "\n对下文进行润色,不要改变原有语言:" + d - case "sequel_writing": + case "extension": message = "请在这个背景下:" + b + "\n对下文进行续写,不要改变原有语言:" + d - case "rewrite_wrong": + case "correction": message = "请在这个背景下:" + b + "\n对下文进行改错,不要改变原有语言:" + d - case "translate": + case "translation": message = "请在这个背景下:" + b + "\n对下文进行翻译:" + d } - resp, _ := chat.Do( context.TODO(), &qianfan.ChatCompletionRequest{ @@ -45,6 +43,5 @@ func RequestQianFan(cont *gin.Context) (r bool, c interface{}) { }, }, ) - fmt.Println(resp.Result) return true, gin.H{"new_doc": resp.Result} } diff --git a/GinSkeleton/app/service/ai_model_cli/layout_cli.go b/GinSkeleton/app/service/ai_model_cli/layout_cli.go index e17a3a7..0f9c2d1 100644 --- a/GinSkeleton/app/service/ai_model_cli/layout_cli.go +++ b/GinSkeleton/app/service/ai_model_cli/layout_cli.go @@ -5,6 +5,7 @@ import ( "fmt" "goskeleton/app/global/variable" "os" + "strings" "goskeleton/app/global/consts" @@ -84,85 +85,107 @@ func RequestStyle(c *gin.Context) (interface{}, error) { } func RequestStyleStream(c *gin.Context) error { - userMsg := c.GetString(consts.ValidatorPrefix + "user_input") - - qianfan.GetConfig().AccessKey = variable.ConfigYml.GetString("BaiduCE.QianFanAccessKey") - qianfan.GetConfig().SecretKey = variable.ConfigYml.GetString("BaiduCE.QianFanSecretKey") - - chat := qianfan.NewChatCompletion( - qianfan.WithModel("ERNIE-4.0-8K"), - ) - - chatHistory := []qianfan.ChatCompletionMessage{} - - systemMsgPath := variable.ConfigYml.GetString("BaiduCE.StyleGeneratePromptPath") - prompt, err := os.ReadFile(variable.BasePath + systemMsgPath) - if err != nil || len(prompt) == 0 { - variable.ZapLog.Error(fmt.Sprintf("读取提示词文件失败: %v", err)) - return err - } - - userHistory, exist := c.Get(consts.ValidatorPrefix + "chat_history") - if exist && userHistory != nil { - historySlice, ok := userHistory.([]interface{}) - if !ok || len(historySlice)%2 != 0 { - variable.ZapLog.Error(fmt.Sprintf("用户历史对话格式错误: %v", userHistory)) - return fmt.Errorf("用户历史对话格式错误") - } - - var chatHistoryConverted []qianfan.ChatCompletionMessage - for _, item := range historySlice { - if itemMap, ok := item.(map[string]interface{}); ok { - role, roleOk := itemMap["role"].(string) - content, contentOk := itemMap["content"].(string) - if roleOk && contentOk { - chatHistoryConverted = append(chatHistoryConverted, qianfan.ChatCompletionMessage{ - Role: role, - Content: content, - }) - } else { - variable.ZapLog.Error(fmt.Sprintf("用户历史对话格式错误: %v\nrole 或 content 类型断言失败", userHistory)) - return fmt.Errorf("用户历史对话格式错误") - } - } else { - variable.ZapLog.Error(fmt.Sprintf("用户历史对话格式错误: %v\n无法将 item 转换为 map[string]interface{}", userHistory)) - return fmt.Errorf("用户历史对话格式错误") - } - } - - if len(chatHistoryConverted) > 0 && len(chatHistoryConverted)%2 == 0 { - chatHistory = append(chatHistory, chatHistoryConverted...) - } - } - - chatHistory = append(chatHistory, qianfan.ChatCompletionUserMessage(userMsg)) - - stream, err := chat.Stream(context.TODO(), &qianfan.ChatCompletionRequest{System: string(prompt), Messages: chatHistory}) - if err != nil { - variable.ZapLog.Error(fmt.Sprintf("对话失败: %v", err)) - return err - } - defer stream.Close() + userMsg := c.GetString(consts.ValidatorPrefix + "user_input") + + chatHistory := []qianfan.ChatCompletionMessage{} + + systemMsgPath := variable.ConfigYml.GetString("BaiduCE.StyleGeneratePromptPath") + prompt, err := os.ReadFile(variable.BasePath + systemMsgPath) + if err != nil || len(prompt) == 0 { + variable.ZapLog.Error(fmt.Sprintf("读取提示词文件失败: %v", err)) + return err + } + + userHistory, exist := c.Get(consts.ValidatorPrefix + "chat_history") + if exist && userHistory != nil { + historySlice, ok := userHistory.([]interface{}) + if !ok || len(historySlice)%2 != 0 { + variable.ZapLog.Error(fmt.Sprintf("用户历史对话格式错误: %v", userHistory)) + return fmt.Errorf("用户历史对话格式错误") + } + + var chatHistoryConverted []qianfan.ChatCompletionMessage + for _, item := range historySlice { + if itemMap, ok := item.(map[string]interface{}); ok { + role, roleOk := itemMap["role"].(string) + content, contentOk := itemMap["content"].(string) + if roleOk && contentOk { + chatHistoryConverted = append(chatHistoryConverted, qianfan.ChatCompletionMessage{ + Role: role, + Content: content, + }) + } else { + variable.ZapLog.Error(fmt.Sprintf("用户历史对话格式错误: %v\nrole 或 content 类型断言失败", userHistory)) + return fmt.Errorf("用户历史对话格式错误") + } + } else { + variable.ZapLog.Error(fmt.Sprintf("用户历史对话格式错误: %v\n无法将 item 转换为 map[string]interface{}", userHistory)) + return fmt.Errorf("用户历史对话格式错误") + } + } + + if len(chatHistoryConverted) > 0 && len(chatHistoryConverted)%2 == 0 { + chatHistory = append(chatHistory, chatHistoryConverted...) + } + } + + chatHistory = append(chatHistory, qianfan.ChatCompletionUserMessage(userMsg)) + return ChatByStream(c, string(prompt), chatHistory) +} + +func RequestLayout(c *gin.Context) error { + doc_content := c.GetString(consts.ValidatorPrefix + "doc_content") + + chatHistory := []qianfan.ChatCompletionMessage{} + + systemMsgPath := variable.ConfigYml.GetString("BaiduCE.LayoutGeneratePromptPath") + prompt, err := os.ReadFile(variable.BasePath + systemMsgPath) + if err != nil || len(prompt) == 0 { + variable.ZapLog.Error(fmt.Sprintf("读取提示词文件失败: %v", err)) + return err + } + + chatHistory = append(chatHistory, qianfan.ChatCompletionUserMessage("待排版内容\n"+doc_content)) + return ChatByStream(c, string(prompt), chatHistory) +} + +func ChatByStream(c *gin.Context, prompt string, chatHistory []qianfan.ChatCompletionMessage) error{ + + qianfan.GetConfig().AccessKey = variable.ConfigYml.GetString("BaiduCE.QianFanAccessKey") + qianfan.GetConfig().SecretKey = variable.ConfigYml.GetString("BaiduCE.QianFanSecretKey") + + chat := qianfan.NewChatCompletion( + qianfan.WithModel("ERNIE-4.0-8K"), + ) + + stream, err := chat.Stream(context.TODO(), &qianfan.ChatCompletionRequest{System: string(prompt), Messages: chatHistory}) + if err != nil { + variable.ZapLog.Error(fmt.Sprintf("对话失败: %v", err)) + return err + } + defer stream.Close() c.Writer.Flush() defer c.Writer.Flush() - for { - response, err := stream.Recv() - if response.IsEnd { - break // 流结束,退出循环 - } - if err != nil { - variable.ZapLog.Error(fmt.Sprintf("接收流失败: %v", err)) - return err - } + outputMsg:=strings.Builder{} + for { + response, err := stream.Recv() + if response.IsEnd { + break // 流结束,退出循环 + } + if err != nil { + variable.ZapLog.Error(fmt.Sprintf("接收流失败: %v", err)) + return err + } // 将结果写入到响应体 - if _,err:=fmt.Fprintf(c.Writer,"%s",response.Result);err!=nil{ + outputMsg.WriteString(response.Result) + if _, err := fmt.Fprintf(c.Writer, "%s", response.Result); err != nil { variable.ZapLog.Error(fmt.Sprintf("写入流失败: %v", err)) return err } // 立即刷新缓冲区,以确保数据立即发送到客户端 c.Writer.Flush() - } - return nil // 正常结束,返回 nil + } + return nil // 正常结束,返回 nil } \ No newline at end of file diff --git a/GinSkeleton/config/config.yml b/GinSkeleton/config/config.yml index e1b6352..ac86283 100644 --- a/GinSkeleton/config/config.yml +++ b/GinSkeleton/config/config.yml @@ -154,3 +154,4 @@ BaiduCE: # QianFanSecretKey: "1edf17c358574e75b9913ebff7d95b61" # 访问千帆sdk 时用的 SecretKey QianFanSecretKey: "cb812e1b6e56420ea858d160e1351869" StyleGeneratePromptPath: "/storage/app/prompt/style_generate.prompt" # 生成样式的提示词保存路径 + LayoutGeneratePromptPath: "/storage/app/prompt/layout_generate.prompt" # 生成布局的提示词保存路径 diff --git a/GinSkeleton/storage/app/prompt/layout_generate.prompt b/GinSkeleton/storage/app/prompt/layout_generate.prompt new file mode 100644 index 0000000..f084a58 --- /dev/null +++ b/GinSkeleton/storage/app/prompt/layout_generate.prompt @@ -0,0 +1,4 @@ +请将以下文章进行重新排版,使其格式更加美观且井然有序,同时保留文章的所有原始内容和文字,不要对内容进行任何修改。 +请你调整标题样式,将所有标题改为清晰的分级结构。文章标题使用#,一级标题使用##,以此类推。 +正文部分的段落需适当分段,避免过长或过短。 请确保整体排版整洁、易于阅读。 +不要对文章的原始内容进行修改。只返回重新排版后的内容,不要返回其他的额外内容。 \ No newline at end of file diff --git a/coeditor_frontend/package-lock.json b/coeditor_frontend/package-lock.json index ea3c977..4fdd72b 100644 --- a/coeditor_frontend/package-lock.json +++ b/coeditor_frontend/package-lock.json @@ -19,7 +19,8 @@ "html-docx-js-typescript": "^0.1.5", "jquery": "^3.7.1", "jwt-decode": "^4.0.0", - "vue": "^3.5.13", + "mitt": "^3.0.1", + "vue": "^3.2.13", "vue-router": "^4.0.3", "vuex": "^4.0.0" }, @@ -8686,6 +8687,11 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmmirror.com/mkdirp/-/mkdirp-0.5.6.tgz", diff --git a/coeditor_frontend/package.json b/coeditor_frontend/package.json index 7cbfef0..224f16d 100644 --- a/coeditor_frontend/package.json +++ b/coeditor_frontend/package.json @@ -19,6 +19,7 @@ "html-docx-js-typescript": "^0.1.5", "jquery": "^3.7.1", "jwt-decode": "^4.0.0", + "mitt": "^3.0.1", "vue": "^3.5.13", "vue-router": "^4.0.3", "vuex": "^4.0.0" diff --git a/coeditor_frontend/src/components/plugins.js b/coeditor_frontend/src/components/plugins.js index 1c6cfb6..863970f 100644 --- a/coeditor_frontend/src/components/plugins.js +++ b/coeditor_frontend/src/components/plugins.js @@ -83,9 +83,10 @@ import { getPageContent, getUserConfigFromBackend, saveData, - // markdown2html + // markdown2html, + html2markdown } from './utils'; - +import mitt from 'mitt'; // 导出为docx插件 function exportWord(){ const pageContent = getPageContent(); @@ -198,9 +199,56 @@ class Export2PDF extends Plugin { } } -// 智能润色插件 -class Translation extends Plugin { - init() { + +// 智能润色发送消息 +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); + }); + +} + + +class RefineDoc extends Plugin { + init() { // console.log('Translation initialized!'); this.editor.ui.componentFactory.add('translate', (locale) => { @@ -254,19 +302,14 @@ class Translation extends Plugin { addListToDropdown(dropdownView, items); dropdownView.on('execute', (eventInfo) => { - const { id, label } = eventInfo.source; - // 获取选中的文本,用来进行后续操作 - const selectionText = window.getSelection().toString(); - if (id === 'summary') { - // this.editor.execute('ExportToWord'); - console.log('Object (en):', label, selectionText); - } - }); + const id = eventInfo.source.id; + sendDORMsg(id, ''); + }); - return dropdownView; - }); - } -} + return dropdownView; + }); + } + } // 侧边栏按钮 class ToggleSideBar extends Plugin { @@ -340,7 +383,58 @@ class SaveButton extends Plugin { }); } } +// AI 自动排版 +function aiformat(){ + console.log("ai formatting") + const editor = window.editor; + const doc_content = editor.getData() + console.log(doc_content); + // TODO 处理html文件 + // step 1 - split images and insert text tag + const markdown_content = html2markdown(doc_content) + console.log(markdown_content) + + // TODO 请求大模型 + // step 2 - convert markdown response to html text and setData + // step 3 - recover original images + // step 4 - fetch users config + // step 5 - apply title styles of user + // step 6 - apply others styles of user +} + +class AiFormat extends Plugin { + init() { + const editor = this.editor; + + editor.ui.componentFactory.add('AiFormat', () => { + // The button will be an instance of ButtonView. + const button = new ButtonView(); + button.set({ + label: '自动排版', + // withText: true + tooltip: true, + // 图标 直接插入svg文件 + icon: '', + keystroke: 'Shift+F' + + }); + + // Execute a callback function when the button is clicked + button.on('execute', () => { + aiformat(); + }); + + return button; + }); + + // 添加快捷键 Shift+F 保存 + editor.keystrokes.set('Shift+F', (event, cancel) => { + aiformat(); + cancel(); + }); + } +} // 配置CKEditor5 function setConfig() { // 获取用户的样式配置 @@ -375,7 +469,7 @@ function setConfig() { 'numberedList', 'outdent', 'indent', - '|', 'ExportToWord', 'ExportToPDF', 'translate', 'SideBar', 'SaveButton' + '|', 'ExportToWord', 'ExportToPDF', 'translate', 'SideBar', 'SaveButton','AiFormat' ], shouldNotGroupWhenFull: true }, @@ -449,9 +543,9 @@ function setConfig() { TodoList, Underline, Undo, - Export2Word, Translation, Export2PDF, ToggleSideBar, SaveButton + Export2Word, RefineDoc, Export2PDF, ToggleSideBar, SaveButton, AiFormat ], - balloonToolbar: ['bold', 'italic', '|', 'link', 'insertImage', '|', 'bulletedList', 'numberedList'], + balloonToolbar: ['bold', 'italic', '|', 'link', 'insertImage', '|', 'bulletedList', 'numberedList','|','AiFormat'], //自定义设置字体 fontFamily: { // 自定义字体 @@ -579,11 +673,12 @@ function setConfig() { autosave: { waitingTime: 180000, // (in ms) 3minutes save() { - // TODO save return saveData(getPageContent()); } }, translations: [translations] } } -export { Export2Word, Export2PDF, Translation, ToggleSideBar, setConfig }; \ No newline at end of file +const emitter = new mitt(); +export default emitter; +export {Export2Word, Export2PDF, RefineDoc, ToggleSideBar, setConfig}; diff --git a/coeditor_frontend/src/components/utils.js b/coeditor_frontend/src/components/utils.js index bcd66f2..7137295 100644 --- a/coeditor_frontend/src/components/utils.js +++ b/coeditor_frontend/src/components/utils.js @@ -1,11 +1,11 @@ // utils.js import { MarkdownToHtml } from '@ckeditor/ckeditor5-markdown-gfm/src/markdown2html/markdown2html.js'; +import { HtmlToMarkdown } from '@ckeditor/ckeditor5-markdown-gfm/src/html2markdown/html2markdown.js'; // 获取用户配置 export function getUserConfigFromBackend() { // TODO 请求用户配置 const options = {}; // 字体、字号、样式 - // TODO const { fontFamilyOptions = [ 'default', @@ -92,7 +92,7 @@ export function getUserConfigFromBackend() { }; } -// 实现自动保存saveData方法,将编辑内容发送至后端 +// TODO 实现自动保存saveData方法,将编辑内容发送至后端 export function saveData(data) { // return new Promise( resolve => { // setTimeout( () => { @@ -128,7 +128,7 @@ export function getPageContent() { // return pageContent.outerHTML; } -// 获取并应用用户定义的样式 +// TODO 获取并应用用户定义的样式css export function getAndApplyUserStyles() { // 模拟从后端获取用户定义的样式 const response = fetch('/api/user-styles'); @@ -140,10 +140,18 @@ export function getAndApplyUserStyles() { } // markdown转html 便于将大语言模型的输出(一般为markdown格式)转换为ckeditor的html格式 -// 利用ckeditor markdown插件,但不能在CkeditorView.vue中使用 +// 利用ckeditor markdown插件的功能子类,但不能在CkeditorView.vue中直接使用markdown插件 // 否则会改变编辑器数据处理器为markdown,即getData()需要传入markdown string,setData()返回markdown string export function markdown2html(markdownString){ const markdownToHtml = new MarkdownToHtml(); const htmlString = markdownToHtml.parse(markdownString); return htmlString; +} + +// html转markdown 便于将文件内容发送给大语言模型来进行排版 +// 再利用 @markdown2html 将大语言模型的返回内容重新转换为文件内容并展示 或做进一步处理 +export function html2markdown(htmlString){ + const htmltomarkdown = new HtmlToMarkdown(); + const markdownString = htmltomarkdown.parse(htmlString); + return markdownString } \ No newline at end of file diff --git a/coeditor_frontend/src/main.js b/coeditor_frontend/src/main.js index 5546580..e8bf438 100644 --- a/coeditor_frontend/src/main.js +++ b/coeditor_frontend/src/main.js @@ -7,6 +7,8 @@ import { CkeditorPlugin } from '@ckeditor/ckeditor5-vue'; const app = createApp(App); +window.store = store; + app.use(router); app.use(store); app.use(CkeditorPlugin); diff --git a/coeditor_frontend/src/public/sidebar.css b/coeditor_frontend/src/public/sidebar.css index 9bf8f56..f1e2135 100644 --- a/coeditor_frontend/src/public/sidebar.css +++ b/coeditor_frontend/src/public/sidebar.css @@ -124,4 +124,16 @@ padding: 10px; border-radius: 4px; background-color: #ecf0f1; +} +.message-answer { + margin: 5px 0; + padding: 10px; + border-radius: 4px; + background-color: rgb(189, 195, 199); +} +.message-answer pre { + white-space: pre-wrap; + /* 保留空白符,但允许自动换行 */ + word-wrap: break-word; + /* 允许长单词换行 */ } \ No newline at end of file diff --git a/coeditor_frontend/src/router/index.js b/coeditor_frontend/src/router/index.js index 464c2d6..ca52dc4 100644 --- a/coeditor_frontend/src/router/index.js +++ b/coeditor_frontend/src/router/index.js @@ -40,6 +40,12 @@ const routes = [ { path: '/:catchAll(.*)', redirect: '/404/', + }, + { + path: '/web_api/', + target:'http://localhost:14514/', + changeOrigin:true, //修改源 + rewrite:{'^/web_api/':''} } ] diff --git a/coeditor_frontend/src/store/index.js b/coeditor_frontend/src/store/index.js index 49ed985..cd0c08e 100644 --- a/coeditor_frontend/src/store/index.js +++ b/coeditor_frontend/src/store/index.js @@ -1,5 +1,6 @@ import { createStore } from 'vuex' import ModulerUser from './user'; +import ModulerRefineDoc from './refine_doc'; export default createStore({ state: { @@ -12,5 +13,6 @@ export default createStore({ }, modules: { user: ModulerUser, + refine_doc: ModulerRefineDoc } }) diff --git a/coeditor_frontend/src/store/refine_doc.js b/coeditor_frontend/src/store/refine_doc.js new file mode 100644 index 0000000..e84d31c --- /dev/null +++ b/coeditor_frontend/src/store/refine_doc.js @@ -0,0 +1,54 @@ + +const ModulerRefineDoc = { + state: { + current_tag: '', + contents: new Map() + }, + mutations: { + setCurrentTag(state, newTag) { + console.log("storetag", newTag); + state.current_tag = newTag; + }, + // 增加tag下的value数组里的内容的mutation方法 + addContentToTag(state, {tag, newContent}) { + console.log("tag, newContent", {tag, newContent}); + if (!state.contents.has(tag)) { + state.contents.set(tag, []); + } + state.contents.get(tag).push(newContent); + return state.contents.get(tag) - 1; + }, + // 修改tag下的value数组里的内容的mutation方法 + changeContentForTag(state, {tag, index, newContent}) { + if (!state.contents.has(tag)) { + state.contents.set(tag, []); + } + var tagContents = state.contents.get(tag); + console.log("oldtagc", tagContents, tagContents[index]); + if (tagContents[index]){ + state.contents.get(tag).splice(index,1,newContent); + } + }, + // 清空tag下的数组内容的mutation方法 + clearContentsForTag(state, tag) { + if (state.contents.has(tag)) { + state.contents.set(tag, []); + } + } + }, + getters: { + getCurrentTag(state) { + return state.current_tag; + }, + getCurrentContent(state) { + return state.contents.get(state.current_tag); + }, + getCurrentindex(state) { + return state.contents.get(state.current_tag).length - 1; + } + }, + modules: { + } +} + +export default ModulerRefineDoc; \ No newline at end of file diff --git a/coeditor_frontend/src/views/CkeditorView.vue b/coeditor_frontend/src/views/CkeditorView.vue index 77604c6..1336df2 100644 --- a/coeditor_frontend/src/views/CkeditorView.vue +++ b/coeditor_frontend/src/views/CkeditorView.vue @@ -16,16 +16,16 @@ -