tag-generator.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import axios from "axios";
  2. import config from "../config";
  3. import LogLevels from "../enums/log-levels";
  4. export default class TagGenerator {
  5. private readonly _name: string;
  6. private readonly _ollamaUrl: string;
  7. private readonly _ollamaModel: string;
  8. private readonly _ollamaTimeout: number;
  9. private readonly _enabled: boolean;
  10. constructor(name: string) {
  11. this._name = name;
  12. this._ollamaUrl = config.OLLAMA_URL;
  13. this._ollamaModel = config.OLLAMA_MODEL;
  14. this._ollamaTimeout = config.OLLAMA_TIMEOUT;
  15. this._enabled = config.OLLAMA_ENABLED;
  16. }
  17. private buildPrompt(title: string, content: string): string {
  18. return `A partir del siguiente título y contenido de una noticia de un medio digital chileno, sugiere como máximo 5 hashtags relevantes y concisos para publicar en Mastodon. Pueden ser menos de 3 si no aplica más.
  19. Reglas:
  20. - Responde ÚNICAMENTE con un array JSON de strings.
  21. - Los hashtags deben estar en español.
  22. - Sin espacios ni caracteres especiales dentro de cada hashtag.
  23. - Sin símbolo #.
  24. - Solo el array JSON, sin explicaciones.
  25. Título: ${title}
  26. Contenido: ${content}
  27. Respuesta:`;
  28. }
  29. private cleanTag(tag: string): string {
  30. return tag
  31. .normalize("NFD")
  32. .replace(/[\u0300-\u036f]/g, "")
  33. .replace(/[^a-zA-Z0-9]/g, "")
  34. .trim()
  35. .toLowerCase();
  36. }
  37. private parseResponse(responseText: string): string[] {
  38. const cleaned = responseText.trim();
  39. try {
  40. const parsed = JSON.parse(cleaned);
  41. if (Array.isArray(parsed)) {
  42. return parsed
  43. .slice(0, 5)
  44. .map((tag: unknown) => this.cleanTag(String(tag)))
  45. .filter((tag) => tag !== "");
  46. }
  47. } catch {
  48. // Intento extraer el primer array JSON que aparezca en la respuesta
  49. const match = cleaned.match(/\[[\s\S]*?\]/);
  50. if (match) {
  51. return this.parseResponse(match[0]);
  52. }
  53. }
  54. return [];
  55. }
  56. public async generate(title: string, content: string): Promise<string[]> {
  57. if (!this._enabled) {
  58. return [];
  59. }
  60. const trimmedTitle = title.trim();
  61. const trimmedContent = content.trim();
  62. if (trimmedTitle === "" && trimmedContent === "") {
  63. return [];
  64. }
  65. try {
  66. const response = await axios.post(
  67. this._ollamaUrl,
  68. {
  69. model: this._ollamaModel,
  70. prompt: this.buildPrompt(trimmedTitle, trimmedContent),
  71. stream: false
  72. },
  73. {
  74. timeout: this._ollamaTimeout,
  75. headers: { "Content-Type": "application/json" }
  76. }
  77. );
  78. const responseText = response.data?.response ?? "";
  79. const tags = this.parseResponse(responseText);
  80. if (config.LOG_LEVEL === LogLevels.DEBUG) {
  81. console.debug(`${this._name} | Tags generadas por Ollama`, tags);
  82. }
  83. return tags;
  84. } catch (err: any) {
  85. console.log(`${this._name} | No se pudieron generar tags con Ollama`);
  86. console.error(err.message);
  87. return [];
  88. }
  89. }
  90. }