195 lines
9.3 KiB
TypeScript
195 lines
9.3 KiB
TypeScript
// Copyright (c) 2024-present AI-Labs
|
|
|
|
// @ ts-nocheck
|
|
import { Extension } from '@tiptap/core';
|
|
|
|
import Suggestion, { SuggestionOptions, SuggestionProps } from '@tiptap/suggestion';
|
|
import tippy, { Instance } from 'tippy.js';
|
|
import { InnerEditor } from '../core/UAIEditor.ts';
|
|
import { AICommand } from '../ai/config/AIConfig.ts';
|
|
import OpenAI from "openai";
|
|
import { markdownToHtml } from '../utils/MarkdownUtil.ts';
|
|
import { uuid } from '../utils/UUID.ts';
|
|
|
|
export type QuickCommandOptions = {
|
|
HTMLAttributes?: Record<string, any>;
|
|
suggestion: Omit<SuggestionOptions, 'editor'>;
|
|
}
|
|
|
|
/**
|
|
* 定义快捷命令
|
|
*/
|
|
export default Extension.create<QuickCommandOptions>({
|
|
name: 'quickCommand',
|
|
addOptions() {
|
|
return {
|
|
suggestion: {
|
|
char: '/',
|
|
command: ({ editor, range, props }) => {
|
|
editor.chain().focus().deleteRange(range).run();
|
|
const from = editor.state.selection.to;
|
|
const type = props.type;
|
|
const command = props.command as AICommand;
|
|
const options = (editor as InnerEditor).uaiEditor.options;
|
|
const actionKey = "uai_waiting_action";
|
|
|
|
const selectedText = editor.state.selection.$head.parent.textContent;
|
|
if (type === "chat") {
|
|
// 模型对话命令
|
|
var models = Object.keys(options.ai?.chat?.models ?? {});
|
|
if (models.length > 0) {
|
|
var model = command.model ?? "auto";
|
|
if (model === "auto") {
|
|
model = models[0];
|
|
}
|
|
const modelConfig = options.ai!.chat!.models![model];
|
|
const client = new OpenAI({
|
|
baseURL: modelConfig.baseUrl,
|
|
apiKey: modelConfig.apiKey,
|
|
dangerouslyAllowBrowser: true,
|
|
});
|
|
|
|
client.chat.completions.create({
|
|
model: modelConfig.model ?? "o1",
|
|
stream: true,
|
|
max_tokens: modelConfig.max_tokens,
|
|
temperature: modelConfig.temperature,
|
|
top_p: modelConfig.top_p,
|
|
frequency_penalty: modelConfig.frequency_penalty,
|
|
messages: [
|
|
{ "role": "system", "content": command.prompt ?? "你是一个很有帮助的人工智能助手。" },
|
|
{ "role": "user", "content": selectedText }
|
|
],
|
|
}).then(async response => {
|
|
for await (var chunk of response) {
|
|
var content = chunk.choices[0]?.delta?.content || '';
|
|
editor.view.dispatch(editor.state.tr.insertText(content));
|
|
}
|
|
const end = editor.state.selection.to;
|
|
const insertedText = editor.state.doc.textBetween(from, end);
|
|
editor.view.dispatch(editor.state.tr.replaceWith(from, end, (editor as InnerEditor).parseHtml(markdownToHtml(insertedText))).scrollIntoView());
|
|
});
|
|
}
|
|
} else if (type === "text2image") {
|
|
// 文生图命令
|
|
var models = Object.keys(options.ai?.image?.models?.text2image ?? {});
|
|
const id = uuid()
|
|
if (models.length > 0) {
|
|
var model = command.model ?? "auto";
|
|
if (model === "auto") {
|
|
model = models[0];
|
|
}
|
|
const modelConfig = options.ai!.image!.models!.text2image![model];
|
|
const client = new OpenAI({
|
|
baseURL: modelConfig.baseUrl,
|
|
apiKey: modelConfig.apiKey,
|
|
dangerouslyAllowBrowser: true,
|
|
});
|
|
editor.view.dispatch(editor.state.tr.setMeta(actionKey, {
|
|
type: "add",
|
|
id,
|
|
pos: editor.state.tr.selection.from,
|
|
}));
|
|
client.images.generate({
|
|
"model": modelConfig.model ?? "dall-e-3",
|
|
"prompt": `${command.prompt ?? ""}${selectedText}`,
|
|
"size": "1024x1024",
|
|
}).then(response => {
|
|
editor.view.dispatch(editor.state.tr.setMeta(actionKey, { type: "remove", id }));
|
|
const previewType = "image";
|
|
const type = "image";
|
|
|
|
editor.commands.insertContentAt(editor.state.tr.selection.from, {
|
|
type: type,
|
|
attrs: {
|
|
['src']: response.data[0].url,
|
|
type,
|
|
previewType,
|
|
},
|
|
});
|
|
});
|
|
}
|
|
}
|
|
},
|
|
render: () => {
|
|
let container: HTMLElement;
|
|
let popup: Instance;
|
|
|
|
return {
|
|
onStart: (props: SuggestionProps) => {
|
|
container = document.createElement("div");
|
|
container.classList.add("uai-popup-action-list");
|
|
(props.editor as InnerEditor).uaiEditor.options.ai?.chat?.commands?.forEach(command => {
|
|
const item = document.createElement("div");
|
|
item.classList.add("uai-popup-action-item");
|
|
item.innerHTML = `${command.icon} ${command.name}`;
|
|
item.addEventListener('click', () => {
|
|
props.command({
|
|
type: "chat",
|
|
command: command
|
|
})
|
|
});
|
|
container.appendChild(item);
|
|
})
|
|
|
|
container.appendChild(document.createElement("hr"));
|
|
|
|
(props.editor as InnerEditor).uaiEditor.options.ai?.image?.commands?.forEach(command => {
|
|
const item = document.createElement("div");
|
|
item.classList.add("uai-popup-action-item");
|
|
item.innerHTML = `${command.icon} ${command.name}`;
|
|
item.addEventListener('click', () => {
|
|
props.command({
|
|
type: "text2image",
|
|
command: command
|
|
})
|
|
});
|
|
container.appendChild(item);
|
|
})
|
|
|
|
// @ts-ignore
|
|
popup = tippy('body', {
|
|
appendTo: props.editor.options.element,
|
|
getReferenceClientRect: props.clientRect,
|
|
content: container,
|
|
showOnCreate: true,
|
|
interactive: true,
|
|
allowHTML: true,
|
|
trigger: 'manual',
|
|
placement: 'right',
|
|
arrow: false,
|
|
})[0]
|
|
},
|
|
onUpdate(props) {
|
|
if (!props.clientRect) {
|
|
return;
|
|
}
|
|
popup.setProps({
|
|
getReferenceClientRect: props.clientRect as any,
|
|
})
|
|
},
|
|
onKeyDown(props) {
|
|
if (props.event.key === 'Escape') {
|
|
popup.hide();
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
onExit() {
|
|
popup.hide();
|
|
container.remove();
|
|
},
|
|
}
|
|
},
|
|
}
|
|
}
|
|
},
|
|
addProseMirrorPlugins() {
|
|
return [
|
|
Suggestion({
|
|
editor: this.editor,
|
|
...this.options.suggestion,
|
|
}),
|
|
]
|
|
},
|
|
}) |