Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 29 additions & 174 deletions client/package-lock.json

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@
"dependencies": {
"@hello-pangea/dnd": "18.0.1",
"@iconify/react": "6.0.2",
"@openrouter/ai-sdk-provider": "1.2.0",
"@redux-devtools/extension": "3.3.0",
"@tailwindcss/vite": "4.1.11",
"@tinymce/tinymce-react": "6.3.0",
"ai": "5.0.54",
"animejs": "4.2.1",
"axios": "1.12.1",
"clsx": "2.1.1",
Expand Down
65 changes: 30 additions & 35 deletions client/src/hooks/useAI.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useRef, useState } from "react";
import { openrouter } from "../lib/openrouter";
import { streamText } from "ai";
import { formattedToHTML } from "../utils/formatted";
import { generateDescription } from "../pages/boards/service";
import { useTranslation } from "react-i18next";
import { formattedToHTML } from "../utils/formatted";
import axios from "axios";

export const useAI = () => {
const { t } = useTranslation();
Expand All @@ -14,42 +14,38 @@ export const useAI = () => {
onChunk?: (chunk: string) => void,
): Promise<string | null> => {
abortAction.current = new AbortController();
const signal = abortAction.current?.signal;

setLoading(true);
setError(null);

const signal = abortAction.current.signal;

let description = "";

try {
const { textStream } = streamText({
model: openrouter("google/gemma-3n-e2b-it:free"),
prompt: t("boardModal.AI.prompt", {
title,
}),
abortSignal: signal,
});

let buffer = "";
for await (const chunk of textStream) {
description += chunk;
buffer += chunk;
if (buffer.length >= 30) {
const formatted = formattedToHTML(description);
onChunk?.(formatted); // descripcion en tiempo real
buffer = "";
}
}
if (buffer) {
const formatted = formattedToHTML(description);
onChunk?.(formatted);
}
const description = await generateDescription(title, signal);
// let buffer = "";
// for await (const chunk of textStream) {
// description += chunk;
// buffer += chunk;
// if (buffer.length >= 30) {
// const formatted = formattedToHTML(description);
// onChunk?.(formatted); // descripcion en tiempo real
// buffer = "";
// }
// }
// if (buffer) {
// const formatted = formattedToHTML(description);
// onChunk?.(formatted);
// }
// return description;
onChunk?.(formattedToHTML(description));
return description;
} catch (err: unknown) {
if (err instanceof Error) {
setError(err.message || t("boardModal.AI.error"));
if (axios.isCancel(err)) {
setError(t("boardModal.AI.canceled"));
} else if (err instanceof Error) {
if (err.message.includes("limit") || err.message.includes("quota")) {
setError(err.message || t("boardModal.AI.maxPetitions"));
setError(t("boardModal.AI.maxPetitions"));
} else {
setError(err.message || t("boardModal.AI.error"));
}
}
return null;
Expand All @@ -58,15 +54,14 @@ export const useAI = () => {
abortAction.current = null;
}
};

const stopGenerationDescription = () => {
abortAction.current?.abort();
setLoading(false);
};

return {
generateDescriptionFromTitle,
stopGenerationDescription,
loading,
error,
stopGenerationDescription,
};
};
5 changes: 0 additions & 5 deletions client/src/lib/openrouter.ts

This file was deleted.

13 changes: 13 additions & 0 deletions client/src/pages/boards/service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { apiClient } from "../../api/client";
import {
AI_ENDPOINT,
BOARD_ENDPOINTS,
CARD_ENDPOINT,
LABEL_ENDPOINTS,
Expand Down Expand Up @@ -147,6 +148,18 @@ export const removeAssignee = async (
);
};

export const generateDescription = async (
title: string,
signal?: AbortSignal,
) => {
const response = await apiClient.post<{ description: string }>(
AI_ENDPOINT.GENERATE,
{ title },
{ signal },
);
return response.data.description;
};

export const getBoardLabels = async (
boardId: string | number,
): Promise<Label[]> => {
Expand Down
5 changes: 5 additions & 0 deletions client/src/utils/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const USER_ENDPOINTS = {
BY_ID: byId("profile"),
RESET_PASSWORD: `/api/${VERSION}/auth/reset_password`,
CHANGE_PASSWORD: `/api/${VERSION}/auth/change_password`,
AI_GENERATE: `/api/${VERSION}/ai/generate-description`,
};

export const BOARD_ENDPOINTS = {
Expand All @@ -40,6 +41,10 @@ export const CARD_ENDPOINT = {
BY_ID: byId("cards"),
};

export const AI_ENDPOINT = {
GENERATE: `${USER_ENDPOINTS.AI_GENERATE}`,
};

export const getAccountEndpoint = (typeAccount: TypeAccount): string | null => {
if (!TypeAccountEnum.options.includes(typeAccount)) {
throw new Error("Not valid account.");
Expand Down
Loading