-
Notifications
You must be signed in to change notification settings - Fork 19
ECHO-673 Fixes #443
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ECHO-673 Fixes #443
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| import { t } from "@lingui/core/macro"; | ||
| import { Text, Tooltip } from "@mantine/core"; | ||
| import { type ReactNode, useMemo } from "react"; | ||
|
|
||
| const REDACTED_PATTERN = /<redacted_([a-z_]+)>/g; | ||
|
|
||
| const REDACTED_LABELS: Record<string, string> = { | ||
| address: "Address", | ||
| card: "Card", | ||
| email: "Email", | ||
| iban: "IBAN", | ||
| id: "ID", | ||
| license_plate: "License Plate", | ||
| name: "Name", | ||
| phone: "Phone", | ||
| username: "Username", | ||
| }; | ||
|
|
||
| const formatLabel = (key: string): string => { | ||
| if (key in REDACTED_LABELS) { | ||
| return REDACTED_LABELS[key]; | ||
| } | ||
| return key | ||
| .split("_") | ||
| .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) | ||
| .join(" "); | ||
| }; | ||
|
|
||
| const RedactedBadge = ({ type }: { type: string }) => { | ||
| const label = formatLabel(type); | ||
| return ( | ||
| <Tooltip label={t`This information is anonymized`} withArrow> | ||
| <Text | ||
| component="span" | ||
| size="sm" | ||
| bg="primary.2" | ||
| px={6} | ||
| py={1} | ||
| > | ||
| {label} | ||
| </Text> | ||
| </Tooltip> | ||
| ); | ||
| }; | ||
|
|
||
| /** | ||
| * Parses a text string and replaces `<redacted_*>` placeholders with | ||
| * styled inline badges that show a human-readable label and tooltip. | ||
| * | ||
| * Returns the original string unchanged if no placeholders are found. | ||
| */ | ||
| export const parseRedactedText = (text: string): ReactNode[] | string => { | ||
| if (!text || !text.includes("<redacted_")) { | ||
| return text; | ||
| } | ||
|
|
||
| const parts: ReactNode[] = []; | ||
| let lastIndex = 0; | ||
|
|
||
| const regex = new RegExp(REDACTED_PATTERN); | ||
| for (let match = regex.exec(text); match !== null; match = regex.exec(text)) { | ||
| if (match.index > lastIndex) { | ||
| parts.push(text.slice(lastIndex, match.index)); | ||
| } | ||
| parts.push(<RedactedBadge key={`${match.index}-${match[1]}`} type={match[1]} />); | ||
| lastIndex = regex.lastIndex; | ||
| } | ||
|
|
||
| if (lastIndex < text.length) { | ||
| parts.push(text.slice(lastIndex)); | ||
| } | ||
|
|
||
| return parts; | ||
| }; | ||
|
|
||
| /** | ||
| * Component that renders text with `<redacted_*>` placeholders replaced | ||
| * by subtle inline badges with tooltips. | ||
| */ | ||
| export const RedactedText = ({ | ||
| children, | ||
| className, | ||
| }: { | ||
| children: string; | ||
| className?: string; | ||
| }) => { | ||
| const rendered = useMemo(() => parseRedactedText(children), [children]); | ||
|
|
||
| if (typeof rendered === "string") { | ||
| return <span className={className}>{rendered}</span>; | ||
| } | ||
|
|
||
| return <span className={className}>{rendered}</span>; | ||
|
Comment on lines
+86
to
+93
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Dead branch — both paths return the same JSX. The ♻️ Simplify export const RedactedText = ({
children,
className,
}: {
children: string;
className?: string;
}) => {
const rendered = useMemo(() => parseRedactedText(children), [children]);
-
- if (typeof rendered === "string") {
- return <span className={className}>{rendered}</span>;
- }
-
return <span className={className}>{rendered}</span>;
};🤖 Prompt for AI Agents |
||
| }; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,6 +4,7 @@ import { IconDotsVertical, IconTrash } from "@tabler/icons-react"; | |||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useMutation, useQueryClient } from "@tanstack/react-query"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useParams } from "react-router"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Markdown } from "@/components/common/Markdown"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { RedactedText } from "@/components/common/RedactedText"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { toast } from "@/components/common/Toaster"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { deleteParticipantConversationChunk } from "@/lib/api"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -101,14 +102,18 @@ const UserChunkMessage = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||||
| </Menu.Dropdown> | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Menu> | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Paper className="my-2 rounded-t-xl rounded-bl-xl border-0 bg-gray-100 p-4"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Text className="prose text-sm"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| {chunk.transcript == null && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Markdown content={t`*Transcription in progress.*`} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Paper className="my-2 rounded-t-xl rounded-bl-xl border-0 bg-gray-100 p-4"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Text className="prose text-sm"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| {chunk.transcript == null && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Markdown content={t`*Transcription in progress.*`} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| {chunk.transcript?.includes("<redacted_") ? ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| <RedactedText>{chunk.transcript}</RedactedText> | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Markdown content={chunk.transcript ?? ""} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Text> | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Paper> | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Text> | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Paper> | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+105
to
+116
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: double-render when When Need a proper if/else chain or early return. 🐛 Proposed fix <Paper className="my-2 rounded-t-xl rounded-bl-xl border-0 bg-gray-100 p-4">
<Text className="prose text-sm">
- {chunk.transcript == null && (
+ {chunk.transcript == null ? (
<Markdown content={t`*Transcription in progress.*`} />
- )}
- {chunk.transcript?.includes("<redacted_") ? (
+ ) : chunk.transcript.includes("<redacted_") ? (
<RedactedText>{chunk.transcript}</RedactedText>
) : (
<Markdown content={chunk.transcript ?? ""} />
)}
</Text>
</Paper>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -19,8 +19,8 @@ import { | |||||||||||||||||
| TextInput, | ||||||||||||||||||
| Title, | ||||||||||||||||||
| } from "@mantine/core"; | ||||||||||||||||||
| import { ShieldCheckIcon } from "@phosphor-icons/react"; | ||||||||||||||||||
| import { IconEye, IconEyeOff, IconRefresh, IconX } from "@tabler/icons-react"; | ||||||||||||||||||
| import { DetectiveIcon } from "@phosphor-icons/react"; | ||||||||||||||||||
| import { IconEye, IconEyeOff, IconInfoCircle, IconRefresh, IconRosetteDiscountCheck, IconX } from "@tabler/icons-react"; | ||||||||||||||||||
| import { useQueryClient } from "@tanstack/react-query"; | ||||||||||||||||||
| import { Resizable } from "re-resizable"; | ||||||||||||||||||
| import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react"; | ||||||||||||||||||
|
|
@@ -30,7 +30,6 @@ import { useAutoSave } from "@/hooks/useAutoSave"; | |||||||||||||||||
| import { useLanguage } from "@/hooks/useLanguage"; | ||||||||||||||||||
| import type { VerificationTopicsResponse } from "@/lib/api"; | ||||||||||||||||||
| import { testId } from "@/lib/testUtils"; | ||||||||||||||||||
| import { Logo } from "../common/Logo"; | ||||||||||||||||||
| import { toast } from "../common/Toaster"; | ||||||||||||||||||
| import { FormLabel } from "../form/FormLabel"; | ||||||||||||||||||
| import { MarkdownWYSIWYG } from "../form/MarkdownWYSIWYG/MarkdownWYSIWYG"; | ||||||||||||||||||
|
|
@@ -634,7 +633,6 @@ const ProjectPortalEditorComponent: React.FC<ProjectPortalEditorProps> = ({ | |||||||||||||||||
| <Title order={4}> | ||||||||||||||||||
| <Trans>Explore</Trans> | ||||||||||||||||||
| </Title> | ||||||||||||||||||
| <Logo hideTitle alwaysDembrane /> | ||||||||||||||||||
| <Badge color="mauve" c="graphite" size="sm"> | ||||||||||||||||||
| <Trans id="dashboard.dembrane.concrete.beta"> | ||||||||||||||||||
| Beta | ||||||||||||||||||
|
|
@@ -841,7 +839,10 @@ const ProjectPortalEditorComponent: React.FC<ProjectPortalEditorProps> = ({ | |||||||||||||||||
| Verify | ||||||||||||||||||
| </Trans> | ||||||||||||||||||
| </Title> | ||||||||||||||||||
| <Logo hideTitle alwaysDembrane /> | ||||||||||||||||||
| <IconRosetteDiscountCheck | ||||||||||||||||||
| size={20} | ||||||||||||||||||
| color="var(--mantine-color-primary-filled)" | ||||||||||||||||||
| /> | ||||||||||||||||||
| <Badge color="mauve" c="graphite" size="sm"> | ||||||||||||||||||
| <Trans id="dashboard.dembrane.verify.beta"> | ||||||||||||||||||
| Beta | ||||||||||||||||||
|
|
@@ -1171,7 +1172,7 @@ const ProjectPortalEditorComponent: React.FC<ProjectPortalEditorProps> = ({ | |||||||||||||||||
| <Title order={4}> | ||||||||||||||||||
| <Trans>Anonymize Transcripts</Trans> | ||||||||||||||||||
| </Title> | ||||||||||||||||||
| <ShieldCheckIcon | ||||||||||||||||||
| <DetectiveIcon | ||||||||||||||||||
| size={20} | ||||||||||||||||||
| color="var(--mantine-color-primary-filled)" | ||||||||||||||||||
| /> | ||||||||||||||||||
|
|
@@ -1219,6 +1220,10 @@ const ProjectPortalEditorComponent: React.FC<ProjectPortalEditorProps> = ({ | |||||||||||||||||
| <Title order={4}> | ||||||||||||||||||
| <Trans>Auto-generate Titles</Trans> | ||||||||||||||||||
| </Title> | ||||||||||||||||||
| <IconInfoCircle | ||||||||||||||||||
| size={20} | ||||||||||||||||||
| className="text-gray-400" | ||||||||||||||||||
| /> | ||||||||||||||||||
|
Comment on lines
+1223
to
+1226
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Use a CSS variable instead of Every other icon in this file uses Proposed fix <IconInfoCircle
size={20}
- className="text-gray-400"
+ style={{ color: "var(--mantine-color-dimmed)" }}
/>As per coding guidelines: "Keep static utility classes (borders, spacing, layout) in Tailwind; move theme-dependent colors to CSS variables" and "Use 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| <Badge color="mauve" c="graphite" size="sm"> | ||||||||||||||||||
| <Trans>Beta</Trans> | ||||||||||||||||||
| </Badge> | ||||||||||||||||||
|
|
||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Label strings aren't wrapped with
t— won't be extracted for localization.Since the localization workflow is active, these user-visible labels ("Address", "Email", etc.) should go through Lingui so translators can pick them up.
✏️ Example fix
Note: since
tis a macro evaluated at render time, you'd need to make this a function or compute labels inside the component/badge. Alternatively, moveformatLabelto usetdynamically.As per coding guidelines: "Localization workflow is active: keep Lingui extract/compile scripts in mind when touching
t/Transstrings."🤖 Prompt for AI Agents