|
|
@@ -0,0 +1,108 @@
|
|
|
+import axios from "axios";
|
|
|
+
|
|
|
+import config from "../config";
|
|
|
+import LogLevels from "../enums/log-levels";
|
|
|
+
|
|
|
+export default class TagGenerator {
|
|
|
+ private readonly _name: string;
|
|
|
+ private readonly _ollamaUrl: string;
|
|
|
+ private readonly _ollamaModel: string;
|
|
|
+ private readonly _ollamaTimeout: number;
|
|
|
+ private readonly _enabled: boolean;
|
|
|
+
|
|
|
+ constructor(name: string) {
|
|
|
+ this._name = name;
|
|
|
+ this._ollamaUrl = config.OLLAMA_URL;
|
|
|
+ this._ollamaModel = config.OLLAMA_MODEL;
|
|
|
+ this._ollamaTimeout = config.OLLAMA_TIMEOUT;
|
|
|
+ this._enabled = config.OLLAMA_ENABLED;
|
|
|
+ }
|
|
|
+
|
|
|
+ private buildPrompt(title: string, content: string): string {
|
|
|
+ return `A partir del siguiente título y contenido de una noticia de un medio digital chileno, sugiere exactamente 3 hashtags relevantes y concisos para publicar en Mastodon.
|
|
|
+
|
|
|
+Reglas:
|
|
|
+- Responde ÚNICAMENTE con un array JSON de strings.
|
|
|
+- Los hashtags deben estar en español.
|
|
|
+- Sin espacios ni caracteres especiales dentro de cada hashtag.
|
|
|
+- Sin símbolo #.
|
|
|
+- Solo el array JSON, sin explicaciones.
|
|
|
+
|
|
|
+Título: ${title}
|
|
|
+Contenido: ${content}
|
|
|
+
|
|
|
+Respuesta:`;
|
|
|
+ }
|
|
|
+
|
|
|
+ private cleanTag(tag: string): string {
|
|
|
+ return tag
|
|
|
+ .normalize("NFD")
|
|
|
+ .replace(/[\u0300-\u036f]/g, "")
|
|
|
+ .replace(/[^a-zA-Z0-9]/g, "")
|
|
|
+ .trim()
|
|
|
+ .toLowerCase();
|
|
|
+ }
|
|
|
+
|
|
|
+ private parseResponse(responseText: string): string[] {
|
|
|
+ const cleaned = responseText.trim();
|
|
|
+
|
|
|
+ try {
|
|
|
+ const parsed = JSON.parse(cleaned);
|
|
|
+ if (Array.isArray(parsed)) {
|
|
|
+ return parsed
|
|
|
+ .slice(0, 3)
|
|
|
+ .map((tag: unknown) => this.cleanTag(String(tag)))
|
|
|
+ .filter((tag) => tag !== "");
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ // Intento extraer el primer array JSON que aparezca en la respuesta
|
|
|
+ const match = cleaned.match(/\[[\s\S]*?\]/);
|
|
|
+ if (match) {
|
|
|
+ return this.parseResponse(match[0]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ public async generate(title: string, content: string): Promise<string[]> {
|
|
|
+ if (!this._enabled) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ const trimmedTitle = title.trim();
|
|
|
+ const trimmedContent = content.trim();
|
|
|
+
|
|
|
+ if (trimmedTitle === "" && trimmedContent === "") {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await axios.post(
|
|
|
+ this._ollamaUrl,
|
|
|
+ {
|
|
|
+ model: this._ollamaModel,
|
|
|
+ prompt: this.buildPrompt(trimmedTitle, trimmedContent),
|
|
|
+ stream: false
|
|
|
+ },
|
|
|
+ {
|
|
|
+ timeout: this._ollamaTimeout,
|
|
|
+ headers: { "Content-Type": "application/json" }
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ const responseText = response.data?.response ?? "";
|
|
|
+ const tags = this.parseResponse(responseText);
|
|
|
+
|
|
|
+ if (config.LOG_LEVEL === LogLevels.DEBUG) {
|
|
|
+ console.debug(`${this._name} | Tags generadas por Ollama`, tags);
|
|
|
+ }
|
|
|
+
|
|
|
+ return tags;
|
|
|
+ } catch (err: any) {
|
|
|
+ console.log(`${this._name} | No se pudieron generar tags con Ollama`);
|
|
|
+ console.error(err.message);
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|