diff --git a/src/ai/config/AIConfig.ts b/src/ai/config/AIConfig.ts
index e2b588f..2f3bbc9 100644
--- a/src/ai/config/AIConfig.ts
+++ b/src/ai/config/AIConfig.ts
@@ -24,6 +24,19 @@ export interface AIChatConfig {
access_token?: string, // wenxin
}
+/**
+ * 文生图的基本配置信息
+ */
+export interface Text2ImageConfig {
+ modelType: "openai" | "custom";
+
+ baseUrl?: string; // openai && gitee
+ apiKey?: string; // openai && gitee && spark
+ model?: string; // openai
+
+ size?: "512x512" | "1024x1024";
+}
+
/**
* 快捷命令基本配置信息
*/
diff --git a/src/components/Footer.ts b/src/components/Footer.ts
index 2912ed1..6e826a0 100644
--- a/src/components/Footer.ts
+++ b/src/components/Footer.ts
@@ -6,6 +6,7 @@ import { UAIEditorEventListener, UAIEditorOptions } from "../core/UAIEditor.ts";
import { ToggleToc } from "./menus/toolbar/page/ToggleToc";
import { ToggleChat } from "./menus/toolbar/ai/ToggleChat.ts";
+import { ToggleImage } from "./menus/toolbar/ai/ToggleImage.ts";
import { CharacterCount } from "./menus/statusbar/CharacterCount.ts";
import { Fullscreen } from "./menus/statusbar/Fullscreen.ts";
@@ -22,6 +23,8 @@ export class Footer extends HTMLElement implements UAIEditorEventListener {
toggleToc!: ToggleToc;
// 模型对话
toggleChat!: ToggleChat;
+ // 图片绘制
+ toggleImage!: ToggleImage;
// 字数统计
characterCount!: CharacterCount;
@@ -39,6 +42,7 @@ export class Footer extends HTMLElement implements UAIEditorEventListener {
// 创建状态栏菜单
this.toggleToc = new ToggleToc({ menuType: "button", enable: true });
this.toggleChat = new ToggleChat({ menuType: "button", enable: true });
+ this.toggleImage = new ToggleImage({ menuType: "button", enable: true });
this.characterCount = new CharacterCount();
this.fullscreen = new Fullscreen({ menuType: "button", enable: true });
@@ -55,6 +59,7 @@ export class Footer extends HTMLElement implements UAIEditorEventListener {
// 初始化状态栏菜单
this.toggleToc.onCreate(event, options);
this.toggleChat.onCreate(event, options);
+ this.toggleImage.onCreate(event, options);
this.characterCount.onCreate(event, options);
this.fullscreen.onCreate(event, options);
@@ -82,13 +87,17 @@ export class Footer extends HTMLElement implements UAIEditorEventListener {
this.toggleToc.classList.add("uai-status-bar-button");
this.toggleChat.classList.add("uai-status-bar-button");
+ this.toggleImage.classList.add("uai-status-bar-button");
this.fullscreen.classList.add("uai-status-bar-button");
+ this.poweredBy.classList.add("uai-status-bar-button");
+ this.feedback.classList.add("uai-status-bar-button");
// 状态栏中添加功能菜单按钮
statusBarLeft.appendChild(this.toggleToc);
statusBarLeft.appendChild(this.createSplit());
statusBarLeft.appendChild(this.toggleChat);
+ statusBarLeft.appendChild(this.toggleImage);
statusBarLeft.appendChild(this.createSplit());
statusBarLeft.appendChild(this.characterCount);
@@ -116,6 +125,7 @@ export class Footer extends HTMLElement implements UAIEditorEventListener {
onTransaction(event: EditorEvents["transaction"], options: UAIEditorOptions) {
this.toggleToc.onTransaction(event, options);
this.toggleChat.onTransaction(event, options);
+ this.toggleImage.onTransaction(event, options);
this.characterCount.onTransaction(event, options);
this.fullscreen.onTransaction(event, options);
@@ -126,6 +136,7 @@ export class Footer extends HTMLElement implements UAIEditorEventListener {
onEditableChange(editable: boolean) {
this.toggleToc.onEditableChange(editable);
this.toggleChat.onEditableChange(editable);
+ this.toggleImage.onEditableChange(editable);
this.characterCount.onEditableChange(editable);
this.fullscreen.onEditableChange(editable);
diff --git a/src/components/ai/AIAction.ts b/src/components/ai/AIAction.ts
index 4a21506..9b67a42 100644
--- a/src/components/ai/AIAction.ts
+++ b/src/components/ai/AIAction.ts
@@ -220,3 +220,56 @@ export class AIChatResponseAction extends AbstractAIAction {
this.sourceInputElement = sourceInputElement
}
}
+
+/**
+ * 定义画图界面中响应数据的功能菜单
+ */
+export class AIImageResponseAction extends AbstractAIAction {
+ sourceDivElement!: HTMLElement
+ sourceInputElement!: HTMLElement
+
+ // 默认的菜单项
+ defaultAIActions: AICommand[] = [
+ {
+ // 追加图片内容到编辑器的当前光标位置
+ icon: Icons.InsertAction,
+ name: `追加`,
+ action: () => {
+ const {
+ state: { selection, tr },
+ view: { dispatch }
+ } = this.editor
+ const content = (this.editor as InnerEditor).parseHtml(markdownToHtml(this.sourceDivElement.innerHTML));
+ dispatch(tr.replace(selection.to, selection.to, new Slice(content, 0, 0)).scrollIntoView())
+ }
+ },
+ {
+ // 替换编辑器中选中的内容
+ icon: Icons.ReplaceAction,
+ name: `替换`,
+ action: () => {
+ const {
+ state: { tr },
+ view: { dispatch }
+ } = this.editor!
+ const content = (this.editor as InnerEditor).parseHtml(markdownToHtml(this.sourceDivElement.innerHTML));
+ dispatch(tr.replaceSelection(new Slice(content, 0, 0)).scrollIntoView())
+ }
+ }
+ ]
+
+ constructor() {
+ super()
+ this.aiActions = this.defaultAIActions.map((menu) => {
+ return {
+ ...menu,
+ name: `${t(menu.name)}`
+ }
+ })
+ }
+
+ bindSourceElement(sourceDivElement: HTMLElement, sourceInputElement: HTMLElement) {
+ this.sourceDivElement = sourceDivElement
+ this.sourceInputElement = sourceInputElement
+ }
+}
diff --git a/src/components/containers/ImageContainer.ts b/src/components/containers/ImageContainer.ts
new file mode 100644
index 0000000..42dd8f7
--- /dev/null
+++ b/src/components/containers/ImageContainer.ts
@@ -0,0 +1,305 @@
+// Copyright (c) 2024-present AI-Labs
+
+// @ ts-nocheck
+import closeIcon from "../../assets/icons/close.svg";
+import titleIcon from "../../assets/icons/image-bot.svg";
+import sendIcon from "../../assets/icons/send-message.svg";
+import { t } from "i18next";
+import { UAIEditorEventListener, UAIEditorOptions } from "../../core/UAIEditor.ts";
+import { EditorEvents } from "@tiptap/core";
+import { createAvatar } from "../../utils/CreateAvatar.ts";
+import { Icons } from "../Icons.ts";
+import OpenAI from "openai";
+import { AIChatConfig } from "../../ai/config/AIConfig.ts";
+import { markdownToHtml } from "../../utils/MarkdownUtil.ts";
+import { AIRequestAction, AIImageResponseAction } from "../ai/AIAction.ts";
+
+/**
+ * 定义图像处理容器
+ */
+export class ImageContainer extends HTMLElement implements UAIEditorEventListener {
+ // 图像处理容器元素
+ container!: HTMLElement;
+
+ // 图像处理标题元素
+ imageTitle!: HTMLElement;
+
+ // 图像生成内容元素
+ content!: HTMLElement;
+
+ // 输入组件元素
+ messageTextarea!: HTMLTextAreaElement;
+ imageButton!: HTMLButtonElement;
+ modelSelect!: HTMLSelectElement;
+
+ client?: OpenAI;
+ modelConfig?: AIChatConfig;
+
+ event!: EditorEvents["create"];
+ options!: UAIEditorOptions;
+
+ constructor() {
+ super();
+ // 初始化图像处理容器
+ this.container = document.createElement("div");
+ this.container.classList.add("uai-pannel-container");
+ this.appendChild(this.container);
+
+ // 初始化标题
+ this.imageTitle = document.createElement("div");
+ this.imageTitle.classList.add("uai-pannel-title");
+ this.imageTitle.innerHTML = `
${t('ai.image.title')}`;
+ this.container.appendChild(this.imageTitle);
+
+ // 初始化关闭容器按钮
+ const dialogClose = document.createElement("div");
+ dialogClose.classList.add("uai-pannel-close");
+ dialogClose.innerHTML = `
`;
+ // 点击关闭按钮关闭容器
+ dialogClose.addEventListener("click", () => {
+ this.style.display = "none";
+ })
+ this.imageTitle.appendChild(dialogClose);
+
+ // 初始化画图模型选择容器
+ const modelSelectDiv = document.createElement('div');
+ modelSelectDiv.classList.add('uai-container-app-models');
+
+ // 画图模型下拉选择
+ const modelSelectText = document.createElement('div');
+ modelSelectText.innerText = "模型:";
+ modelSelectDiv.appendChild(modelSelectText);
+
+ this.modelSelect = document.createElement('select');
+ this.modelSelect.classList.add('uai-model-select');
+ modelSelectDiv.appendChild(this.modelSelect);
+ this.container.appendChild(modelSelectDiv);
+
+ // 画图内容交互区域
+ this.content = document.createElement('div');
+ this.content.classList.add('uai-ai-content');
+ this.container.appendChild(this.content);
+
+ // 定义一个监听,当交互区域的内容发生变化时,滚动到最底部
+ const observer = new MutationObserver((mutations) => {
+ mutations.forEach((mutation) => {
+ if (mutation.type === 'childList' || mutation.type === 'characterData') {
+ this.content.scrollTop = this.content.scrollHeight
+ }
+ })
+ })
+ // 配置MutationObserver
+ const config = { childList: true, characterData: true }
+
+ // 开始监听div的变化
+ observer.observe(this.content, config)
+
+ // 初始化输入组件
+ const divAppInput = document.createElement('div');
+ divAppInput.classList.add('uai-container-app-input');
+
+ const divMessageContainer = document.createElement('div');
+ divMessageContainer.classList.add('uai-message-container');
+
+ this.messageTextarea = document.createElement('textarea');
+ this.messageTextarea.classList.add('uai-message-textarea');
+ this.messageTextarea.rows = 1;
+ this.messageTextarea.placeholder = '请输入内容... Shift+Enter换行;Enter发送';
+
+ // 定义键盘事件,当按Enter键发送内容
+ this.messageTextarea.addEventListener('keydown', (event) => {
+ this.handleKeyDown(event);
+ })
+ // 定义输入事件,输入框自动根据输入内容调整大小
+ this.messageTextarea.addEventListener('input', () => {
+ this.messageTextarea.style.height = 'auto';
+ this.messageTextarea.style.height = this.messageTextarea.scrollHeight + 'px';
+ })
+ divMessageContainer.appendChild(this.messageTextarea);
+
+ // 初始化发送按钮
+ this.imageButton = document.createElement('button');
+ this.imageButton.classList.add('uai-image-button');
+ this.imageButton.innerHTML = `
`;
+ this.imageButton.addEventListener('click', () => {
+ this.sendMessage(this.messageTextarea.value);
+ })
+ divMessageContainer.appendChild(this.imageButton);
+
+ divAppInput.appendChild(divMessageContainer);
+ this.container.appendChild(divAppInput);
+ }
+
+ /**
+ * 定义创建方法
+ * @param event
+ * @param options
+ */
+ onCreate(event: EditorEvents["create"], options: UAIEditorOptions) {
+ this.event = event;
+ this.options = options;
+
+ // 初始化画图客户端
+ if (options.ai && options.ai.image?.models?.text2image) {
+ // 初始化所有可选画图模型
+ for (let model of Object.keys(options.ai.image?.models?.text2image)) {
+ const option = document.createElement('option');
+ option.textContent = model;
+ this.modelSelect.appendChild(option);
+ }
+ // 获取当前选中的模型
+ const model = this.modelSelect.selectedOptions[0].value;
+ // 获取选中的模型的配置信息
+ this.modelConfig = options.ai!.image!.models!.text2image![model];
+ // 根据模型配置信息初始化一个客户端
+ this.client = new OpenAI({
+ baseURL: this.modelConfig.baseUrl,
+ apiKey: this.modelConfig.apiKey,
+ dangerouslyAllowBrowser: true,
+ });
+ // 定义模型选择的改变事件,当重新选择了对话模型,则需要重新创建一个客户端
+ this.modelSelect.addEventListener("change", () => {
+ // 获取当前选中的模型
+ const model = this.modelSelect.selectedOptions[0].value;
+ // 获取选中的模型的配置信息
+ this.modelConfig = options.ai!.image!.models!.text2image![model];
+ // 根据模型配置信息初始化一个客户端
+ this.client = new OpenAI({
+ baseURL: this.modelConfig.baseUrl,
+ apiKey: this.modelConfig.apiKey,
+ dangerouslyAllowBrowser: true,
+ });
+ })
+ }
+ }
+
+ onTransaction(event: EditorEvents["transaction"], options: UAIEditorOptions) {
+ }
+
+ onEditableChange(editable: boolean) {
+ }
+
+ handleKeyDown(event: KeyboardEvent) {
+ // 检查是否按下了 Shift 和 Enter 键
+ if (event.shiftKey && event.key === 'Enter') {
+ // 如果是 Shift + Enter,允许默认行为(通常是换行)
+ return;
+ }
+ // 检查是否按下了 Enter 键
+ if (event.key === 'Enter') {
+ // 阻止默认的换行行为
+ event.preventDefault();
+ // 处理发送消息的逻辑
+ this.sendMessage(this.messageTextarea.value);
+ }
+ }
+
+ /**
+ * 定义发送消息的方法
+ * @param message
+ */
+ sendMessage(message: string) {
+ if (message.trim() !== '') {
+ // 创建用户消息容器
+ const userMessageDiv = document.createElement('div');
+ userMessageDiv.classList.add('message');
+ userMessageDiv.classList.add('message-sent');
+
+ // 创建用户发送消息容器
+ const userBubbleDiv = document.createElement('div');
+ userBubbleDiv.classList.add('markdown-body');
+ userBubbleDiv.classList.add('bubble');
+ userBubbleDiv.classList.add('bubble-sent');
+ userBubbleDiv.innerHTML = markdownToHtml(message);
+ userBubbleDiv.setAttribute("InnerContent", message);
+
+ // 创建用户输入的功能按钮
+ const userActionDown = document.createElement(
+ 'uai-editor-ai-action-request-action'
+ ) as AIRequestAction;
+ userActionDown.classList.add('hidden-content');
+ userActionDown.onCreate(this.event, this.options);
+ userActionDown.bindSourceElement(userBubbleDiv, this.messageTextarea);
+
+ // 创建用户头像容器
+ const userAvatarDiv = createAvatar(Icons.UserAvatar);
+ userAvatarDiv.classList.add('user-avatar');
+
+ // 用户消息容器中,添加功能按钮
+ userMessageDiv.appendChild(userActionDown);
+ // 用户消息容器中,添加用户发送内容
+ userMessageDiv.appendChild(userBubbleDiv);
+ // 用户消息容器中,添加用户头像
+ userMessageDiv.appendChild(userAvatarDiv);
+
+ // 展示用户发送的消息
+ this.content.appendChild(userMessageDiv);
+
+ // 清空消息输入框
+ this.messageTextarea.value = '';
+ this.messageTextarea.style.height = 'auto';
+ this.messageTextarea.style.height = this.messageTextarea.scrollHeight + 'px';
+ this.messageTextarea.focus();
+
+ // 发送消息并等待响应
+ this.receiveResponse(message);
+ }
+ }
+
+ /**
+ * 定义接收模型响应的方法
+ * @param message
+ */
+ receiveResponse(message: string) {
+ // 创建模型响应消息容器
+ const botResponseDiv = document.createElement('div');
+ botResponseDiv.classList.add('message');
+ botResponseDiv.classList.add('message-received');
+
+ // 定义模型头像容器
+ const botAvatarDiv = createAvatar(Icons.BotAvatar);
+ botAvatarDiv.classList.add('bot-avatar');
+
+ // 定义模型输出内容容器
+ const botBubbleDiv = document.createElement('div');
+ botBubbleDiv.classList.add('markdown-body');
+ botBubbleDiv.classList.add('bubble');
+ botBubbleDiv.classList.add('bubble-received');
+ botBubbleDiv.innerHTML = Icons.Loading;
+
+ // 定义模型输出的功能按钮
+ const botActionDown = document.createElement(
+ 'uai-editor-ai-action-image-response-action'
+ ) as AIImageResponseAction;
+ botActionDown.classList.add('hidden-content');
+ botActionDown.onCreate(this.event, this.options);
+ botActionDown.bindSourceElement(botBubbleDiv, this.messageTextarea);
+
+ // 在模型响应消息容器中,添加头像
+ botResponseDiv.appendChild(botAvatarDiv);
+ // 在模型响应消息容器中,添加模型响应图片内容
+ botResponseDiv.appendChild(botBubbleDiv);
+ // 在模型响应消息容器中,添加功能按钮
+ botResponseDiv.appendChild(botActionDown);
+
+ this.content.appendChild(botResponseDiv);
+
+ // 发起画图请求,并接收模型响应图像
+ if (this.client && this.modelConfig) {
+ // 通过客户端发起请求
+ this.client.images.generate({
+ "model": this.modelConfig.model ?? "dall-e-3",
+ "prompt": message,
+ "size": "1024x1024",
+ }).then(response => {
+ // 接收模型响应图像,并进行图像展示
+ if (response.data) {
+ botBubbleDiv.innerHTML = `
`;
+ } else if (response.images) {
+ botBubbleDiv.innerHTML = `
`;
+ }
+ this.content.scrollTop = this.content.scrollHeight;
+ });
+ }
+ }
+}
diff --git a/src/components/index.ts b/src/components/index.ts
index cf49fc0..4d06161 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -6,6 +6,7 @@ import { Editor } from "../components/Editor.ts";
import { Footer } from "../components/Footer.ts";
import { ChatContainer } from "../components/containers/ChatContainer.ts";
+import { ImageContainer } from "../components/containers/ImageContainer.ts";
import { TocContainer } from "../components/containers/TocContainer.ts";
import { Ribbon } from "./menus/toolbar/Ribbon.ts";
@@ -88,6 +89,7 @@ import { ExportMarkdown } from "./menus/toolbar/export/ExportMarkdown.ts";
import { ExportImage } from "./menus/toolbar/export/ExportImage.ts";
import { ToggleChat } from "./menus/toolbar/ai/ToggleChat.ts";
+import { ToggleImage } from "./menus/toolbar/ai/ToggleImage.ts";
import { TextSelectionBubbleMenu } from "./menus/bubble/TextSelectionBubbleMenu.ts";
@@ -105,7 +107,7 @@ import { Fullscreen } from "./menus/statusbar/Fullscreen.ts";
import { Feedback } from "./menus/statusbar/Feedback.ts";
import { PoweredBy } from "./menus/statusbar/PoweredBy.ts";
-import { AIRequestAction, AIChatResponseAction } from "./ai/AIAction.ts";
+import { AIRequestAction, AIChatResponseAction, AIImageResponseAction } from "./ai/AIAction.ts";
// 注册组件
defineCustomElement('uai-editor-header', Header);
@@ -113,6 +115,7 @@ defineCustomElement('uai-editor-editor', Editor);
defineCustomElement('uai-editor-footer', Footer);
defineCustomElement('uai-editor-chat-container', ChatContainer);
+defineCustomElement('uai-editor-image-container', ImageContainer);
defineCustomElement('uai-editor-toc-container', TocContainer);
defineCustomElement('uai-editor-ribbon-menu', Ribbon);
@@ -195,6 +198,7 @@ defineCustomElement('uai-editor-export-menu-markdown', ExportMarkdown);
defineCustomElement('uai-editor-export-menu-image', ExportImage);
defineCustomElement('uai-editor-ai-menu-toggle-chat', ToggleChat);
+defineCustomElement('uai-editor-ai-menu-toggle-image', ToggleImage);
defineCustomElement('uai-editor-statusbar-menu-character-count', CharacterCount);
@@ -214,3 +218,4 @@ defineCustomElement('uai-editor-bubble-menu-audio', AudioBubbleMenu);
defineCustomElement('uai-editor-ai-action-request-action', AIRequestAction);
defineCustomElement('uai-editor-ai-action-chat-response-action', AIChatResponseAction);
+defineCustomElement('uai-editor-ai-action-image-response-action', AIImageResponseAction);
diff --git a/src/components/menus/toolbar/Classic.ts b/src/components/menus/toolbar/Classic.ts
index 354b9da..4927e94 100644
--- a/src/components/menus/toolbar/Classic.ts
+++ b/src/components/menus/toolbar/Classic.ts
@@ -78,6 +78,7 @@ import { ExportMarkdown } from "./export/ExportMarkdown.ts";
import { ExportImage } from "./export/ExportImage.ts";
import { ToggleChat } from "./ai/ToggleChat.ts";
+import { ToggleImage } from "./ai/ToggleImage.ts";
/**
* 传统菜单栏
@@ -192,6 +193,7 @@ export class Classic extends HTMLElement implements UAIEditorEventListener {
// 人工智能菜单
aiMenuToggleChat!: ToggleChat;
+ aiMenuToggleImage!: ToggleImage;
constructor(defaultToolbarMenus: Record[]) {
super();
@@ -467,6 +469,9 @@ export class Classic extends HTMLElement implements UAIEditorEventListener {
this.aiMenuToggleChat = new ToggleChat({ menuType: "button", enable: true, header: "classic", hideText: false });
this.eventComponents.push(this.aiMenuToggleChat);
+
+ this.aiMenuToggleImage = new ToggleImage({ menuType: "button", enable: true, header: "classic", hideText: false });
+ this.eventComponents.push(this.aiMenuToggleImage);
}
/**
* 创建基础菜单
@@ -685,5 +690,6 @@ export class Classic extends HTMLElement implements UAIEditorEventListener {
group1.classList.add("uai-classic-virtual-group");
this.classicMenuAIGroup.appendChild(group1);
group1.appendChild(this.aiMenuToggleChat);
+ group1.appendChild(this.aiMenuToggleImage);
}
}
\ No newline at end of file
diff --git a/src/components/menus/toolbar/Ribbon.ts b/src/components/menus/toolbar/Ribbon.ts
index bbcb7a0..a1f2946 100644
--- a/src/components/menus/toolbar/Ribbon.ts
+++ b/src/components/menus/toolbar/Ribbon.ts
@@ -80,6 +80,7 @@ import { ExportMarkdown } from "./export/ExportMarkdown.ts";
import { ExportImage } from "./export/ExportImage.ts";
import { ToggleChat } from "./ai/ToggleChat.ts";
+import { ToggleImage } from "./ai/ToggleImage.ts";
/**
* 经典菜单栏
@@ -195,6 +196,7 @@ export class Ribbon extends HTMLElement implements UAIEditorEventListener {
// 人工智能菜单
aiMenuToggleChat!: ToggleChat;
+ aiMenuToggleImage!: ToggleImage;
constructor(defaultToolbarMenus: Record[]) {
super();
@@ -490,6 +492,9 @@ export class Ribbon extends HTMLElement implements UAIEditorEventListener {
this.aiMenuToggleChat = new ToggleChat({ menuType: "button", enable: true, huge: true, hideText: false });
this.eventComponents.push(this.aiMenuToggleChat);
+
+ this.aiMenuToggleImage = new ToggleImage({ menuType: "button", enable: true, huge: true, hideText: false });
+ this.eventComponents.push(this.aiMenuToggleImage);
}
/**
@@ -790,5 +795,6 @@ export class Ribbon extends HTMLElement implements UAIEditorEventListener {
group1.classList.add("uai-ribbon-virtual-group");
this.ribbonMenuAIGroup.appendChild(group1);
group1.appendChild(this.aiMenuToggleChat);
+ group1.appendChild(this.aiMenuToggleImage);
}
}
\ No newline at end of file
diff --git a/src/components/menus/toolbar/ai/ToggleImage.ts b/src/components/menus/toolbar/ai/ToggleImage.ts
new file mode 100644
index 0000000..0a14eb7
--- /dev/null
+++ b/src/components/menus/toolbar/ai/ToggleImage.ts
@@ -0,0 +1,79 @@
+// Copyright (c) 2024-present AI-Labs
+
+// @ ts-nocheck
+import { MenuButton, MenuButtonOptions } from "../../MenuButton.ts";
+import icon from "../../../../assets/icons/image-bot.svg";
+import { t } from "i18next";
+import { UAIEditorEventListener, UAIEditorOptions, InnerEditor } from "../../../../core/UAIEditor.ts";
+import { EditorEvents } from "@tiptap/core";
+
+/**
+ * 人工智能菜单:画图
+ */
+export class ToggleImage extends HTMLElement implements UAIEditorEventListener {
+ // 按钮选项
+ menuButtonOptions: MenuButtonOptions = {
+ menuType: "button",
+ enable: true,
+ icon: icon,
+ hideText: false,
+ text: t('ai.image.title'),
+ tooltip: t('ai.image.title'),
+ }
+
+ // 功能按钮
+ menuButton: MenuButton;
+
+ constructor(options: MenuButtonOptions) {
+ super();
+
+ // 初始化功能按钮选项
+ this.menuButtonOptions = { ...this.menuButtonOptions, ...options };
+
+ // 创建功能按钮
+ this.menuButton = new MenuButton(this.menuButtonOptions);
+ }
+
+ /**
+ * 定义创建方法
+ * @param event
+ * @param options
+ */
+ onCreate(event: EditorEvents["create"], options: UAIEditorOptions) {
+ this.menuButton.onCreate(event, options);
+ this.appendChild(this.menuButton);
+
+ // 定义按钮点击事件,打开人工智能画图窗口
+ this.addEventListener("click", () => {
+ if (this.menuButtonOptions.enable) {
+ // 判断画图窗口是否已打开
+ const display = (event.editor as InnerEditor).uaiEditor.imageContainer.style.display;
+ if (display != "none") {
+ // 如果窗口已打开,则关闭窗口
+ (event.editor as InnerEditor).uaiEditor.imageContainer.style.display = "none";
+ } else {
+ // 如果窗口没打开,则先关闭其他窗口,再打开画图窗口
+ (event.editor as InnerEditor).uaiEditor.toggleContainers.forEach(toggle => {
+ toggle.style.display = "none";
+ });
+ (event.editor as InnerEditor).uaiEditor.imageContainer.style.display = "block";
+ }
+ }
+ })
+ }
+
+ /**
+ * 定义Transaction监听方法
+ * @param event
+ * @param options
+ */
+ onTransaction(event: EditorEvents["transaction"], options: UAIEditorOptions) {
+ this.menuButton.onTransaction(event, options);
+ }
+
+ onEditableChange(editable: boolean) {
+ this.menuButtonOptions.enable = editable;
+ this.menuButton.onEditableChange(editable);
+ }
+}
+
diff --git a/src/core/UAIEditor.ts b/src/core/UAIEditor.ts
index 7b62628..bb57e8c 100644
--- a/src/core/UAIEditor.ts
+++ b/src/core/UAIEditor.ts
@@ -13,6 +13,7 @@ import { TableOfContentData } from "@tiptap-pro/extension-table-of-contents";
import "../components"
import { Header } from "../components/Header.ts";
import { ChatContainer } from "../components/containers/ChatContainer.ts";
+import { ImageContainer } from "../components/containers/ImageContainer.ts";
import { TocContainer } from "../components/containers/TocContainer.ts";
import { Editor } from "../components/Editor.ts";
import { Footer } from "../components/Footer.ts";
@@ -25,7 +26,7 @@ import i18next from "i18next";
import { zh } from "../i18n/zh.ts";
import { Resource } from "i18next";
import { allExtensions } from "./UAIExtensions.ts";
-import { AIChatConfig } from "../ai/config/AIConfig.ts";
+import { AIChatConfig, Text2ImageConfig } from "../ai/config/AIConfig.ts";
import { markdownToHtml } from "../utils/MarkdownUtil.ts";
import { Uploader } from "../utils/FileUploader.ts";
@@ -110,6 +111,11 @@ export type UAIEditorOptions = {
ai?: {
chat?: {
models?: Record,
+ },
+ image?: {
+ models?: {
+ text2image?: Record,
+ },
}
}
}
@@ -150,6 +156,7 @@ export class UAIEditor {
header!: Header;
chatContainer!: ChatContainer;
+ imageContainer!: ImageContainer;
tocContainer!: TocContainer;
editor!: Editor;
footer!: Footer;
@@ -252,6 +259,11 @@ export class UAIEditor {
chat: {
models: customOptions.ai?.chat?.models,
},
+ image: {
+ models: {
+ text2image: customOptions.ai?.image?.models?.text2image,
+ },
+ }
}
};
const i18nConfig = this.options.i18n || {};
@@ -295,6 +307,11 @@ export class UAIEditor {
this.center.appendChild(this.chatContainer);
this.toggleContainers.push(this.chatContainer);
+ this.imageContainer = new ImageContainer();
+ this.imageContainer.style.display = "none";
+ this.center.appendChild(this.imageContainer);
+ this.toggleContainers.push(this.imageContainer);
+
this.editor = new Editor();
this.editor.classList.add("uai-main");
this.center.appendChild(this.editor);
@@ -358,6 +375,7 @@ export class UAIEditor {
this.header.onCreate(event, this.options);
this.tocContainer.onCreate(event, this.options);
this.chatContainer.onCreate(event, this.options);
+ this.imageContainer.onCreate(event, this.options);
this.editor.onCreate(event, this.options);
this.footer.onCreate(event, this.options);
this.eventComponents.forEach(component => {
@@ -369,6 +387,7 @@ export class UAIEditor {
this.header.onTransaction(event, this.options);
this.tocContainer.onTransaction(event, this.options);
this.chatContainer.onTransaction(event, this.options);
+ this.imageContainer.onTransaction(event, this.options);
this.editor.onTransaction(event, this.options);
this.footer.onTransaction(event, this.options);
this.eventComponents.forEach(component => {