diff --git a/packages/word/src/editor/core/range/RangeManager.ts b/packages/word/src/editor/core/range/RangeManager.ts index 1568166..5bb18fd 100644 --- a/packages/word/src/editor/core/range/RangeManager.ts +++ b/packages/word/src/editor/core/range/RangeManager.ts @@ -433,6 +433,7 @@ export class RangeManager { el => !el.underline && !el.control?.underline ) const strikeout = !~curElementList.findIndex(el => !el.strikeout) + const aiEdit = !~curElementList.findIndex(el => !el.aiEdit) const color = curElement.color || null const highlight = curElement.highlight || null const rowFlex = curElement.rowFlex || null @@ -461,6 +462,7 @@ export class RangeManager { italic, underline, strikeout, + aiEdit, color, highlight, rowFlex, @@ -503,6 +505,7 @@ export class RangeManager { italic: false, underline: false, strikeout: false, + aiEdit: false, color: null, highlight: null, rowFlex: null, diff --git a/packages/word/src/editor/interface/Element.ts b/packages/word/src/editor/interface/Element.ts index 734f7b1..b758b0b 100644 --- a/packages/word/src/editor/interface/Element.ts +++ b/packages/word/src/editor/interface/Element.ts @@ -33,6 +33,7 @@ export interface IElementStyle { italic?: boolean underline?: boolean strikeout?: boolean + aiEdit?: boolean rowFlex?: RowFlex rowMargin?: number letterSpacing?: number diff --git a/packages/word/src/editor/interface/Listener.ts b/packages/word/src/editor/interface/Listener.ts index b31d435..18e4276 100644 --- a/packages/word/src/editor/interface/Listener.ts +++ b/packages/word/src/editor/interface/Listener.ts @@ -23,6 +23,7 @@ export interface IRangeStyle { italic: boolean underline: boolean strikeout: boolean + aiEdit: boolean color: string | null highlight: string | null rowFlex: RowFlex | null diff --git a/packages/word/src/plugins/floatingToolbar/enum/index.ts b/packages/word/src/plugins/floatingToolbar/enum/index.ts index 8205921..c6ae04e 100644 --- a/packages/word/src/plugins/floatingToolbar/enum/index.ts +++ b/packages/word/src/plugins/floatingToolbar/enum/index.ts @@ -6,5 +6,6 @@ export enum ToolbarType { UNDERLINE = 'underline', STRIKEOUT = 'strikeout', COLOR = 'color', - HIGHLIGHT = 'highlight' + HIGHLIGHT = 'highlight', + AI_EDIT = 'ai-edit' } diff --git a/packages/word/src/plugins/floatingToolbar/icons/Svgs.ts b/packages/word/src/plugins/floatingToolbar/icons/Svgs.ts new file mode 100644 index 0000000..ba209c5 --- /dev/null +++ b/packages/word/src/plugins/floatingToolbar/icons/Svgs.ts @@ -0,0 +1,15 @@ +export const Svgs = { + tips: ``, + check: ``, + refresh: ``, + aiPanelStart: ``, + addContent: ``, + replace: ` `, + cancle: ``, + optimize: ``, + checkGrammar: ``, + simplification: ``, + richContent: ``, + translation: ``, + summary: `` +} \ No newline at end of file diff --git a/packages/word/src/plugins/floatingToolbar/icons/ai-edit.svg b/packages/word/src/plugins/floatingToolbar/icons/ai-edit.svg new file mode 100644 index 0000000..d3e202b --- /dev/null +++ b/packages/word/src/plugins/floatingToolbar/icons/ai-edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/word/src/plugins/floatingToolbar/index.ts b/packages/word/src/plugins/floatingToolbar/index.ts index 9b3f429..06d1d6f 100644 --- a/packages/word/src/plugins/floatingToolbar/index.ts +++ b/packages/word/src/plugins/floatingToolbar/index.ts @@ -5,6 +5,7 @@ import './style/index.scss' import { ToolbarType } from './enum' import { IToolbarRegister } from './interface' import { PLUGIN_PREFIX } from './constant' +import { Svgs } from './icons/Svgs' function createPickerToolbar( container: HTMLDivElement, @@ -60,9 +61,110 @@ function createPickerToolbar( changed(color) }) } +export const defaultAiPanelMenus = [ + { + prompt: `{content}\n请帮我优化一下这段内容,并直接返回优化后的结果。\n注意:你应该先判断一下这句话是中文还是英文,如果是中文,请给我返回中文的内容,如果是英文,请给我返回英文内容,只需要返回内容即可,不需要告知我是中文还是英文。`, + icon: Svgs.optimize, + title: '改进写作' + }, + { + prompt: `{content}\n请帮我检查一下这段内容,是否有拼写错误或者语法上的错误。\n注意:你应该先判断一下这句话是中文还是英文,如果是中文,请给我返回中文的内容,如果是英文,请给我返回英文内容,只需要返回内容即可,不需要告知我是中文还是英文。`, + icon: Svgs.checkGrammar, + title: '检查拼写和语法' + }, + { + prompt: `{content}\n这句话的内容较长,帮我简化一下这个内容,并直接返回简化后的内容结果。\n注意:你应该先判断一下这句话是中文还是英文,如果是中文,请给我返回中文的内容,如果是英文,请给我返回英文内容,只需要返回内容即可,不需要告知我是中文还是英文。`, + icon: Svgs.simplification, + title: '简化内容' + }, + { + prompt: `{content}\n这句话的内容较简短,帮我简单的优化和丰富一下内容,并直接返回优化后的结果。注意:优化的内容不能超过原来内容的 2 倍。\n注意:你应该先判断一下这句话是中文还是英文,如果是中文,请给我返回中文的内容,如果是英文,请给我返回英文内容,只需要返回内容即可,不需要告知我是中文还是英文。`, + icon: Svgs.richContent, + title: '丰富内容' + }, + '
', + { + prompt: `{content}\n请帮我翻译以上内容,在翻译之前,想先判断一下这个内容是不是中文,如果是中文,则翻译问英文,如果是其他语言,则需要翻译为中文,注意,你只需要返回翻译的结果,不需要对此进行任何解释,不需要除了翻译结果以外的其他任何内容。`, + icon: Svgs.translation, + title: '翻译' + }, + { + prompt: `{content}\n请帮我总结以上内容,并直接返回总结的结果\n注意:你应该先判断一下这句话是中文还是英文,如果是中文,请给我返回中文的内容,如果是英文,请给我返回英文内容,只需要返回内容即可,不需要告知我是中文还是英文。`, + icon: Svgs.summary, + title: '总结' + } +] +// 创建ai选择栏 +function createAIToolbar( + container: HTMLDivElement, + toolbarType: ToolbarType, + changed: (color: string) => void +) { + const toolbarItem = document.createElement('div') + toolbarItem.classList.add(`${PLUGIN_PREFIX}-picker-ai`) + toolbarItem.classList.add(`${PLUGIN_PREFIX}-${toolbarType}`) + const chooseBox = document.createElement('div') + chooseBox.classList.add('ce-picker-container') + toolbarItem.append(chooseBox) + const aiBubbleMenuItems = defaultAiPanelMenus + toolbarItem.innerHTML = ` + +
+
+ +
+
+
+ ${Svgs.tips} + 提示:您可以在上面输入文字或者选择下方的操作 +
+
+ + + + +
+ ` + container.append(toolbarItem) +} // 工具栏列表 const toolbarRegisterList: IToolbarRegister[] = [ + { + // key: ToolbarType.AI_EDIT, + // callback(editor) { + // console.log('editor:', editor) + // } + render(container, editor) { + createAIToolbar(container, ToolbarType.AI_EDIT, color => { + editor.command.executeColor(color) + }) + } + }, + { + isDivider: true + }, { key: ToolbarType.SIZE_ADD, callback(editor) { @@ -178,6 +280,12 @@ export default function floatingToolbarPlugin(editor: Editor) { toolbarContainer.style.left = `${position.x}px` toolbarContainer.style.top = `${position.y + position.height}px` // 样式回显 + const aiDom = toolbarContainer.querySelector( + `.${PLUGIN_PREFIX}-ai-edit` + ) + if (aiDom) { + toggleToolbarItemActive(aiDom, rangeStyle.aiEdit) + } const boldDom = toolbarContainer.querySelector( `.${PLUGIN_PREFIX}-bold` ) diff --git a/packages/word/src/plugins/floatingToolbar/style/index.scss b/packages/word/src/plugins/floatingToolbar/style/index.scss index 5c1184f..b1c524b 100644 --- a/packages/word/src/plugins/floatingToolbar/style/index.scss +++ b/packages/word/src/plugins/floatingToolbar/style/index.scss @@ -44,6 +44,10 @@ background-color: #cfd2d8; } + &.ce-ai-edit i { + background: url(../icons/ai-edit.svg); + } + &.ce-size-add i { background: url(../icons/size-add.svg); } @@ -97,4 +101,200 @@ background: url(../icons/highlight.svg); } } +} +.aie-container { + position: absolute; + top: 40px; + left: 0; + height: 100%; + + *, ::before, ::after { + box-sizing: content-box; + } + .aie-ai-panel-body { + width: 350px; + border: solid 1px; + background: #ffffff; + border-radius: 5px; + border-color: #eee; + box-shadow: 0 0 7px 0 rgba(0, 0, 0, .15); + display: flex; + flex-direction: column; + overflow: hidden; + + &-tips { + width: 100%; + padding: 10px; + color: #999; + font-size: 12px; + background-color: #fafafa; + box-sizing: border-box; + display: flex; + align-items: center; + + svg { + width: 15px; + height: 15px; + margin-right: 5px; + } + } + + &-content { + width: 100%; + // height: 160px; + position: relative; + + .loader { + position: absolute; + top: 10px; + right: 10px; + display: none; + + svg { + width: 16px; + color: #999; + animation: rotate 2s linear infinite; + @keyframes rotate { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + } + } + + textarea { + width: 100%; + height: 100%; + max-height: 300px; + padding: 15px; + box-sizing: border-box; + font-size: 12px; + line-height: 1.6em; + font-family: Arial, 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; + border: none; + resize: none; + } + + textarea:focus-visible { + outline: none; + } + + } + + &-input { + display: flex; + justify-content: space-between; + box-sizing: border-box; + border-bottom: 1px solid; + border-bottom-color: #eee; + align-items: center; + padding: 5px 10px 5px 5px; + + input { + width: 92%; + height: 90%; + border: none; + margin: 0; + background: #ffffff; + font-size: 12px; + font-family: Arial, 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; + background-color: #edf0f5; + padding: 10px; + box-sizing: border-box; + border-radius: 4px; + + &::-webkit-input-placeholder { + color: #ccc; + font-size: 12px; + } + } + + input:focus-visible { + outline: none; + } + + button { + border: none; + border-radius: 50%; + width: 25px !important; + height: 25px !important; + padding: 0; + margin-left: 5px; + background: #609eec; + + svg { + fill: #fff; + width: 14px; + margin-top: 2px; + } + } + + button:hover { + background: #ccc; + cursor: pointer; + } + } + } + + .aie-ai-panel-footer { + width: fit-content; + border: solid 1px; + border-color: #eee; + background: #ffffff; + border-radius: 5px; + box-shadow: 0 0 7px 0 rgba(0, 0, 0, .15); + display: flex; + padding: 10px 0; + flex-direction: column; + margin-top: 5px; + + &-tips { + padding: 5px 10px; + color: #999; + font-size: 12px; + margin-bottom: 5px; + } + + hr { + height: 1px; + width: 100%; + margin: 5px 0; + background-color: #eaeaea; + border: none; + } + + p { + width: 100%; + margin: 0; + } + + p { + cursor: pointer; + display: flex; + align-items: center; + width: 100%; + box-sizing: border-box; + height: 25px; + line-height: 25px; + font-weight: 600; + font-size: 12px; + padding: 0 20px; + color: #666; + text-decoration: none; + font-family: Arial, 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; + } + + p:hover { + background: #e3e2e2; + } + + svg { + width: 16px; + height: 16px; + margin-right: 10px; + } + } } \ No newline at end of file