diff --git a/web3/packages/tiptap/src/components/AppHeader.vue b/web3/packages/tiptap/src/components/AppHeader.vue new file mode 100644 index 00000000..56825a6f --- /dev/null +++ b/web3/packages/tiptap/src/components/AppHeader.vue @@ -0,0 +1,70 @@ + + + diff --git a/web3/packages/tiptap/src/components/EditorActions.vue b/web3/packages/tiptap/src/components/EditorActions.vue new file mode 100644 index 00000000..f0cf6cdc --- /dev/null +++ b/web3/packages/tiptap/src/components/EditorActions.vue @@ -0,0 +1,58 @@ + + + diff --git a/web3/packages/tiptap/src/components/HtmlOutput.vue b/web3/packages/tiptap/src/components/HtmlOutput.vue new file mode 100644 index 00000000..ac772f67 --- /dev/null +++ b/web3/packages/tiptap/src/components/HtmlOutput.vue @@ -0,0 +1,14 @@ + + + diff --git a/web3/packages/tiptap/src/components/useEditorConfig.ts b/web3/packages/tiptap/src/components/useEditorConfig.ts new file mode 100644 index 00000000..452806d9 --- /dev/null +++ b/web3/packages/tiptap/src/components/useEditorConfig.ts @@ -0,0 +1,241 @@ +import { computed } from "vue"; +import { + Bold, + BulletList, + Italic, + BaseKit, + Underline, + Strike, + LineHeight, + Image, + History, + Heading, + CodeBlock, + FontSize, + Highlight, + Table, + Clear, + Blockquote, + Link, + Color, + Video, + OrderedList, + HorizontalRule, + Fullscreen, + TaskList, + MoreMark, + FormatPainter, + SlashCommand, + Indent, + ImportWord, + Columns, + TextAlign, + ImageUpload, + VideoUpload, + FontFamily, + FindAndReplace, + Code, + AI, + Preview, + Printer, + Iframe, + SpecialCharacter, + SourceCode, +} from "echo-editor"; +import { ExportWord } from "./ExportWord"; +import OpenAI from "openai"; + +export function useEditorConfig(minimal: boolean) { + const extensions = computed(() => + minimal ? minimalExtensions : fullExtensions + ); + + const minimalExtensions = [ + BaseKit.configure({ + characterCount: { + limit: 50000, + }, + }), + Heading, + Bold.configure({ spacer: true }), + Italic, + Underline, + HorizontalRule, + TextAlign.configure({ + types: ["heading", "paragraph", "image"], + spacer: true, + }), + Image, + Blockquote.configure({ spacer: true }), + Code, + Link, + Color, + TaskList.configure({ spacer: true }), + OrderedList, + BulletList, + ]; + const fullExtensions = [ + BaseKit.configure({ + placeholder: { + showOnlyCurrent: true, + }, + characterCount: { + limit: 50000, + }, + }), + History, + Columns, + FormatPainter, + Clear, + Heading.configure({ spacer: true }), + FontSize, + FontFamily, + Bold, + Italic, + Underline, + Strike, + MoreMark, + Color.configure({ spacer: true }), + Highlight, + BulletList, + OrderedList, + TextAlign.configure({ + types: ["heading", "paragraph", "image"], + spacer: true, + }), + Indent, + LineHeight, + TaskList.configure({ + spacer: true, + taskItem: { + nested: true, + }, + }), + Link, + Image, + ImageUpload.configure({ + upload: (files: File) => { + return new Promise((resolve) => { + setTimeout(() => { + resolve(URL.createObjectURL(files)); + }, 3000); + }); + }, + }), + Video, + VideoUpload.configure({ + upload: handleFileUpload, + }), + Blockquote, + SlashCommand, + HorizontalRule, + CodeBlock, + Table.configure({ spacer: true }), + Code, + ExportWord, + AI.configure({ + completions: AICompletions, + shortcuts: [ + // 这里可以传入额外的自定义shortcuts + { + label: "Custom Actions", + children: [ + { + label: "This is Custom Action", + prompt: + "Rewrite this content with no spelling mistakes, proper grammar, and with more descriptive language, using best writing practices without losing the original meaning.", + }, + ], + }, + ], + }), + ImportWord.configure({ + upload: handleFileUpload, + }), + SpecialCharacter, + Fullscreen.configure({ spacer: true }), + SourceCode, + Preview, + FindAndReplace.configure({ spacer: true }), + Printer, + Iframe, + ]; + async function handleFileUpload(files: File[]) { + const f = files.map((file) => ({ + src: URL.createObjectURL(file), + alt: file.name, + })); + return Promise.resolve(f); + } + + async function AICompletions( + history: Array<{ role: string; content: string }> = [], + signal?: AbortSignal + ) { + // groq.com For free llm api recommend deepseek r1 70b + // SECURITY WARNING: API keys should never be exposed in the frontend + // This is just for demo purposes + // In production, implement this through your backend API + const apiKey = "import.meta.env.VITE_OPENAI_API_KEY"; + const baseURL = "import.meta.env.VITE_OPENAI_BASE_URL"; + const model = "import.meta.env.VITE_OPENAI_MODEL"; + + if (!apiKey || !baseURL || !model) { + throw new Error( + "OpenAI configuration is missing. Please check your environment variables." + ); + } + + const openai = new OpenAI({ + apiKey: apiKey, + dangerouslyAllowBrowser: true, + baseURL: baseURL, + }); + + const systemMsg = `You are a professional writing assistant. Please respond based on the user's context: + +1. Maintain a professional, accurate, and objective tone +2. Ensure responses are clear, coherent, and well-structured +3. Responses must be in HTML format, preserving all HTML tags, links, and styles +4. Support the following writing enhancements: + - Grammar and spelling corrections + - Improved sentence structure and expression + - Optimized article formatting and layout + - Maintain the core meaning of the original text +5. If context includes code, maintain code formatting and provide optimization suggestions +6. Add appropriate HTML elements like headings, lists, quotes etc. to enhance readability as needed + +Please respond only based on the provided context, do not add irrelevant information.`; + + const systemPrompt = [{ role: "system", content: systemMsg }]; + const finalMessages = [...systemPrompt]; + + if (history.length > 0) { + finalMessages.push(...history); + } + + try { + const stream = await openai.chat.completions.create( + { + model, + messages: finalMessages, + temperature: 0.7, + stream: true, + reasoning_format: "parsed", // groq deepseek r1 need this + } as any, + { signal } + ); + + return stream; + } catch (error) { + if (error instanceof Error) { + console.error("Error in AI Completions:", error.message); + } + throw error; + } + } + + return { + extensions, + }; +} diff --git a/web3/packages/tiptap/src/views/index.vue b/web3/packages/tiptap/src/views/index.vue index bd62f60b..b83e761b 100644 --- a/web3/packages/tiptap/src/views/index.vue +++ b/web3/packages/tiptap/src/views/index.vue @@ -1,109 +1,18 @@