From df368218fde4a3e173152d46c6bcfd727dff44e3 Mon Sep 17 00:00:00 2001 From: Gabriel Menezes Date: Fri, 6 Nov 2026 10:44:09 -0400 Subject: [PATCH] fix(tasks): corrigir resolucao do responsavel e extracao de externalUrl com IA --- .../controllers/task/ParsedTaskResponse.java | 1 + .../controllers/task/TaskController.java | 1 + .../task/ai/ParseTaskWithAiUseCase.java | 217 +++++++++++++++++- .../usecase/task/ai/ParsedTaskOutput.java | 1 + .../src/components/tasks/CreateTaskModal.tsx | 7 +- frontend/src/types/task.ts | 1 + vscode-extension/bee-tech-plugin-1.2.0.vsix | Bin 56736 -> 56768 bytes vscode-extension/src/api.ts | 2 + vscode-extension/src/commands.ts | 2 + vscode-extension/src/types.ts | 1 + 10 files changed, 219 insertions(+), 14 deletions(-) diff --git a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/ParsedTaskResponse.java b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/ParsedTaskResponse.java index 51cee3d..8c47e02 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/ParsedTaskResponse.java +++ b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/ParsedTaskResponse.java @@ -6,5 +6,6 @@ public record ParsedTaskResponse( Long projectId, Long categoryId, Long assigneeUserId, + String externalUrl, String status ) {} diff --git a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/TaskController.java b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/TaskController.java index 4e08b4d..3e1045c 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/TaskController.java +++ b/backend/src/main/java/net/beetechgroup/beetask/interfaceadapters/controllers/task/TaskController.java @@ -207,6 +207,7 @@ public class TaskController { output.projectId(), output.categoryId(), output.assigneeUserId(), + output.externalUrl(), output.status() ); } diff --git a/backend/src/main/java/net/beetechgroup/beetask/usecase/task/ai/ParseTaskWithAiUseCase.java b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/ai/ParseTaskWithAiUseCase.java index e175315..db3350f 100644 --- a/backend/src/main/java/net/beetechgroup/beetask/usecase/task/ai/ParseTaskWithAiUseCase.java +++ b/backend/src/main/java/net/beetechgroup/beetask/usecase/task/ai/ParseTaskWithAiUseCase.java @@ -13,14 +13,22 @@ import net.beetechgroup.beetask.usecase.repository.ProjectRepository; import net.beetechgroup.beetask.usecase.repository.UserOrganizationRepository; import org.jboss.logging.Logger; +import java.text.Normalizer; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; public class ParseTaskWithAiUseCase { private static final Logger LOGGER = Logger.getLogger(ParseTaskWithAiUseCase.class); + private static final Pattern URL_PATTERN = Pattern.compile( + "https?://[a-zA-Z0-9\\-._~:/?#\\[\\]@!$&'()*+,;=%]+", + Pattern.CASE_INSENSITIVE + ); + private final OllamaService ollamaService; private final ProjectRepository projectRepository; private final CategoryRepository categoryRepository; @@ -77,7 +85,8 @@ public class ParseTaskWithAiUseCase { promptBuilder.append(" \"description\": \"Descrição detalhada com escopo e checklist de critérios de aceitação se aplicável\",\n"); promptBuilder.append(" \"projectId\": ,\n"); promptBuilder.append(" \"categoryId\": ,\n"); - promptBuilder.append(" \"assigneeUserId\": \n"); + promptBuilder.append(" \"assigneeUserId\": ,\n"); + promptBuilder.append(" \"externalUrl\": \n"); promptBuilder.append("}\n\n"); promptBuilder.append("Contexto disponível na organização:\n"); @@ -99,7 +108,7 @@ public class ParseTaskWithAiUseCase { } } - promptBuilder.append("Membros disponíveis:\n"); + promptBuilder.append("Membros disponíveis (use o ID correspondente se o membro for citado pelo nome ou email):\n"); if (members.isEmpty()) { promptBuilder.append(" (Nenhum membro cadastrado)\n"); } else { @@ -114,9 +123,10 @@ public class ParseTaskWithAiUseCase { } promptBuilder.append("\nRegras mandatórias:\n"); - promptBuilder.append("1. Apenas retorne IDs numéricos que estejam explicitamente listados acima. Caso não encontre correspondência direta, use null.\n"); - promptBuilder.append("2. Se a instrução do usuário contiver itens ou passos, formate a descrição com marcadores Markdown ou critérios de aceite.\n"); - promptBuilder.append("3. Não inclua texto antes ou depois do JSON."); + promptBuilder.append("1. Apenas retorne IDs numéricos (projectId, categoryId, assigneeUserId) que estejam explicitamente listados acima. Se um membro for citado por nome ou email (mesmo primeiro nome), retorne o ID numérico desse membro. Caso não encontre correspondência direta, use null.\n"); + promptBuilder.append("2. Se houver qualquer link ou URL mencionado na instrução, capture a URL completa no campo externalUrl.\n"); + promptBuilder.append("3. Se a instrução do usuário contiver itens ou passos, formate a descrição com marcadores Markdown ou critérios de aceite.\n"); + promptBuilder.append("4. Não inclua texto antes ou depois do JSON."); String rawJson = ollamaService != null ? ollamaService.chat(promptBuilder.toString(), input.prompt(), true) @@ -143,11 +153,8 @@ public class ParseTaskWithAiUseCase { categoryId = null; } - Long assigneeUserId = node.hasNonNull("assigneeUserId") ? node.path("assigneeUserId").asLong() : null; - if (assigneeUserId != null && !validMemberUserIds.contains(assigneeUserId)) { - LOGGER.infof("AI sugeriu assigneeUserId %d inválido para a org, descartando...", assigneeUserId); - assigneeUserId = null; - } + Long assigneeUserId = resolveAssignee(node, input.prompt(), members, validMemberUserIds); + String externalUrl = extractExternalUrl(node, input.prompt()); return new ParsedTaskOutput( title, @@ -155,6 +162,7 @@ public class ParseTaskWithAiUseCase { projectId, categoryId, assigneeUserId, + externalUrl, "NOT_STARTED" ); } catch (Exception e) { @@ -168,8 +176,195 @@ public class ParseTaskWithAiUseCase { "", null, null, - null, + resolveAssignee(null, input.prompt(), members, validMemberUserIds), + extractExternalUrl(null, input.prompt()), "NOT_STARTED" ); } + + private Long resolveAssignee(JsonNode node, String prompt, List members, Set validMemberUserIds) { + // 1. Verificação direta de assigneeUserId + if (node != null && node.hasNonNull("assigneeUserId")) { + JsonNode aNode = node.path("assigneeUserId"); + if (aNode.isNumber()) { + long id = aNode.asLong(); + if (validMemberUserIds.contains(id)) { + return id; + } + } else if (aNode.isTextual()) { + String text = aNode.asText("").trim(); + try { + long id = Long.parseLong(text); + if (validMemberUserIds.contains(id)) { + return id; + } + } catch (NumberFormatException ignored) { + Long matched = matchMemberByNameOrEmail(text, members); + if (matched != null) { + return matched; + } + } + } + } + + // 2. Verificação de campos alternativos que a IA possa ter retornado + if (node != null) { + for (String field : List.of("assignee", "assigneeName", "responsible", "user")) { + if (node.hasNonNull(field)) { + String name = node.path(field).asText("").trim(); + if (!name.isBlank()) { + Long matched = matchMemberByNameOrEmail(name, members); + if (matched != null) { + return matched; + } + } + } + } + } + + // 3. Fallback inteligente: buscar correspondência de membro diretamente no prompt + return matchMemberInText(prompt, members); + } + + private Long matchMemberByNameOrEmail(String text, List members) { + if (text == null || text.isBlank() || members == null) { + return null; + } + String cleanTarget = normalize(text); + + // Correspondência exata por e-mail ou prefixo do e-mail + for (UserOrganization m : members) { + if (m.getUser() == null || m.getUser().getId() == null) continue; + String email = m.getUser().getEmail(); + if (email != null && !email.isBlank()) { + String normEmail = normalize(email); + if (normEmail.equalsIgnoreCase(cleanTarget)) { + return m.getUser().getId(); + } + String username = normEmail.split("@")[0]; + if (username.equalsIgnoreCase(cleanTarget)) { + return m.getUser().getId(); + } + } + } + + // Correspondência exata por nome completo + for (UserOrganization m : members) { + if (m.getUser() == null || m.getUser().getId() == null) continue; + String name = m.getUser().getName(); + if (name != null && !name.isBlank()) { + String normName = normalize(name); + if (normName.equalsIgnoreCase(cleanTarget)) { + return m.getUser().getId(); + } + } + } + + // Correspondência por primeiro nome (se comprimento >= 3 e não ambíguo) + Long matchedId = null; + int matchCount = 0; + for (UserOrganization m : members) { + if (m.getUser() == null || m.getUser().getId() == null) continue; + String name = m.getUser().getName(); + if (name != null && !name.isBlank()) { + String normName = normalize(name); + String firstName = normName.split("\\s+")[0]; + if (firstName.equalsIgnoreCase(cleanTarget) && firstName.length() >= 3) { + matchedId = m.getUser().getId(); + matchCount++; + } + } + } + if (matchCount == 1) { + return matchedId; + } + + return null; + } + + private Long matchMemberInText(String prompt, List members) { + if (prompt == null || prompt.isBlank() || members == null) { + return null; + } + String normPrompt = normalize(prompt); + + // 1. Nomes completos primeiro (ordenados por tamanho decrescente) + List sortedMembers = members.stream() + .filter(m -> m.getUser() != null && m.getUser().getId() != null && m.getUser().getName() != null) + .sorted((a, b) -> Integer.compare(b.getUser().getName().length(), a.getUser().getName().length())) + .toList(); + + for (UserOrganization m : sortedMembers) { + String normName = normalize(m.getUser().getName()); + if (normName.length() >= 3 && containsWordOrPhrase(normPrompt, normName)) { + return m.getUser().getId(); + } + } + + // 2. E-mails citados no texto + for (UserOrganization m : members) { + if (m.getUser() == null || m.getUser().getId() == null) continue; + String email = m.getUser().getEmail(); + if (email != null && !email.isBlank()) { + String normEmail = normalize(email); + if (containsWordOrPhrase(normPrompt, normEmail)) { + return m.getUser().getId(); + } + } + } + + // 3. Primeiro nome no texto (não ambíguo) + Long matchedId = null; + int matchCount = 0; + for (UserOrganization m : members) { + if (m.getUser() == null || m.getUser().getId() == null) continue; + String name = m.getUser().getName(); + if (name != null && !name.isBlank()) { + String normName = normalize(name); + String firstName = normName.split("\\s+")[0]; + if (firstName.length() >= 3 && containsWordOrPhrase(normPrompt, firstName)) { + matchedId = m.getUser().getId(); + matchCount++; + } + } + } + if (matchCount == 1) { + return matchedId; + } + + return null; + } + + private String extractExternalUrl(JsonNode node, String prompt) { + if (node != null && node.hasNonNull("externalUrl")) { + String url = node.path("externalUrl").asText("").trim(); + if (!url.isBlank()) { + if (url.startsWith("http://") || url.startsWith("https://")) { + return url; + } else if (url.startsWith("www.") || url.contains(".com") || url.contains(".org") || url.contains(".dev") || url.contains(".io") || url.contains(".net")) { + return "https://" + url; + } + } + } + // Fallback: extrair URL diretamente do prompt via regex + if (prompt != null) { + Matcher matcher = URL_PATTERN.matcher(prompt); + if (matcher.find()) { + return matcher.group().replaceAll("[.,;)]+$", "").trim(); + } + } + return null; + } + + private boolean containsWordOrPhrase(String text, String target) { + String escaped = Pattern.quote(target); + Pattern p = Pattern.compile("(? = ({ if (parsed.projectId !== undefined) setProjectId(parsed.projectId) if (parsed.categoryId !== undefined) setCategoryId(parsed.categoryId) if (parsed.assigneeUserId !== undefined) setAssigneeUserId(parsed.assigneeUserId) + if (parsed.externalUrl !== undefined) setExternalUrl(parsed.externalUrl || '') setAiQuickPrompt('') } catch (err: unknown) { const msg = err instanceof Error ? err.message : 'Falha ao processar com IA.' @@ -179,7 +180,7 @@ export const CreateTaskModal: React.FC = ({ setProjectId(initialDraft.projectId) setCategoryId(initialDraft.categoryId) setAssigneeUserId(initialDraft.assigneeUserId) - setExternalUrl('') + setExternalUrl(initialDraft.externalUrl || '') setLocalHistory([]) setActiveTab('details') } else { @@ -224,7 +225,7 @@ export const CreateTaskModal: React.FC = ({ if (memRes.status === 'fulfilled') { const memberList = memRes.value || [] setMembers(memberList) - if (!initialTask && user?.email) { + if (!initialTask && !initialDraft?.assigneeUserId && user?.email) { const currentMember = memberList.find( (m) => m.userEmail?.toLowerCase() === user.email.toLowerCase() ) @@ -241,7 +242,7 @@ export const CreateTaskModal: React.FC = ({ } loadMetadata() - }, [isOpen, organizationId, initialStatus, initialTask, initialTab, user?.email]) + }, [isOpen, organizationId, initialStatus, initialTask, initialDraft, initialTab, user?.email]) // Total seconds calculated dynamically from localHistory const totalCalculatedSeconds = useMemo(() => { diff --git a/frontend/src/types/task.ts b/frontend/src/types/task.ts index e5946ec..54be3ac 100644 --- a/frontend/src/types/task.ts +++ b/frontend/src/types/task.ts @@ -66,6 +66,7 @@ export interface ParsedAiTask { projectId?: number; categoryId?: number; assigneeUserId?: number; + externalUrl?: string; status?: TaskStatus; } diff --git a/vscode-extension/bee-tech-plugin-1.2.0.vsix b/vscode-extension/bee-tech-plugin-1.2.0.vsix index e05c52cd549e7f673bb771f0e5202027da82226c..4bfa55488d90046923976485474d6f097f24ce61 100644 GIT binary patch delta 4231 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 XCkFuN0RLaL!Ltb-7+yjI`mXms(-trl delta 4205 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 B3{n69 diff --git a/vscode-extension/src/api.ts b/vscode-extension/src/api.ts index 50aa23e..9971b6b 100644 --- a/vscode-extension/src/api.ts +++ b/vscode-extension/src/api.ts @@ -100,6 +100,8 @@ export class BeeTaskApiClient { organizationId: number; projectId?: number; categoryId?: number; + assigneeUserId?: number; + externalUrl?: string; }): Promise { return await this.request('/tasks', { method: 'POST', diff --git a/vscode-extension/src/commands.ts b/vscode-extension/src/commands.ts index eb19bf4..9448c08 100644 --- a/vscode-extension/src/commands.ts +++ b/vscode-extension/src/commands.ts @@ -360,6 +360,8 @@ export function registerCommands( organizationId: orgId, projectId: parsed.projectId, categoryId: parsed.categoryId, + assigneeUserId: parsed.assigneeUserId, + externalUrl: parsed.externalUrl, }); vscode.window.showInformationMessage(`✨ Tarefa #${created.id} criada via IA!`); diff --git a/vscode-extension/src/types.ts b/vscode-extension/src/types.ts index 5d027c9..1f10827 100644 --- a/vscode-extension/src/types.ts +++ b/vscode-extension/src/types.ts @@ -63,5 +63,6 @@ export interface ParsedAiTask { projectId?: number; categoryId?: number; assigneeUserId?: number; + externalUrl?: string; status?: TaskStatus; }