Skip to content

Commit 6da034d

Browse files
authored
fix: parsing promptList text and breadcrumb (#177)
* fix: parsing promptList text and breadcrumb * test: check only the day * fix: handle default use cases without checking the chars length
1 parent 16a7213 commit 6da034d

File tree

4 files changed

+111
-35
lines changed

4 files changed

+111
-35
lines changed

src/components/PromptList.tsx

+9-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Link } from "react-router-dom";
22
import {
3-
extractTitleFromMessage,
3+
parsingPromptText,
44
groupPromptsByRelativeDate,
55
sanitizeQuestionPrompt,
66
} from "@/lib/utils";
@@ -31,15 +31,14 @@ export function PromptList({ prompts }: { prompts: Conversation[] }) {
3131
{ "font-bold": currentPromptId === prompt.chat_id },
3232
)}
3333
>
34-
{extractTitleFromMessage(
35-
prompt.question_answers?.[0]?.question?.message
36-
? sanitizeQuestionPrompt({
37-
question:
38-
prompt.question_answers?.[0].question.message,
39-
answer:
40-
prompt.question_answers?.[0]?.answer?.message ?? "",
41-
})
42-
: `Prompt ${prompt.conversation_timestamp}`,
34+
{parsingPromptText(
35+
sanitizeQuestionPrompt({
36+
question:
37+
prompt.question_answers?.[0]?.question.message ?? "",
38+
answer:
39+
prompt.question_answers?.[0]?.answer?.message ?? "",
40+
}),
41+
prompt.conversation_timestamp,
4342
)}
4443
</Link>
4544
</li>

src/components/__tests__/PromptList.test.tsx

+60-9
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,52 @@ import mockedPrompts from "@/mocks/msw/fixtures/GET_MESSAGES.json";
55
import { render } from "@/lib/test-utils";
66
import { Conversation } from "@/api/generated";
77

8+
const conversationTimestamp = "2025-01-02T14:19:58.024100Z";
89
const prompt = mockedPrompts[0] as Conversation;
910

11+
const testCases: [string, { message: string; expected: RegExp | string }][] = [
12+
[
13+
"codegate cmd",
14+
{
15+
message: "codegate workspace -h",
16+
expected: /codegate workspace -h/i,
17+
},
18+
],
19+
[
20+
"render code with path",
21+
{
22+
message: "// Path: src/lib/utils.ts",
23+
expected: /Prompt on filepath: src\/lib\/utils.ts/i,
24+
},
25+
],
26+
[
27+
"render code with file path",
28+
{
29+
message: "<file> ```tsx // filepath: /tests/my-test.tsx import",
30+
expected: /Prompt on file\/\/ filepath: \/tests\/my-test.tsx/i,
31+
},
32+
],
33+
[
34+
"render snippet",
35+
{
36+
message:
37+
'Compare this snippet from src/test.ts: // import { fakePkg } from "fake-pkg";',
38+
expected: /Prompt from snippet compare this snippet from src\/test.ts:/i,
39+
},
40+
],
41+
[
42+
"render default",
43+
{
44+
message:
45+
"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.",
46+
expected:
47+
"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.",
48+
},
49+
],
50+
];
51+
1052
describe("PromptList", () => {
11-
it("should render correct prompt", () => {
53+
it("render prompt", () => {
1254
render(<PromptList prompts={[prompt]} />);
1355
expect(
1456
screen.getByRole("link", {
@@ -17,25 +59,34 @@ describe("PromptList", () => {
1759
).toBeVisible();
1860
});
1961

20-
it("should render default prompt value when missing question", async () => {
21-
const conversationTimestamp = "2025-01-02T14:19:58.024100Z";
62+
it.each(testCases)("%s", (_title: string, { message, expected }) => {
2263
render(
2364
<PromptList
2465
prompts={[
2566
{
26-
question_answers: [],
27-
provider: "vllm",
28-
type: "fim",
29-
chat_id: "b97fbe59-0e34-4b98-8f2f-41332ebc059a",
30-
conversation_timestamp: conversationTimestamp,
67+
...prompt,
68+
question_answers: [
69+
{
70+
answer: {
71+
message: "Mock AI answer",
72+
message_id: "fake_ai_id",
73+
timestamp: conversationTimestamp,
74+
},
75+
question: {
76+
message,
77+
message_id: "fake_id",
78+
timestamp: conversationTimestamp,
79+
},
80+
},
81+
],
3182
},
3283
]}
3384
/>,
3485
);
3586

3687
expect(
3788
screen.getByRole("link", {
38-
name: `Prompt ${conversationTimestamp}`,
89+
name: expected,
3990
}),
4091
).toBeVisible();
4192
});

src/lib/utils.ts

+31-5
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,49 @@
11
import { AlertConversation, Conversation } from "@/api/generated/types.gen";
22
import { MaliciousPkgType, TriggerType } from "@/types";
3-
import { isToday, isYesterday } from "date-fns";
3+
import { format, isToday, isYesterday } from "date-fns";
44

55
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
66
const SEVEN_DAYS_MS = 7 * ONE_DAY_MS;
77
const TEEN_DAYS_MS = 14 * ONE_DAY_MS;
88
const THTY_DAYS_MS = 30 * ONE_DAY_MS;
9+
const FILEPATH_REGEX = /(?:---FILEPATH|Path:|\/\/\s*filepath:)\s*([^\s]+)/g;
10+
const COMPARE_CODE_REGEX = /Compare this snippet[^:]*:/g;
911

10-
export function extractTitleFromMessage(message: string) {
12+
function parsingByKeys(text: string | undefined, timestamp: string) {
13+
const fallback = `Prompt ${format(new Date(timestamp ?? ""), "y/MM/dd - hh:mm:ss a")}`;
1114
try {
15+
if (!text) return fallback;
16+
const filePath = text.match(FILEPATH_REGEX);
17+
const compareCode = text.match(COMPARE_CODE_REGEX);
18+
// there some edge cases in copilot where the prompts are not correctly parsed. In this case is better to show the filepath
19+
if (compareCode || filePath) {
20+
if (filePath)
21+
return `Prompt on file${filePath[0]?.trim().toLocaleLowerCase()}`;
22+
23+
if (compareCode)
24+
return `Prompt from snippet ${compareCode[0]?.trim().toLocaleLowerCase()}`;
25+
}
26+
27+
return text.trim();
28+
} catch {
29+
return fallback;
30+
}
31+
}
32+
33+
export function parsingPromptText(message: string, timestamp: string) {
34+
try {
35+
// checking malformed markdown code blocks
1236
const regex = /^(.*)```[\s\S]*?```(.*)$/s;
1337
const match = message.match(regex);
1438

1539
if (match !== null && match !== undefined) {
1640
const beforeMarkdown = match[1]?.trim();
1741
const afterMarkdown = match[2]?.trim();
1842
const title = beforeMarkdown || afterMarkdown;
19-
return title;
43+
return parsingByKeys(title, timestamp);
2044
}
2145

22-
return message.trim();
46+
return parsingByKeys(message, timestamp);
2347
} catch {
2448
return message.trim();
2549
}
@@ -119,7 +143,9 @@ export function sanitizeQuestionPrompt({
119143
}) {
120144
try {
121145
// it shouldn't be possible to receive the prompt answer without a question
122-
if (!answer) return question;
146+
if (!answer) {
147+
throw new Error("Missing AI answer");
148+
}
123149

124150
// Check if 'answer' is truthy; if so, try to find and return the text after "Query:"
125151
const index = question.indexOf("Query:");

src/routes/route-chat.tsx

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useParams } from "react-router-dom";
22
import { usePromptsData } from "@/hooks/usePromptsData";
3-
import { extractTitleFromMessage, sanitizeQuestionPrompt } from "@/lib/utils";
3+
import { parsingPromptText, sanitizeQuestionPrompt } from "@/lib/utils";
44
import { ChatMessageList } from "@/components/ui/chat/chat-message-list";
55
import {
66
ChatBubble,
@@ -17,22 +17,22 @@ export function RouteChat() {
1717
const chat = prompts?.find((prompt) => prompt.chat_id === id);
1818

1919
const title =
20-
chat === undefined
21-
? ""
22-
: extractTitleFromMessage(
23-
chat.question_answers?.[0]?.question?.message
24-
? sanitizeQuestionPrompt({
25-
question: chat.question_answers?.[0].question.message,
26-
answer: chat.question_answers?.[0]?.answer?.message ?? "",
27-
})
28-
: `Prompt ${chat.conversation_timestamp}`,
20+
chat === undefined ||
21+
chat.question_answers?.[0]?.question?.message === undefined
22+
? `Prompt ${id}`
23+
: parsingPromptText(
24+
sanitizeQuestionPrompt({
25+
question: chat.question_answers?.[0].question.message,
26+
answer: chat.question_answers?.[0]?.answer?.message ?? "",
27+
}),
28+
chat.conversation_timestamp,
2929
);
3030

3131
return (
3232
<>
3333
<Breadcrumbs>
3434
<BreadcrumbHome />
35-
<Breadcrumb>{title}</Breadcrumb>
35+
<Breadcrumb className="w-96 block truncate">{title}</Breadcrumb>
3636
</Breadcrumbs>
3737

3838
<div className="w-[calc(100vw-18rem)]">

0 commit comments

Comments
 (0)