Cómo creé TMDX
Este artículo explicará cómo surgió la idea de crear tmdx y cómo la implementé.

Origen
Hace mucho tiempo que comencé a escribir blogs, al principio usaba jekyll y luego gatsby, pero estos generadores de sitios estáticos eran muy complicados de usar, necesitabas configurar git, aprender a construir, subir imágenes era un dolor, tenías que copiar manualmente las imágenes a la carpeta de imágenes. Así que cambié a medium, pero la experiencia de escritura en medium siempre fue mala, no soportaba markdown, no soportaba resaltado de código, y la interfaz no era lo suficientemente limpia. Siempre estaba buscando una mejor plataforma de escritura.
En julio hice un plugin para donaciones de blogs llamado TonTip que ayudaba a los webmasters a obtener ingresos por donaciones en blockchain, pero después de promocionarlo descubrí que no tenía mucha respuesta, así que decidí construir una plataforma de blogs que se ajustara a mis necesidades, que pudiera integrar cualquier plugin que quisiera.
Comienzo
Hay muchos tutoriales en línea sobre cómo construir blogs personales, vi algunos y luego tuve una idea básica de la tecnología, decidí usar:
- svelte + sveltekit
- bun + typescript
- cloudflare
- tailwindcss
- daisyui
- shiki
- markdown-it
- monaco editor
- mathjax
Elegí svelte porque este framework puede compilar código de plantilla a html y js, y eliminar dependencias innecesarias en tiempo de ejecución, elegí bun porque es un runtime de js que ha estado muy popular últimamente, tailwindcss es un framework atómico muy simple, con él incluso los principiantes pueden construir interfaces bonitas rápidamente. daisyui es una biblioteca de componentes ui para tailwindcss, con ella muchos componentes básicos no necesitan ser escritos por uno mismo. markdown-it se usa para compilar markdown, también se podría elegir remarkjs, pero decidí usar markdown-it porque es más simple. shiki es una biblioteca para resaltar sintaxis de código, con ella se pueden insertar códigos bonitos en markdown. mathjax es una biblioteca matemática, si necesitas editar fórmulas latex, necesitas depender de esta biblioteca. Finalmente, y lo más importante, la parte del editor se basa en monaco, que es un editor de código abierto de Microsoft que se puede incrustar en el navegador y tiene una gran extensibilidad.
Diseño del producto
La idea del diseño del producto es bastante simple, me inspiré en muchos blogs de otras personas para diseñar la interfaz de mi blog. En cuanto a la parte del editor, me inspiré completamente en github copilot, soy un suscriptor de github copilot, este plugin ayuda a completar código de alta calidad, para los programadores es una gran herramienta de aumento de productividad. Quiero que mi editor también sea como GitHub Copilot, proporcionando sugerencias y completado de código inteligente, esto no solo puede aumentar la eficiencia de escritura, sino también hacer que el contenido sea más profesional y preciso.
Esta es la interfaz de edición de vscode + github copilot
Esta es la interfaz de edición de tmdx
La funcionalidad y el diseño de la interfaz son similares
Implementación de la galería de imágenes
La galería de imágenes es muy importante para la experiencia de edición, no queremos abrir la carpeta local para buscar imágenes y luego subirlas durante el proceso de edición. Así que integré una función de carga y gestión de imágenes. Los usuarios pueden subir imágenes directamente en la galería de imágenes, las imágenes se almacenarán automáticamente en la nube y se insertarán enlaces de imágenes en el artículo. Esto no solo simplifica el proceso de operación, sino también asegura una gestión unificada y un acceso rápido a las imágenes.
Esta es la interfaz de la galería de imágenes
Soporta subir archivos con un clic del ratón, soporta Ctrl + V para pegar y arrastrar archivos a la galería de imágenes para subir
A veces no quiero subir imágenes a través de la galería de imágenes, también escuché eventos del editor, implementé pegar y arrastrar para subir dentro del editor
editor.getDomNode()?.addEventListener("paste", handlePaste);
editor.getDomNode()?.addEventListener("dragover", handleDragOver);
editor.getDomNode()?.addEventListener("drop", handleDrop);
Implementación de la conversación AI
Desde que chatgpt salió en 23, ahora hay muchos robots de conversación, el costo de las interfaces también ha bajado mucho. Implementar una interfaz de conversación es muy simple, simplemente mostrar preguntas y respuestas alternativamente en un contenedor, y enviar preguntas editadas en un textarea en la parte inferior. Debido a que la velocidad de respuesta de las interfaces de chat es lenta y se devuelve token por token, en el proceso de hacer la interfaz usé una característica bastante conveniente de svelte llamada rune
, usando rune
para programación reactiva, de modo que cada vez que la interfaz devuelve un token adicional, se puede mostrar inmediatamente en la interfaz.
Esta captura de pantalla muestra cómo actualizo messages
y luego actualizo la interfaz mediante programación reactiva y la característica SSE de la interfaz http
Inicialmente solo integré un modelo de deepseek, luego experimenté con algunos otros modelos de otros fabricantes, como chatglm, y descubrí que también estaba bien, así que también los agregué, ahora los modelos soportados son:
- deepseek: solo soporta diálogo de texto
- glm: solo soporta diálogo de texto
- flux: solo soporta generación de imágenes a partir de texto
- cogview: generación de imágenes a partir de texto
- cogvideox: generación de video a partir de texto
En cuanto a la generación de imágenes a partir de texto, personalmente me gusta usarla para generar imágenes de portada de blogs, así puedo generar fácilmente imágenes de portada de alta calidad sin necesidad de diseñarlas o buscarlas manualmente.
Por ejemplo, le pedí a flux que generara una imagen con el siguiente prompt:
"blue sky, white cloud"
Devolvió la siguiente imagen,
Solo necesito copiar el enlace y establecerlo como el campo cover en frontmatter
Implementación del completado AI
El completado AI es algo con un cierto umbral técnico, todavía puedo recordar cuando tabnine apareció por primera vez en 2019, la alabanza y el asombro de todos. Luego llegó github copilot, pero en ese momento la tecnología llm no estaba desarrollada, el completado de copilot no era muy preciso, hasta 2023, cuando apareció chatgpt, la gente descubrió que se podía usar un gran modelo para lograr el completado de código, así que Microsoft rápidamente actualizó github copilot, y ha estado iterando hasta ahora que el completado es muy bueno. Decidí integrar la función de completado AI en mi editor, para que los usuarios puedan disfrutar de una experiencia de completado inteligente similar a GitHub Copilot al escribir. Para lograr esta función, elegí el modelo deepseek como soporte backend, porque tiene un excelente rendimiento en procesamiento de lenguaje natural y completado de código. A través de llamadas API, el editor puede obtener sugerencias de completado en tiempo real y mostrarlas en el editor, los usuarios solo necesitan presionar la tecla Tab para aceptar las sugerencias.
monaco editor proporciona interfaces para completado, solo necesitamos implementar estas interfaces para obtener el completado deseado
monaco.languages.registerInlineCompletionsProvider(
"markdown",
{
provideInlineCompletions: async (
model,
position,
context,
token,
) => {
// generate completions...
}
}
);
Cómo generar contenido de completado rápidamente, eficientemente y con precisión es el punto clave, para ello me inspiré en muchas implementaciones de otras personas, incluyendo
- https://spencerporter2.medium.com/building-copilot-on-the-web-f090ceb9b20b
- https://github.com/arshad-yaseen/monacopilot
- https://sourcegraph.com/blog/the-lifecycle-of-a-code-ai-completion
Actualmente estoy usando el método más simple, enviar solicitudes de completado a AI a intervalos regulares y luego almacenar en caché el contenido, mostrarlo al usuario cuando sea necesario.
Tengo una tabla que registra el tiempo de completado, esta es una captura de pantalla de algunas de las entradas
La tercera columna es la longitud del contenido generado, la cuarta columna es el tiempo de la interfaz, generalmente se devuelve en menos de 5s, para los creadores de contenido es aceptable, también hay espacio para optimizar en el futuro.
No he hecho un seguimiento especial del contenido de completado y la aceptación del usuario, mientras editaba este artículo, tomé algunas capturas de pantalla de los resultados de completado, personalmente creo que son buenos
Implementación de la búsqueda AI
Coloqué un cuadro de búsqueda en la barra de navegación
A diferencia de las búsquedas tradicionales que usan elasticsearch o algolia, basé la función de búsqueda AI en la base de datos vectorial vectorize proporcionada por cloud flare AI, cada vez que un usuario publica un artículo, se activa AI para analizar el artículo, generar embeddings y luego guardarlos en la base de datos vectorial, cuando el usuario realiza una búsqueda, se realiza mediante la comparación de similitud de vectores, en lugar de la coincidencia de palabras clave tradicional. Este método puede comprender mejor la intención de búsqueda del usuario y proporcionar resultados de búsqueda más relevantes.
Implementación de la traducción AI
No soy un hablante nativo de inglés, y mi inglés tampoco es muy bueno, pero quiero que mis artículos sean vistos y entendidos por personas de todo el mundo, así que decidí integrar una función de traducción AI en mi plataforma de blogs. De esta manera, los usuarios pueden elegir traducir sus artículos a varios idiomas al publicarlos, ampliando así el alcance de su audiencia. Para lograr esta función, inicialmente consideré usar google translate para traducir, pero después de usarlo descubrí que no podía entender el contexto, los resultados de traducción eran muy rígidos, luego probé algunas interfaces de traducción AI, finalmente decidí usar deepseek para implementar la traducción
const systemPrompt = `Eres un asistente de traducción AI, por favor traduce el archivo markdown proporcionado por el usuario a <lang>${lang}</lang>,
Requisitos:
1. Omite las imágenes, enlaces, citas, código en markdown
2. Si hay frontmatter, solo traduce los campos title, description
3. Si no se puede traducir al idioma objetivo, mantén el texto original
`;
const output = await aiClient.chat.completions.create({
model: 'deepseek-chat',
messages: [
{ "role": "system", "content": systemPrompt },
{ "role": "user", "content": mdContent }
],
stream: false,
});
¿Es simple, verdad?
Usar la traducción también es simple, en la parte superior del artículo hay una barra de selección de idioma, selecciona el idioma en el que deseas leer
Resumen
Actualmente he estado trabajando en TMDX durante 2 meses, pero las funciones todavía son bastante simples, en el futuro necesito seguir iterando, ¿qué opinan de mi idea?