diff --git a/echo/directus/sync/collections/folders.json b/echo/directus/sync/collections/folders.json index ad3d1d96..e171a536 100644 --- a/echo/directus/sync/collections/folders.json +++ b/echo/directus/sync/collections/folders.json @@ -1,4 +1,9 @@ [ + { + "name": "custom_logos", + "parent": null, + "_syncId": "416965c6-7695-4235-8322-8515c9a05820" + }, { "name": "Public", "parent": null, diff --git a/echo/directus/sync/collections/permissions.json b/echo/directus/sync/collections/permissions.json index 3af3a057..e02c7dbc 100644 --- a/echo/directus/sync/collections/permissions.json +++ b/echo/directus/sync/collections/permissions.json @@ -2123,6 +2123,13 @@ "_and": [ { "_or": [ + { + "folder": { + "name": { + "_contains": "custom_logos" + } + } + }, { "folder": { "name": { diff --git a/echo/frontend/src/components/common/RedactedText.tsx b/echo/frontend/src/components/common/RedactedText.tsx new file mode 100644 index 00000000..5bf7d576 --- /dev/null +++ b/echo/frontend/src/components/common/RedactedText.tsx @@ -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 = //g; + +const REDACTED_LABELS: Record = { + 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 ( + + + {label} + + + ); +}; + +/** + * Parses a text string and replaces `` 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(" lastIndex) { + parts.push(text.slice(lastIndex, match.index)); + } + parts.push(); + lastIndex = regex.lastIndex; + } + + if (lastIndex < text.length) { + parts.push(text.slice(lastIndex)); + } + + return parts; +}; + +/** + * Component that renders text with `` 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 {rendered}; + } + + return {rendered}; +}; diff --git a/echo/frontend/src/components/conversation/ConversationAccordion.tsx b/echo/frontend/src/components/conversation/ConversationAccordion.tsx index 7b056e12..2f1ad35f 100644 --- a/echo/frontend/src/components/conversation/ConversationAccordion.tsx +++ b/echo/frontend/src/components/conversation/ConversationAccordion.tsx @@ -34,7 +34,7 @@ import { useMediaQuery, useSessionStorage, } from "@mantine/hooks"; -import { ShieldCheckIcon } from "@phosphor-icons/react"; +import { DetectiveIcon } from "@phosphor-icons/react"; import { IconArrowsExchange, IconArrowsUpDown, @@ -587,7 +587,7 @@ const ConversationAccordionItem = ({ size={18} style={{ cursor: "default" }} > - + )} diff --git a/echo/frontend/src/components/conversation/ConversationChunkAudioTranscript.tsx b/echo/frontend/src/components/conversation/ConversationChunkAudioTranscript.tsx index b0718f62..4de96f55 100644 --- a/echo/frontend/src/components/conversation/ConversationChunkAudioTranscript.tsx +++ b/echo/frontend/src/components/conversation/ConversationChunkAudioTranscript.tsx @@ -2,6 +2,7 @@ import { t } from "@lingui/core/macro"; import { Divider, Skeleton, Text } from "@mantine/core"; import { BaseMessage } from "../chat/BaseMessage"; +import { RedactedText } from "../common/RedactedText"; import { useConversationChunkContentUrl } from "./hooks"; export const ConversationChunkAudioTranscript = ({ @@ -65,13 +66,13 @@ export const ConversationChunkAudioTranscript = ({ {t`Transcript not available yet`} )} */} - {chunk.error ? ( - {t`Unable to process this chunk`} - ) : !chunk.transcript ? ( - {t`Transcribing...`} - ) : ( - chunk.transcript - )} + {chunk.error ? ( + {t`Unable to process this chunk`} + ) : !chunk.transcript ? ( + {t`Transcribing...`} + ) : ( + {chunk.transcript} + )} ); diff --git a/echo/frontend/src/components/participant/UserChunkMessage.tsx b/echo/frontend/src/components/participant/UserChunkMessage.tsx index 01386e55..0c07db55 100644 --- a/echo/frontend/src/components/participant/UserChunkMessage.tsx +++ b/echo/frontend/src/components/participant/UserChunkMessage.tsx @@ -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 = ({ - - - {chunk.transcript == null && ( - - )} + + + {chunk.transcript == null && ( + + )} + {chunk.transcript?.includes("{chunk.transcript} + ) : ( - - + )} + + ); }; diff --git a/echo/frontend/src/components/project/ProjectPortalEditor.tsx b/echo/frontend/src/components/project/ProjectPortalEditor.tsx index 3a910b03..1d521f54 100644 --- a/echo/frontend/src/components/project/ProjectPortalEditor.tsx +++ b/echo/frontend/src/components/project/ProjectPortalEditor.tsx @@ -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 = ({ <Trans>Explore</Trans> - Beta @@ -841,7 +839,10 @@ const ProjectPortalEditorComponent: React.FC = ({ Verify - + Beta @@ -1171,7 +1172,7 @@ const ProjectPortalEditorComponent: React.FC = ({ <Trans>Anonymize Transcripts</Trans> - @@ -1219,6 +1220,10 @@ const ProjectPortalEditorComponent: React.FC = ({ <Trans>Auto-generate Titles</Trans> + Beta diff --git a/echo/frontend/src/components/quote/Quote.tsx b/echo/frontend/src/components/quote/Quote.tsx index 2679188f..4f5ea424 100644 --- a/echo/frontend/src/components/quote/Quote.tsx +++ b/echo/frontend/src/components/quote/Quote.tsx @@ -12,6 +12,7 @@ import { useCopyQuote } from "@/components/aspect/hooks/useCopyQuote"; import { cn } from "@/lib/utils"; import { CopyIconButton } from "../common/CopyIconButton"; import { I18nLink } from "../common/i18nLink"; +import { RedactedText } from "../common/RedactedText"; // replacement for AspectSegment export const Quote = ({ @@ -132,12 +133,12 @@ export const Quote = ({ > {showTranscript ? (
- - {data.verbatim_transcript} - + + {data.verbatim_transcript ?? ""} + {data.relevant_index && (
@@ -149,9 +150,9 @@ export const Quote = ({
) : (
- - "{transcriptExcerpt}" - + + "{transcriptExcerpt}" + {hasTranscript && ( Click to see full context diff --git a/echo/frontend/src/components/settings/WhitelabelLogoCard.tsx b/echo/frontend/src/components/settings/WhitelabelLogoCard.tsx index ae125a9f..37c4a4ad 100644 --- a/echo/frontend/src/components/settings/WhitelabelLogoCard.tsx +++ b/echo/frontend/src/components/settings/WhitelabelLogoCard.tsx @@ -81,9 +81,10 @@ export const WhitelabelLogoCard = () => { }, }); - const handleUpload = () => { - if (file) { - uploadMutation.mutate(file); + const handleFileChange = (selectedFile: File | null) => { + setFile(selectedFile); + if (selectedFile) { + uploadMutation.mutate(selectedFile); } }; @@ -135,23 +136,14 @@ export const WhitelabelLogoCard = () => { )} - - } - /> - - + } + disabled={uploadMutation.isPending} + /> );