From 44d167394675708b6214033ffdfedc0088f7cdbe Mon Sep 17 00:00:00 2001 From: Giuseppe Scuglia Date: Wed, 22 Jan 2025 18:19:47 +0100 Subject: [PATCH 1/3] fix: parsing promptList text and breadcrumb --- src/components/PromptList.tsx | 19 +++--- src/components/__tests__/PromptList.test.tsx | 68 +++++++++++++++++--- src/lib/utils.ts | 40 ++++++++++-- src/routes/route-chat.tsx | 22 +++---- 4 files changed, 114 insertions(+), 35 deletions(-) diff --git a/src/components/PromptList.tsx b/src/components/PromptList.tsx index bbf04450..2e989ded 100644 --- a/src/components/PromptList.tsx +++ b/src/components/PromptList.tsx @@ -1,6 +1,6 @@ import { Link } from "react-router-dom"; import { - extractTitleFromMessage, + parsingPromptText, groupPromptsByRelativeDate, sanitizeQuestionPrompt, } from "@/lib/utils"; @@ -31,15 +31,14 @@ export function PromptList({ prompts }: { prompts: Conversation[] }) { { "font-bold": currentPromptId === prompt.chat_id }, )} > - {extractTitleFromMessage( - prompt.question_answers?.[0]?.question?.message - ? sanitizeQuestionPrompt({ - question: - prompt.question_answers?.[0].question.message, - answer: - prompt.question_answers?.[0]?.answer?.message ?? "", - }) - : `Prompt ${prompt.conversation_timestamp}`, + {parsingPromptText( + sanitizeQuestionPrompt({ + question: + prompt.question_answers?.[0]?.question.message ?? "", + answer: + prompt.question_answers?.[0]?.answer?.message ?? "", + }), + prompt.conversation_timestamp, )} diff --git a/src/components/__tests__/PromptList.test.tsx b/src/components/__tests__/PromptList.test.tsx index 189e90a5..41f14651 100644 --- a/src/components/__tests__/PromptList.test.tsx +++ b/src/components/__tests__/PromptList.test.tsx @@ -5,10 +5,51 @@ import mockedPrompts from "@/mocks/msw/fixtures/GET_MESSAGES.json"; import { render } from "@/lib/test-utils"; import { Conversation } from "@/api/generated"; +const conversationTimestamp = "2025-01-02T14:19:58.024100Z"; const prompt = mockedPrompts[0] as Conversation; +const testCases: [string, { message: string; expected: RegExp | string }][] = [ + [ + "codegate cmd", + { + message: "codegate workspace -h", + expected: /codegate workspace -h/i, + }, + ], + [ + "render code with path", + { + message: "// Path: src/lib/utils.ts", + expected: /Prompt on filepath: src\/lib\/utils.ts/i, + }, + ], + [ + "render code with file path", + { + message: " ```tsx // filepath: /tests/my-test.tsx import", + expected: /Prompt on file\/\/ filepath: \/tests\/my-test.tsx/i, + }, + ], + [ + "render snippet", + { + message: + 'Compare this snippet from src/test.ts: // import { fakePkg } from "fake-pkg";', + expected: /Prompt from snippet compare this snippet from src\/test.ts:/i, + }, + ], + [ + "render fallback", + { + message: + " ```Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", + expected: "Prompt 2025/01/03 - 10:09:33 AM", + }, + ], +]; + describe("PromptList", () => { - it("should render correct prompt", () => { + it("render prompt", () => { render(); expect( screen.getByRole("link", { @@ -17,17 +58,26 @@ describe("PromptList", () => { ).toBeVisible(); }); - it("should render default prompt value when missing question", async () => { - const conversationTimestamp = "2025-01-02T14:19:58.024100Z"; + it.each(testCases)("%s", (_title: string, { message, expected }) => { render( , @@ -35,7 +85,7 @@ describe("PromptList", () => { expect( screen.getByRole("link", { - name: `Prompt ${conversationTimestamp}`, + name: expected, }), ).toBeVisible(); }); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index e3f319c0..e282602d 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,14 +1,42 @@ import { AlertConversation, Conversation } from "@/api/generated/types.gen"; import { MaliciousPkgType, TriggerType } from "@/types"; -import { isToday, isYesterday } from "date-fns"; +import { format, isToday, isYesterday } from "date-fns"; const ONE_DAY_MS = 24 * 60 * 60 * 1000; const SEVEN_DAYS_MS = 7 * ONE_DAY_MS; const TEEN_DAYS_MS = 14 * ONE_DAY_MS; const THTY_DAYS_MS = 30 * ONE_DAY_MS; +const MAX_TEXT_LENGTH = 200; +const FILEPATH_REGEX = /(?:---FILEPATH|Path:|\/\/\s*filepath:)\s*([^\s]+)/g; +const COMPARE_CODE_REGEX = /Compare this snippet[^:]*:/g; -export function extractTitleFromMessage(message: string) { +function parsingByKeys(text: string | undefined, timestamp: string) { + const fallback = `Prompt ${format(new Date(timestamp ?? ""), "y/MM/dd - hh:mm:ss a")}`; try { + if (!text) return fallback; + const filePath = text.match(FILEPATH_REGEX); + const compareCode = text.match(COMPARE_CODE_REGEX); + if (compareCode || filePath) { + if (filePath) + return `Prompt on file${filePath[0]?.trim().toLocaleLowerCase()}`; + + if (compareCode) + return `Prompt from snippet ${compareCode[0]?.trim().toLocaleLowerCase()}`; + } + + if (text.length > MAX_TEXT_LENGTH) { + return fallback; + } + + return text.trim(); + } catch { + return fallback; + } +} + +export function parsingPromptText(message: string, timestamp: string) { + try { + // checking malformed markdown code blocks const regex = /^(.*)```[\s\S]*?```(.*)$/s; const match = message.match(regex); @@ -16,10 +44,10 @@ export function extractTitleFromMessage(message: string) { const beforeMarkdown = match[1]?.trim(); const afterMarkdown = match[2]?.trim(); const title = beforeMarkdown || afterMarkdown; - return title; + return parsingByKeys(title, timestamp); } - return message.trim(); + return parsingByKeys(message, timestamp); } catch { return message.trim(); } @@ -119,7 +147,9 @@ export function sanitizeQuestionPrompt({ }) { try { // it shouldn't be possible to receive the prompt answer without a question - if (!answer) return question; + if (!answer) { + throw new Error("Missing AI answer"); + } // Check if 'answer' is truthy; if so, try to find and return the text after "Query:" const index = question.indexOf("Query:"); diff --git a/src/routes/route-chat.tsx b/src/routes/route-chat.tsx index e0c6da82..4ed15a7e 100644 --- a/src/routes/route-chat.tsx +++ b/src/routes/route-chat.tsx @@ -1,6 +1,6 @@ import { useParams } from "react-router-dom"; import { usePromptsData } from "@/hooks/usePromptsData"; -import { extractTitleFromMessage, sanitizeQuestionPrompt } from "@/lib/utils"; +import { parsingPromptText, sanitizeQuestionPrompt } from "@/lib/utils"; import { ChatMessageList } from "@/components/ui/chat/chat-message-list"; import { ChatBubble, @@ -17,22 +17,22 @@ export function RouteChat() { const chat = prompts?.find((prompt) => prompt.chat_id === id); const title = - chat === undefined - ? "" - : extractTitleFromMessage( - chat.question_answers?.[0]?.question?.message - ? sanitizeQuestionPrompt({ - question: chat.question_answers?.[0].question.message, - answer: chat.question_answers?.[0]?.answer?.message ?? "", - }) - : `Prompt ${chat.conversation_timestamp}`, + chat === undefined || + chat.question_answers?.[0]?.question?.message === undefined + ? `Prompt ${id}` + : parsingPromptText( + sanitizeQuestionPrompt({ + question: chat.question_answers?.[0].question.message, + answer: chat.question_answers?.[0]?.answer?.message ?? "", + }), + chat.conversation_timestamp, ); return ( <> - {title} + {title}
From e7d0b02aa8eb95c0386d760184d17e83e23ec475 Mon Sep 17 00:00:00 2001 From: Giuseppe Scuglia Date: Wed, 22 Jan 2025 18:27:59 +0100 Subject: [PATCH 2/3] test: check only the day --- src/components/__tests__/PromptList.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/__tests__/PromptList.test.tsx b/src/components/__tests__/PromptList.test.tsx index 41f14651..61a7fbdd 100644 --- a/src/components/__tests__/PromptList.test.tsx +++ b/src/components/__tests__/PromptList.test.tsx @@ -43,7 +43,7 @@ const testCases: [string, { message: string; expected: RegExp | string }][] = [ { message: " ```Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - expected: "Prompt 2025/01/03 - 10:09:33 AM", + expected: /Prompt 2025\/01\/03/i, }, ], ]; From 64f583509c8874fb5d651cd90bde312cc55e0ff0 Mon Sep 17 00:00:00 2001 From: Giuseppe Scuglia Date: Thu, 23 Jan 2025 11:11:28 +0100 Subject: [PATCH 3/3] fix: handle default use cases without checking the chars length --- src/components/__tests__/PromptList.test.tsx | 7 ++++--- src/lib/utils.ts | 6 +----- src/routes/route-chat.tsx | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/components/__tests__/PromptList.test.tsx b/src/components/__tests__/PromptList.test.tsx index 61a7fbdd..26982578 100644 --- a/src/components/__tests__/PromptList.test.tsx +++ b/src/components/__tests__/PromptList.test.tsx @@ -39,11 +39,12 @@ const testCases: [string, { message: string; expected: RegExp | string }][] = [ }, ], [ - "render fallback", + "render default", { message: - " ```Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", - expected: /Prompt 2025\/01\/03/i, + "I know that this local proxy can forward requests to api.foo.com.\n\napi.foo.com will validate whether the connection si trusted using a certificate authority added on the local machine, specifically whether they allow SSL and x.509 basic policy.\n\nI need to be able to validate the proxys ability to make requests to api.foo.com. I only have access to code that can run in the browser. I can infer this based on a successful request. Be creative.", + expected: + "I know that this local proxy can forward requests to api.foo.com. api.foo.com will validate whether the connection si trusted using a certificate authority added on the local machine, specifically whether they allow SSL and x.509 basic policy. I need to be able to validate the proxys ability to make requests to api.foo.com. I only have access to code that can run in the browser. I can infer this based on a successful request. Be creative.", }, ], ]; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index e282602d..886881d1 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -6,7 +6,6 @@ const ONE_DAY_MS = 24 * 60 * 60 * 1000; const SEVEN_DAYS_MS = 7 * ONE_DAY_MS; const TEEN_DAYS_MS = 14 * ONE_DAY_MS; const THTY_DAYS_MS = 30 * ONE_DAY_MS; -const MAX_TEXT_LENGTH = 200; const FILEPATH_REGEX = /(?:---FILEPATH|Path:|\/\/\s*filepath:)\s*([^\s]+)/g; const COMPARE_CODE_REGEX = /Compare this snippet[^:]*:/g; @@ -16,6 +15,7 @@ function parsingByKeys(text: string | undefined, timestamp: string) { if (!text) return fallback; const filePath = text.match(FILEPATH_REGEX); const compareCode = text.match(COMPARE_CODE_REGEX); + // there some edge cases in copilot where the prompts are not correctly parsed. In this case is better to show the filepath if (compareCode || filePath) { if (filePath) return `Prompt on file${filePath[0]?.trim().toLocaleLowerCase()}`; @@ -24,10 +24,6 @@ function parsingByKeys(text: string | undefined, timestamp: string) { return `Prompt from snippet ${compareCode[0]?.trim().toLocaleLowerCase()}`; } - if (text.length > MAX_TEXT_LENGTH) { - return fallback; - } - return text.trim(); } catch { return fallback; diff --git a/src/routes/route-chat.tsx b/src/routes/route-chat.tsx index 4ed15a7e..605d137f 100644 --- a/src/routes/route-chat.tsx +++ b/src/routes/route-chat.tsx @@ -32,7 +32,7 @@ export function RouteChat() { <> - {title} + {title}