Joey

Wie ich TMDX erstellt habe

Dieser Artikel stellt dar, wie ich die Idee zu TMDX hatte und wie ich diese Idee umgesetzt habe.

Wie ich TMDX erstellt habe

Ursprung

Ich schreibe schon seit langer Zeit Blogbeiträge. Anfangs habe ich Jekyll verwendet, später dann Gatsby. Diese statischen Site-Generatoren sind jedoch ziemlich umständlich zu bedienen. Man muss Git konfigurieren, lernen, wie man baut, und das Hochladen von Bildern ist mühsam, da man die Bilder manuell in den Bildordner kopieren muss. Also bin ich zu Medium gewechselt, aber die Schreibumgebung von Medium war immer noch nicht optimal. Es unterstützt kein Markdown, keine Code-Highlighting und die Benutzeroberfläche ist nicht einfach genug. Ich habe ständig nach einer besseren Plattform gesucht.

Im Juli habe ich ein Blog-Spenden-Plugin namens TonTip erstellt. Es sollte Webseitenbetreibern helfen, Einnahmen durch Blockchain-Spenden zu generieren. Nach einigen Versuchen, es zu vermarkten, stellte ich jedoch fest, dass es wenig Resonanz gab. Daher beschloss ich, eine eigene Blogging-Plattform zu erstellen, die meinen Anforderungen besser entspricht und auf der ich alle gewünschten Plugins integrieren kann.

Beginn

Es gibt viele Tutorials zum Erstellen von persönlichen Blogs im Internet. Ich habe einige davon gelesen und hatte eine grundlegende technische Vorstellung. Ich entschied mich für:

  1. Svelte + SvelteKit
  2. Bun + TypeScript
  3. Cloudflare
  4. TailwindCSS
  5. DaisyUI
  6. Shiki
  7. Markdown-it
  8. Monaco Editor
  9. MathJax

Svelte wurde gewählt, weil dieses Framework Vorlagencode in HTML und JS kompilieren kann und unnötige Laufzeitabhängigkeiten eliminiert. Bun wurde gewählt, weil es ein kürzlich sehr beliebter JS-Laufzeitumgebung ist. TailwindCSS ist ein einfaches Atom-Framework, mit dem auch Anfänger schnell schöne Oberflächen erstellen können. DaisyUI ist eine TailwindCSS-UI-Komponentenbibliothek, mit der viele grundlegende Komponenten nicht selbst geschrieben werden müssen. Markdown-it wird zum Kompilieren von Markdown verwendet, alternativ könnte man auch RemarkJS verwenden, aber ich entschied mich für Markdown-it, weil es einfacher ist. Shiki ist eine Bibliothek für die Syntaxhervorhebung von Code, mit der man schönen Code in Markdown einfügen kann. MathJax ist eine Mathematikbibliothek, die benötigt wird, wenn man LaTeX-Formeln bearbeiten möchte. Das wichtigste Teil ist der Editor, der auf Monaco basiert, einem von Microsoft entwickelten Open-Source-Editor, der in den Browser eingebettet werden kann und eine hohe Erweiterbarkeit bietet.

Produktdesign

Die Idee des Produktdesigns war einfach. Ich habe viele Blogs anderer Leute studiert, um meine Blog-Oberfläche zu gestalten. Für den Editor habe ich mich stark an GitHub Copilot orientiert. Ich bin Abonnent von GitHub Copilot, einem Plugin, das qualitativ hochwertigen Code ergänzt und für Programmierer ein großer Produktivitätsgewinn ist. Ich wollte, dass mein Editor ähnlich funktioniert, indem er intelligente Code-Vervollständigungen und Vorschläge bietet, um nicht nur die Schreibgeschwindigkeit zu erhöhen, sondern auch den Inhalt professioneller und präziser zu machen.

Hier ist die Bearbeitungsoberfläche von VSCode + GitHub Copilot:

image.png

Und hier die Bearbeitungsoberfläche von TMDX:

image.png

Die Gesamtfunktionalität und das Design sind ähnlich.

Implementierung der Bildbibliothek

Die Bildbibliothek ist für die Bearbeitungserfahrung sehr wichtig. Wir wollen nicht, dass man während der Bearbeitung noch lokale Ordner öffnet, um nach Bildern zu suchen und hochzuladen. Daher habe ich eine integrierte Bild-Upload- und -Verwaltungsfunktion eingebaut. Benutzer können Bilder direkt in der Bildbibliothek hochladen, die Bilder werden automatisch in die Cloud gespeichert und ein Bildlink in den Artikel eingefügt. Dies vereinfacht den Arbeitsablauf und gewährleistet eine einheitliche Verwaltung und schnellen Zugriff auf Bilder.

Hier ist die Oberfläche der Bildbibliothek:

image.png

Unterstützt wird das Hochladen per Mausklick, Strg + V zum Einfügen und das Ziehen von Dateien in die Bildbibliothek zum Auslösen des Uploads.

Manchmal möchte man nicht über die Bildbibliothek Bilder hochladen, daher habe ich auch Ereignisse im Editor überwacht, um das Einfügen und Ziehen von Dateien direkt im Editor zu ermöglichen.

editor.getDomNode()?.addEventListener("paste", handlePaste);
editor.getDomNode()?.addEventListener("dragover", handleDragOver);
editor.getDomNode()?.addEventListener("drop", handleDrop);

Implementierung der AI-Konversation

Seit der Veröffentlichung von ChatGPT im Jahr 2023 gibt es mittlerweile viele Dialogroboter, und die Kosten für die Schnittstellen sind stark gesunken. Die Implementierung einer Dialogoberfläche ist einfach: In einem Container werden Fragen und Antworten abwechselnd angezeigt, und unten gibt es ein Textfeld zum Eingeben und Senden von Fragen. Da die Antwortgeschwindigkeit der Chat-Schnittstelle langsam ist und die Antwort tokenweise zurückgegeben wird, habe ich während der Entwicklung der Oberfläche eine bequeme Funktion von Svelte namens rune verwendet. Mit rune kann man reaktive Programmierung durchführen, sodass jeder zurückgegebene Token sofort auf der Oberfläche angezeigt wird.

Dieser Screenshot zeigt, wie ich reaktive Programmierung und die SSE-Funktion der HTTP-Schnittstelle verwendet habe, um messages zu aktualisieren und dann die Oberfläche zu aktualisieren.

image.png

Ich habe zunächst nur das Modell Deepseek integriert, später habe ich auch einige andere Modelle wie ChatGLM ausprobiert und festgestellt, dass sie auch gut funktionieren, also habe ich sie auch integriert. Jetzt unterstützte Modelle sind:

  1. Deepseek: Nur Textkonversation
  2. GLM: Nur Textkonversation
  3. Flux: Nur Text-zu-Bild-Generierung
  4. Cogview: Text-zu-Bild-Generierung
  5. Cogvideox: Text-zu-Video-Generierung

Was die Text-zu-Bild-Generierung betrifft, benutze ich sie persönlich gerne, um Titelbilder für meine Blogbeiträge zu generieren, damit ich qualitativ hochwertige Titelbilder erstellen kann, ohne manuell zu designen oder nach Bildern zu suchen.

Zum Beispiel habe ich Flux mit folgendem Prompt aufgefordert, ein Bild zu generieren:

"blue sky, white cloud"

Es hat das folgende Bild zurückgegeben:

blue sky, white cloud

Ich kopiere einfach den Link und setze ihn als cover-Feld in der Frontmatter.

Implementierung der AI-Vervollständigung

Die AI-Vervollständigung ist eine technisch anspruchsvolle Aufgabe. Ich erinnere mich noch an das Jahr 2019, als TabNine gerade auf den Markt kam und viel Lob und Erstaunen hervorrief. Dann kam GitHub Copilot, aber damals war die LLM-Technologie noch nicht so weit entwickelt, und die Vervollständigungen von Copilot waren nicht sehr genau. Bis 2023, als ChatGPT aufkam, stellten die Leute fest, dass man große Modelle zur Code-Vervollständigung verwenden konnte. Daher aktualisierte Microsoft schnell GitHub Copilot und hat es seitdem ständig weiterentwickelt, bis die Vervollständigung heute sehr gut funktioniert. Ich beschloss, die AI-Vervollständigung auch in meinem Editor zu integrieren, damit Benutzer beim Schreiben eine ähnliche intelligente Vervollständigung wie bei GitHub Copilot erleben können. Um dies zu erreichen, habe ich das Deepseek-Modell als Backend-Unterstützung gewählt, da es in der Verarbeitung natürlicher Sprache und der Code-Vervollständigung hervorragende Leistungen zeigt. Durch API-Aufrufe kann der Editor in Echtzeit Vervollständigungsvorschläge erhalten und diese im Editor anzeigen, und der Benutzer muss nur die Tab-Taste drücken, um den Vorschlag zu akzeptieren.

Monaco Editor bietet eine Schnittstelle für Vervollständigungen, die wir nur implementieren müssen, um die gewünschten Vervollständigungen zu erhalten.

monaco.languages.registerInlineCompletionsProvider(
    "markdown",
    {
        provideInlineCompletions: async (
            model,
            position,
            context,
            token,
        ) => {
            // generate completions...
        }
    }
);

Die Herausforderung besteht darin, Vervollständigungen schnell, effizient und präzise zu generieren. Zu diesem Zweck habe ich die Implementierungen vieler anderer Leute studiert, darunter:

  1. https://spencerporter2.medium.com/building-copilot-on-the-web-f090ceb9b20b
  2. https://github.com/arshad-yaseen/monacopilot
  3. https://sourcegraph.com/blog/the-lifecycle-of-a-code-ai-completion

Im Moment verwende ich die einfachste Methode: Ich sende regelmäßig Vervollständigungsanfragen an die AI und speichere den Inhalt zwischen, um ihn bei Bedarf dem Benutzer anzuzeigen.

Ich habe eine Tabelle mit den Vervollständigungszeiten, hier ist ein Screenshot einiger Einträge:

image.png

Die dritte Spalte zeigt die Länge des generierten Vervollständigungsinhalts, die vierte Spalte die Zeit, die die Schnittstelle benötigt hat. Im Allgemeinen wird innerhalb von 5 Sekunden eine Antwort zurückgegeben, was für Inhaltsersteller akzeptabel ist, aber es gibt noch Optimierungspotenzial.

Ich habe nicht speziell die Qualität der Vervollständigungsinhalte oder die Akzeptanz durch Benutzer statistisch erfasst. Während der Bearbeitung dieses Artikels habe ich einige Vervollständigungsergebnisse aufgenommen, und ich persönlich finde sie gut.

image.png

image.png

image.png

image.png

Implementierung der AI-Suche

Ich habe ein Suchfeld in der Navigationsleiste platziert:

image.png

Im Gegensatz zu traditionellen Suchen, die Elasticsearch oder Algolia verwenden, habe ich eine AI-Suchfunktion basierend auf der von Cloudflare AI bereitgestellten Vectorize-Vektordatenbank implementiert. Jedes Mal, wenn ein Benutzer einen Artikel veröffentlicht, wird die AI den Artikel analysieren, Embeddings generieren und in der Vektordatenbank speichern.

Discussion (0)