diff --git a/src/assets/icons/arxiv.svg b/src/assets/icons/arxiv.svg new file mode 100644 index 0000000..4b8a00e --- /dev/null +++ b/src/assets/icons/arxiv.svg @@ -0,0 +1 @@ +Zhihu iconZhihu icon \ No newline at end of file diff --git a/src/components/containers/ArxivContainer.ts b/src/components/containers/ArxivContainer.ts new file mode 100644 index 0000000..eab4ade --- /dev/null +++ b/src/components/containers/ArxivContainer.ts @@ -0,0 +1,441 @@ +// Copyright (c) 2024-present AI-Labs + +// @ ts-nocheck +import { EditorEvents } from "@tiptap/core"; +import { t } from "i18next"; + +import { UAIEditorEventListener, UAIEditorOptions } from "../../core/UAIEditor.ts"; +import { createAvatar } from "../../utils/CreateAvatar.ts"; +import { Icons } from "../Icons.ts"; +import { markdownToHtml } from "../../utils/MarkdownUtil.ts"; +import { AIRequestAction } from "../ai/AIAction.ts"; + +import closeIcon from "../../assets/icons/close.svg"; +import titleIcon from "../../assets/icons/arxiv.svg"; +import sendIcon from "../../assets/icons/send-message.svg"; +import { FullScreenModal } from "../modals/FullScreenModal.ts"; + +export class ArxivContainer extends HTMLElement implements UAIEditorEventListener { + // 容器元素 + container!: HTMLElement; + + // 标题元素 + arxivTitle!: HTMLElement; + + // 内容元素 + content!: HTMLElement; + + // 文本输入框 + messageTextarea!: HTMLTextAreaElement; + imageButton!: HTMLButtonElement; + categorySelect!: HTMLSelectElement; + subCategorySelect!: HTMLSelectElement; + querySelect!: HTMLSelectElement; + + event!: EditorEvents["create"]; + options!: UAIEditorOptions; + + currentPage = 1; + pageSize = 10; + + categories = [ + { + value: 'all', label: 'All Categories', children: [ + { value: 'all', label: 'All Categories' } + ] + }, + { + value: 'cs', label: 'Computer Science', children: [ + { value: 'cs', label: 'All Computer Science' }, + { value: 'cs.AI', label: 'Artificial Intelligence' }, + { value: 'cs.AR', label: 'Hardware Architecture' }, + { value: 'cs.CC', label: 'Computational Complexity' }, + ] + }, + { + value: 'econ', label: 'Economics', children: [ + { value: 'econ', label: 'All Economics' }, + { value: 'econ.EM', label: 'Econometrics' }, + { value: 'econ.GN', label: 'General Economics' }, + { value: 'econ.TH', label: 'Theoretical Economics' }, + ] + }, + { + value: 'eess', label: 'Electrical Engineering and Systems Science', children: [ + { value: 'eess', label: 'Electrical Engineering and Systems Science' } + ] + }, + { + value: 'math', label: 'Mathematics', children: [ + { value: 'math', label: 'Mathematics' } + ] + }, + { + value: 'physics', label: 'Physics', children: [ + { value: 'physics', label: 'Physics' } + ] + }, + { + value: 'q-bio', label: 'Quantitative Biology', children: [ + { value: 'q-bio', label: 'Quantitative Biology' } + ] + }, + { + value: 'q-fin', label: 'Quantitative Finance', children: [ + { value: 'q-fin', label: 'Quantitative Finance' } + ] + }, + { + value: 'stat', label: 'Statistics', children: [ + { value: 'stat', label: 'Statistics' } + ] + }, + ]; + queries = [ + { value: 'all', label: 'All Papers' }, + { value: 'ti', label: 'Titles' }, + { value: 'au', label: 'Authors' }, + { value: 'abs', label: 'Abstracts' }, + { value: 'co', label: 'Comments' }, + ] + constructor() { + super(); + + // 初始化容器 + this.container = document.createElement("div"); + this.container.classList.add("uai-pannel-container"); + this.appendChild(this.container); + + // 初始化标题 + this.arxivTitle = document.createElement("div"); + this.arxivTitle.classList.add("uai-pannel-title"); + this.arxivTitle.innerHTML = `  ${t('tools.arxiv.title')}` + this.container.appendChild(this.arxivTitle); + // 初始化关闭按钮 + const dialogClose = document.createElement("div"); + dialogClose.classList.add("uai-pannel-close"); + dialogClose.innerHTML = ``; + // 点击关闭按钮时关闭容器 + dialogClose.addEventListener("click", () => { + this.style.display = "none"; + }) + this.arxivTitle.appendChild(dialogClose); + + const categorySelectDiv = document.createElement('div'); + categorySelectDiv.classList.add('uai-container-app-models'); + + const categorySelectText = document.createElement('div'); + categorySelectText.innerText = "类别:"; + categorySelectDiv.appendChild(categorySelectText); + + this.categorySelect = document.createElement('select'); + this.categorySelect.classList.add('uai-model-select'); + categorySelectDiv.appendChild(this.categorySelect); + this.container.appendChild(categorySelectDiv); + + const subCategorySelectDiv = document.createElement('div'); + subCategorySelectDiv.classList.add('uai-container-app-models'); + + const subCategorySelectText = document.createElement('div'); + subCategorySelectText.innerText = "子类:"; + subCategorySelectDiv.appendChild(subCategorySelectText); + + this.subCategorySelect = document.createElement('select'); + this.subCategorySelect.classList.add('uai-model-select'); + subCategorySelectDiv.appendChild(this.subCategorySelect); + this.container.appendChild(subCategorySelectDiv); + + const querySelectDiv = document.createElement('div'); + querySelectDiv.classList.add('uai-container-app-models'); + + const querySelectText = document.createElement('div'); + querySelectText.innerText = "查询:"; + querySelectDiv.appendChild(querySelectText); + + this.querySelect = document.createElement('select'); + this.querySelect.classList.add('uai-model-select'); + querySelectDiv.appendChild(this.querySelect); + this.container.appendChild(querySelectDiv); + + // 初始化对话内容容器 + 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.handleSearch(this.messageTextarea.value) + }) + divMessageContainer.appendChild(this.imageButton); + + divAppInput.appendChild(divMessageContainer); + this.container.appendChild(divAppInput); + } + + onCreate(event: EditorEvents["create"], options: UAIEditorOptions) { + this.event = event; + this.options = options; + + this.categories.forEach(cat => { + const option = document.createElement('option'); + option.value = cat.value; + option.textContent = cat.label; + this.categorySelect.appendChild(option); + }); + + this.categorySelect.addEventListener('change', () => { + const subCategories = this.categories.filter(cat => cat.value === this.categorySelect.value).flatMap(cat => cat.children); + this.subCategorySelect.innerHTML = ''; + subCategories.forEach(cat => { + const option = document.createElement('option'); + option.value = cat.value; + option.textContent = cat.label; + this.subCategorySelect.appendChild(option); + }); + }); + + // 触发 categorySelect 的 change 事件,以初始化子类选项 + this.categorySelect.dispatchEvent(new Event('change')); + + this.queries.forEach(query => { + const option = document.createElement('option'); + option.value = query.value; + option.textContent = query.label; + this.querySelect.appendChild(option); + }); + } + + 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.handleSearch(this.messageTextarea.value); + } + } + + handleSearch(message: string) { + try { + 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.fetchArxivData(message); + } + } catch (error) { + console.error('arXiv API error:', error); + } + } + + fetchArxivData(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; + + // 在模型响应消息容器中,添加头像 + botResponseDiv.appendChild(botAvatarDiv); + // 在模型响应消息容器中,添加模型响应消息内容 + botResponseDiv.appendChild(botBubbleDiv); + + this.content.appendChild(botResponseDiv); + + const searchQuery = this.subCategorySelect.value === "all" ? `${this.querySelect.value}:${message}` : `cat:${this.subCategorySelect.value} AND ${this.querySelect.value}:${message}`; + const start = (this.currentPage - 1) * this.pageSize; + fetch( + `https://export.arxiv.org/api/query?search_query=${encodeURIComponent(searchQuery)}&start=${start}&max_results=${this.pageSize}&sortBy=submittedDate&sortOrder=descending` + ) + .then(response => response.text()) + .then(xmlData => { + const arxivs = this.parseXmlResponse(xmlData); + botBubbleDiv.innerHTML = ""; + arxivs.items.forEach(arxiv => { + const arxivItem = document.createElement('div'); + const title = document.createElement('h3'); + title.textContent = arxiv.title; + arxivItem.appendChild(title); + const authors = document.createElement('p'); + authors.textContent = `作者:${arxiv.authors}`; + arxivItem.appendChild(authors); + const published = document.createElement('p'); + published.textContent = `日期:${arxiv.published}`; + arxivItem.appendChild(published); + const summary = document.createElement('p'); + summary.textContent = `摘要:${arxiv.summary}`; + arxivItem.appendChild(summary); + const link = document.createElement('a'); + link.style.margin = '5px'; + link.textContent = '查看原文'; + link.addEventListener('click', () => { + window.open(arxiv.link, '_blank'); + }); + arxivItem.appendChild(link); + const pdf = document.createElement('a'); + pdf.style.margin = '5px'; + pdf.textContent = '查看论文'; + pdf.addEventListener('click', () => { + const modal = new FullScreenModal(arxiv.title); + const body = document.createElement('div'); + body.classList.add("uai-file-preview-modal-body"); + const iframe = document.createElement('iframe'); + iframe.src = arxiv.pdf; + body.appendChild(iframe); + modal.modalBody.appendChild(body); + modal.show(); + }); + arxivItem.appendChild(pdf); + const ref = document.createElement('a'); + ref.style.margin = '5px'; + ref.textContent = '添加引用'; + ref.addEventListener('click', () => { + const { + state: { selection } + } = this.event.editor; + + this.event.editor.commands.insertContentAt(selection.from, { + type: 'file', + attrs: { + ['url']: arxiv.pdf, + name: `${arxiv.title}.pdf`, + size: 0, + type: "application/pdf", + previewType: "application/pdf", + }, + }); + }); + arxivItem.appendChild(ref); + botBubbleDiv.appendChild(arxivItem); + botBubbleDiv.appendChild(document.createElement('hr')); + }); + }) + .catch(error => { + console.error('arXiv API error:', error); + }); + } + + parseXmlResponse(xml: string) { + const parser = new DOMParser(); + const doc = parser.parseFromString(xml, 'application/xml'); + + const entries = Array.from(doc.querySelectorAll('entry')); + return { + total: parseInt(doc.querySelector('opensearch>totalResults')?.textContent || '0'), + items: entries.map(entry => ({ + title: entry.querySelector('title')?.textContent?.trim() || '', + authors: Array.from(entry.querySelectorAll('author')).map(a => + a.querySelector('name')?.textContent?.trim() || '' + ).join(', '), + published: new Date( + entry.querySelector('published')?.textContent || '' + ).toLocaleDateString(), + summary: entry.querySelector('summary')?.textContent?.trim() || '', + link: entry.querySelector('link[type="text/html"]')?.getAttribute('href') || '', + pdf: entry.querySelector('link[type="application/pdf"]')?.getAttribute('href') || '', + })) + }; + } +} diff --git a/src/components/containers/ChatContainer.ts b/src/components/containers/ChatContainer.ts index 5ec3064..2954f5c 100644 --- a/src/components/containers/ChatContainer.ts +++ b/src/components/containers/ChatContainer.ts @@ -50,7 +50,7 @@ export class ChatContainer extends HTMLElement implements UAIEditorEventListener // 初始化标题 this.chatTitle = document.createElement("div"); this.chatTitle.classList.add("uai-pannel-title"); - this.chatTitle.innerHTML = `  ${t('ai.chat.title')}` + this.chatTitle.innerHTML = `  ${t('ai.chat.title')}` this.container.appendChild(this.chatTitle); // 初始化关闭按钮 diff --git a/src/components/containers/ImageContainer.ts b/src/components/containers/ImageContainer.ts index c1684a9..1c4d910 100644 --- a/src/components/containers/ImageContainer.ts +++ b/src/components/containers/ImageContainer.ts @@ -51,7 +51,7 @@ export class ImageContainer extends HTMLElement implements UAIEditorEventListene // 初始化标题 this.imageTitle = document.createElement("div"); this.imageTitle.classList.add("uai-pannel-title"); - this.imageTitle.innerHTML = `  ${t('ai.image.title')}`; + this.imageTitle.innerHTML = `  ${t('ai.image.title')}`; this.container.appendChild(this.imageTitle); // 初始化关闭容器按钮 diff --git a/src/components/containers/TocContainer.ts b/src/components/containers/TocContainer.ts index 483e85a..b44e5a6 100644 --- a/src/components/containers/TocContainer.ts +++ b/src/components/containers/TocContainer.ts @@ -34,7 +34,7 @@ export class TocContainer extends HTMLElement implements UAIEditorEventListener // 初始化标题 this.tocTitle = document.createElement("div"); this.tocTitle.classList.add("uai-pannel-title"); - this.tocTitle.innerHTML = `  ${t('toc.title')}`; + this.tocTitle.innerHTML = `  ${t('toc.title')}`; this.container.appendChild(this.tocTitle); // 初始化关闭容器按钮 diff --git a/src/components/index.ts b/src/components/index.ts index ca177df..e2349be 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -7,6 +7,7 @@ import { Header } from "../components/Header.ts"; import { Editor } from "../components/Editor.ts"; import { Footer } from "../components/Footer.ts"; +import { ArxivContainer } from "./containers/ArxivContainer.ts"; import { ChatContainer } from "../components/containers/ChatContainer.ts"; import { ImageContainer } from "../components/containers/ImageContainer.ts"; import { TocContainer } from "../components/containers/TocContainer.ts"; @@ -80,6 +81,7 @@ import { DeleteRow } from "./menus/toolbar/table/DeleteRow.ts"; import { DeleteTable } from "./menus/toolbar/table/DeleteTable.ts"; import { Diagrams } from "./menus/toolbar/tools/Diagrams.ts"; +import { ToggleArxiv } from "./menus/toolbar/tools/ToggleArxiv.ts"; import { ToggleToc } from "./menus/toolbar/page/ToggleToc.ts"; import { BackgroundColor } from "./menus/toolbar/page/BackgroundColor.ts"; @@ -119,6 +121,7 @@ defineCustomElement('uai-editor-header', Header); defineCustomElement('uai-editor-editor', Editor); defineCustomElement('uai-editor-footer', Footer); +defineCustomElement('uai-editor-arxiv-container', ArxivContainer); defineCustomElement('uai-editor-chat-container', ChatContainer); defineCustomElement('uai-editor-image-container', ImageContainer); defineCustomElement('uai-editor-toc-container', TocContainer); @@ -192,6 +195,7 @@ defineCustomElement('uai-editor-table-menu-delete-row', DeleteRow); defineCustomElement('uai-editor-table-menu-delete-table', DeleteTable); defineCustomElement('uai-editor-tools-menu-diagrams', Diagrams); +defineCustomElement('uai-editor-tools-menu-toggle-arxiv', ToggleArxiv); defineCustomElement('uai-editor-page-menu-toggle-toc', ToggleToc); defineCustomElement('uai-editor-page-menu-background-color', BackgroundColor); diff --git a/src/components/menus/toolbar/Classic.ts b/src/components/menus/toolbar/Classic.ts index 47c1121..b3ee15d 100644 --- a/src/components/menus/toolbar/Classic.ts +++ b/src/components/menus/toolbar/Classic.ts @@ -67,6 +67,7 @@ import { DeleteRow } from "./table/DeleteRow.ts"; import { DeleteTable } from "./table/DeleteTable.ts"; import { Diagrams } from "./tools/Diagrams.ts"; +import { ToggleArxiv } from "./tools/ToggleArxiv.ts"; import { ToggleToc } from "./page/ToggleToc.ts"; import { BackgroundColor } from "./page/BackgroundColor.ts"; @@ -183,6 +184,7 @@ export class Classic extends HTMLElement implements UAIEditorEventListener { // 工具菜单 toolsMenuDiagrams!: Diagrams; + toolsMenuToggleArxiv!: ToggleArxiv; // 页面菜单 pageMenuToggleToc!: ToggleToc; @@ -266,7 +268,7 @@ export class Classic extends HTMLElement implements UAIEditorEventListener { this.classicScrollableContainer.appendChild(this.classicMenuAIScrollable); } }) - + this.classicScrollableContainer = document.createElement("div"); this.classicScrollableContainer.classList.add("uai-classic-scrollable-container"); this.classicMenu.appendChild(this.classicScrollableContainer); @@ -450,6 +452,9 @@ export class Classic extends HTMLElement implements UAIEditorEventListener { this.toolsMenuDiagrams = new Diagrams({ menuType: "button", enable: true, header: "classic", hideText: false }); this.eventComponents.push(this.toolsMenuDiagrams); + this.toolsMenuToggleArxiv = new ToggleArxiv({ menuType: "button", enable: true, header: "classic", hideText: false }); + this.eventComponents.push(this.toolsMenuToggleArxiv); + this.pageMenuToggleToc = new ToggleToc({ menuType: "button", enable: true, header: "classic", hideText: false }); this.eventComponents.push(this.pageMenuToggleToc); @@ -627,6 +632,11 @@ export class Classic extends HTMLElement implements UAIEditorEventListener { group1.classList.add("uai-classic-virtual-group"); this.classicMenuToolGroup.appendChild(group1); group1.appendChild(this.toolsMenuDiagrams); + + const group2 = document.createElement("div"); + group2.classList.add("uai-classic-virtual-group"); + this.classicMenuToolGroup.appendChild(group2); + group2.appendChild(this.toolsMenuToggleArxiv); } /** diff --git a/src/components/menus/toolbar/Ribbon.ts b/src/components/menus/toolbar/Ribbon.ts index df52fc2..03a6871 100644 --- a/src/components/menus/toolbar/Ribbon.ts +++ b/src/components/menus/toolbar/Ribbon.ts @@ -70,6 +70,7 @@ import { DeleteRow } from "./table/DeleteRow.ts"; import { DeleteTable } from "./table/DeleteTable.ts"; import { Diagrams } from "./tools/Diagrams.ts"; +import { ToggleArxiv } from "./tools/ToggleArxiv.ts"; import { ToggleToc } from "./page/ToggleToc.ts"; import { BackgroundColor } from "./page/BackgroundColor.ts"; @@ -185,6 +186,7 @@ export class Ribbon extends HTMLElement implements UAIEditorEventListener { // 工具菜单 toolsMenuDiagrams!: Diagrams; + toolsMenuToggleArxiv!: ToggleArxiv; // 页面菜单 pageMenuToggleToc!: ToggleToc; @@ -471,6 +473,9 @@ export class Ribbon extends HTMLElement implements UAIEditorEventListener { this.toolsMenuDiagrams = new Diagrams({ menuType: "button", enable: true, huge: true, hideText: false }); this.eventComponents.push(this.toolsMenuDiagrams); + this.toolsMenuToggleArxiv = new ToggleArxiv({ menuType: "button", enable: true, huge: true, hideText: false }); + this.eventComponents.push(this.toolsMenuToggleArxiv); + this.pageMenuToggleToc = new ToggleToc({ menuType: "button", enable: true, huge: true, hideText: false }); this.eventComponents.push(this.pageMenuToggleToc); @@ -734,6 +739,11 @@ export class Ribbon extends HTMLElement implements UAIEditorEventListener { group1.classList.add("uai-ribbon-virtual-group"); this.ribbonMenuToolGroup.appendChild(group1); group1.appendChild(this.toolsMenuDiagrams); + + const group2 = document.createElement("div"); + group2.classList.add("uai-ribbon-virtual-group"); + this.ribbonMenuToolGroup.appendChild(group2); + group2.appendChild(this.toolsMenuToggleArxiv); } /** diff --git a/src/components/menus/toolbar/tools/ToggleArxiv.ts b/src/components/menus/toolbar/tools/ToggleArxiv.ts new file mode 100644 index 0000000..9f9c3ba --- /dev/null +++ b/src/components/menus/toolbar/tools/ToggleArxiv.ts @@ -0,0 +1,82 @@ +// Copyright (c) 2024-present AI-Labs + +// @ ts-nocheck +import { EditorEvents } from "@tiptap/core"; + +import { t } from "i18next"; + +import { UAIEditorEventListener, UAIEditorOptions, InnerEditor } from "../../../../core/UAIEditor.ts"; +import { MenuButton, MenuButtonOptions } from "../../MenuButton.ts"; + +import icon from "../../../../assets/icons/arxiv.svg"; + +/** + * 人工智能菜单:聊天 + */ +export class ToggleArxiv extends HTMLElement implements UAIEditorEventListener { + // 按钮选项 + menuButtonOptions: MenuButtonOptions = { + menuType: "button", + enable: true, + icon: icon, + hideText: false, + text: t('tools.arxiv.title'), + tooltip: t('tools.arxiv.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.arxivContainer.style.display; + if (display != "none") { + // 如果窗口已打开,则关闭窗口 + (event.editor as InnerEditor).uaiEditor.arxivContainer.style.display = "none"; + } else { + // 如果窗口没打开,则先关闭其他窗口,再打开聊天窗口 + (event.editor as InnerEditor).uaiEditor.toggleContainers.forEach(toggle => { + toggle.style.display = "none"; + }); + (event.editor as InnerEditor).uaiEditor.arxivContainer.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 c8dbfe4..fb60d5e 100644 --- a/src/core/UAIEditor.ts +++ b/src/core/UAIEditor.ts @@ -16,6 +16,7 @@ import { Resource } from "i18next"; import "../components" import { Header } from "../components/Header.ts"; +import { ArxivContainer } from "../components/containers/ArxivContainer.ts"; import { ChatContainer } from "../components/containers/ChatContainer.ts"; import { ImageContainer } from "../components/containers/ImageContainer.ts"; import { TocContainer } from "../components/containers/TocContainer.ts"; @@ -213,6 +214,7 @@ export class UAIEditor { header!: Header; chatContainer!: ChatContainer; imageContainer!: ImageContainer; + arxivContainer!: ArxivContainer; tocContainer!: TocContainer; editor!: Editor; footer!: Footer; @@ -358,6 +360,11 @@ export class UAIEditor { this.center.appendChild(this.tocContainer); this.toggleContainers.push(this.tocContainer); + this.arxivContainer = new ArxivContainer(); + this.arxivContainer.style.display = "none"; + this.center.appendChild(this.arxivContainer); + this.toggleContainers.push(this.arxivContainer); + this.chatContainer = new ChatContainer(); this.chatContainer.style.display = "none"; this.center.appendChild(this.chatContainer); @@ -430,6 +437,7 @@ export class UAIEditor { } this.header.onCreate(event, this.options); this.tocContainer.onCreate(event, this.options); + this.arxivContainer.onCreate(event, this.options); this.chatContainer.onCreate(event, this.options); this.imageContainer.onCreate(event, this.options); this.editor.onCreate(event, this.options); @@ -442,6 +450,7 @@ export class UAIEditor { protected onTransaction(event: EditorEvents['transaction']) { this.header.onTransaction(event, this.options); this.tocContainer.onTransaction(event, this.options); + this.arxivContainer.onTransaction(event, this.options); this.chatContainer.onTransaction(event, this.options); this.imageContainer.onTransaction(event, this.options); this.editor.onTransaction(event, this.options); diff --git a/src/extensions/File.ts b/src/extensions/File.ts index 360bc6b..277290a 100644 --- a/src/extensions/File.ts +++ b/src/extensions/File.ts @@ -138,7 +138,7 @@ export default Node.create({ const body = document.createElement('div'); body.classList.add("uai-file-preview-modal-body"); const iframe = document.createElement('iframe'); - iframe.src = URL.createObjectURL(base64ToBlob(attrs.url, attrs.previewType)); + iframe.src = attrs.url.startsWith("data") ? URL.createObjectURL(base64ToBlob(attrs.url, attrs.previewType)) : attrs.url; body.appendChild(iframe); modal.modalBody.appendChild(body); modal.show(); diff --git a/src/i18n/zh.ts b/src/i18n/zh.ts index eab0232..cec649d 100644 --- a/src/i18n/zh.ts +++ b/src/i18n/zh.ts @@ -271,6 +271,10 @@ export const zh = { "edit": "编辑流程图", "loading": "加载中..." }, + "arxiv": { + "text": "论文", + "title": "论文搜索", + }, "mermaid": { "text": "Mermaid", "edit": "编辑 Mermaid", diff --git a/src/styles/uaieditor.less b/src/styles/uaieditor.less index e4c776f..acd8846 100644 --- a/src/styles/uaieditor.less +++ b/src/styles/uaieditor.less @@ -1054,7 +1054,7 @@ border-bottom: solid 1px var(--uai-border-color); .uai-model-select { - height: 24px; + height: 32px; font-size: large; }