Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import { ActionIcon, Button, Group, Stack, Text } from "@mantine/core";
import { IconX } from "@tabler/icons-react";
import { testId } from "@/lib/testUtils";
import { useUnreadAnnouncements } from "./hooks";

export const AnnouncementDrawerHeader = ({
Expand All @@ -27,13 +28,14 @@ export const AnnouncementDrawerHeader = ({
onClick={onClose}
aria-label="Close drawer"
className="focus:outline-none"
{...testId("announcement-close-drawer-button")}
>
<IconX color="gray" />
</ActionIcon>
</Group>
<Group gap="xs" justify="space-between" w="100%">
{hasUnreadAnnouncements && (
<Text size="sm" c="dimmed">
<Text size="sm" c="dimmed" {...testId("announcement-unread-count")}>
{unreadCount}{" "}
{unreadCount === 1
? t`unread announcement`
Expand All @@ -47,6 +49,7 @@ export const AnnouncementDrawerHeader = ({
onClick={onMarkAllAsRead}
disabled={isPending}
loading={isPending}
{...testId("announcement-mark-all-read-button")}
>
<Trans>Mark all read</Trans>
</Button>
Expand Down
15 changes: 13 additions & 2 deletions echo/frontend/src/components/announcement/AnnouncementIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useAnnouncementDrawer } from "@/components/announcement/hooks";
import { getTranslatedContent } from "@/components/announcement/hooks/useProcessedAnnouncements";
import { Markdown } from "@/components/common/Markdown";
import { useLanguage } from "@/hooks/useLanguage";
import { testId } from "@/lib/testUtils";
import { useLatestAnnouncement, useUnreadAnnouncements } from "./hooks";

export const AnnouncementIcon = () => {
Expand Down Expand Up @@ -32,7 +33,13 @@ export const AnnouncementIcon = () => {
const isLoading = isLoadingLatest || isLoadingUnread;

return (
<Group onClick={open} gap="sm" align="center" className="cursor-pointer">
<Group
onClick={open}
gap="sm"
align="center"
className="cursor-pointer"
{...testId("announcement-icon-button")}
>
<Box>
<Indicator
inline
Expand All @@ -51,7 +58,10 @@ export const AnnouncementIcon = () => {
{isLoading ? (
<Loader size="xs" />
) : (
<IconSpeakerphone className="me-1 rotate-[330deg]" />
<IconSpeakerphone
className="me-1 rotate-[330deg]"
{...testId("announcement-speakerphone-icon")}
/>
)}
</ActionIcon>
</Indicator>
Expand All @@ -61,6 +71,7 @@ export const AnnouncementIcon = () => {
<Box
className="hidden max-w-xs [mask-image:linear-gradient(to_right,black_80%,transparent)] md:block"
style={{ maxWidth: "400px" }}
{...testId("announcement-preview-message")}
>
<Markdown content={message} className="line-clamp-1" />
</Box>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from "@tabler/icons-react";
import { forwardRef, useEffect, useRef, useState } from "react";
import { Markdown } from "@/components/common/Markdown";
import { testId } from "@/lib/testUtils";
import { useFormatDate } from "./utils/dateUtils";

type Announcement = {
Expand Down Expand Up @@ -60,6 +61,7 @@ export const AnnouncementItem = forwardRef<
? "border-l-4 border-l-blue-500"
: "border-l-4 border-l-gray-50/50 bg-gray-50/50"
}`}
{...testId(`announcement-item-${announcement.id}`)}
>
<Stack gap="xs">
<Group gap="sm" align="flex-start">
Expand Down Expand Up @@ -97,6 +99,7 @@ export const AnnouncementItem = forwardRef<
height: 8,
width: 8,
}}
{...testId("announcement-unread-indicator")}
/>
)}
{/* this part needs a second look */}
Expand All @@ -119,6 +122,7 @@ export const AnnouncementItem = forwardRef<
className="hover:underline"
p={0}
onClick={() => setShowMore(!showMore)}
{...testId("announcement-show-more-button")}
>
{showMore ? (
<Group gap="xs">
Expand All @@ -144,6 +148,7 @@ export const AnnouncementItem = forwardRef<
onClick={() => {
onMarkAsRead(announcement.id);
}}
{...testId("announcement-mark-as-read-button")}
>
<Trans>Mark as read</Trans>
</Button>
Expand Down
4 changes: 3 additions & 1 deletion echo/frontend/src/components/announcement/Announcements.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useProcessedAnnouncements } from "@/components/announcement/hooks/usePr
import { useLanguage } from "@/hooks/useLanguage";
import { analytics } from "@/lib/analytics";
import { AnalyticsEvents as events } from "@/lib/analyticsEvents";
import { testId } from "@/lib/testUtils";
import { Drawer } from "../common/Drawer";
import { AnnouncementDrawerHeader } from "./AnnouncementDrawerHeader";
import { AnnouncementErrorState } from "./AnnouncementErrorState";
Expand Down Expand Up @@ -111,6 +112,7 @@ export const Announcements = () => {
maxWidth: "95%",
},
}}
{...testId("announcement-drawer")}
>
<Stack h="100%">
<ScrollArea className="flex-1">
Expand All @@ -123,7 +125,7 @@ export const Announcements = () => {
) : isLoading ? (
<AnnouncementSkeleton />
) : processedAnnouncements.length === 0 ? (
<Box p="md">
<Box p="md" {...testId("announcement-empty-state")}>
<Text c="dimmed" ta="center">
<Trans>No announcements available</Trans>
</Text>
Expand Down
11 changes: 8 additions & 3 deletions echo/frontend/src/components/chat/ChatAccordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { Suspense, useEffect } from "react";
import { useInView } from "react-intersection-observer";
import { useParams } from "react-router";
import { useI18nNavigate } from "@/hooks/useI18nNavigate";
import { testId } from "@/lib/testUtils";
import { NavigationButton } from "../common/NavigationButton";
import { MODE_COLORS } from "./ChatModeSelector";
import { ChatSkeleton } from "./ChatSkeleton";
Expand Down Expand Up @@ -88,13 +89,14 @@ export const ChatAccordionItemMenu = ({
const navigate = useI18nNavigate();

return (
<Menu shadow="md" position="right">
<Menu shadow="md" position="right" {...testId("chat-item-menu")}>
<Menu.Target>
<ActionIcon
variant="transparent"
c="gray"
size={size}
className="flex items-center justify-center"
{...testId("chat-item-menu-button")}
>
<IconDotsVertical />
</ActionIcon>
Expand All @@ -118,6 +120,7 @@ export const ChatAccordionItemMenu = ({
});
}
}}
{...testId("chat-item-menu-rename")}
>
<Trans id="project.sidebar.chat.rename">Rename</Trans>
</Menu.Item>
Expand All @@ -133,6 +136,7 @@ export const ChatAccordionItemMenu = ({
navigate(`/projects/${chat.project_id}/overview`);
}
}}
{...testId("chat-item-menu-delete")}
>
<Trans id="project.sidebar.chat.delete">Delete</Trans>
</Menu.Item>
Expand Down Expand Up @@ -225,7 +229,7 @@ export const ChatAccordionMain = ({ projectId }: { projectId: string }) => {
const totalChats = Number(chatsCountQuery.data) ?? 0;

return (
<Accordion.Item value="chat">
<Accordion.Item value="chat" {...testId("chat-accordion")}>
<Accordion.Control>
<Group justify="space-between">
<Title order={3}>
Expand All @@ -240,7 +244,7 @@ export const ChatAccordionMain = ({ projectId }: { projectId: string }) => {
<Accordion.Panel>
<Stack gap="xs">
{totalChats === 0 && (
<Text size="sm">
<Text size="sm" {...testId("chat-accordion-empty-text")}>
<Trans id="project.sidebar.chat.empty.description">
No chats found. Start a chat using the "Ask" button.
</Trans>
Expand Down Expand Up @@ -268,6 +272,7 @@ export const ChatAccordionMain = ({ projectId }: { projectId: string }) => {
</Group>
}
ref={index === allChats.length - 1 ? loadMoreRef : undefined}
{...testId(`chat-item-${item.id}`)}
>
<Stack gap="xs">
<Group gap="xs" wrap="nowrap">
Expand Down
102 changes: 60 additions & 42 deletions echo/frontend/src/components/chat/ChatModeSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,34 @@ import {
} from "@mantine/core";
import {
IconMessageCircle,
IconSparkles,
IconQuote,
IconSparkles,
} from "@tabler/icons-react";
import { useState } from "react";
import { analytics } from "@/lib/analytics";
import { AnalyticsEvents as events } from "@/lib/analyticsEvents";
import type { ChatMode } from "@/lib/api";
import { testId } from "@/lib/testUtils";
import { useInitializeChatModeMutation } from "./hooks";

// Color palette from design spec - shared across chat components
export const MODE_COLORS = {
overview: {
primary: "#1EFFA1", // spring green
border: "#1EFFA1", // spring green border
lighter: "rgba(30, 255, 161, 0.1)", // very subtle green bg
shadow: "rgba(30, 255, 161, 0.12)", // subtle green shadow
badge: "teal",
},
deep_dive: {
primary: "#00FFFF", // cyan
badge: "cyan",
border: "#00FFFF", // cyan border
lighter: "rgba(0, 255, 255, 0.1)", // very subtle cyan bg
primary: "#00FFFF", // cyan
shadow: "rgba(0, 255, 255, 0.1)", // subtle cyan shadow
badge: "cyan",
},
// Use CSS variable for theme-aware text color
graphite: "var(--app-text)",
overview: {
badge: "teal",
border: "#1EFFA1", // spring green border
lighter: "rgba(30, 255, 161, 0.1)", // very subtle green bg
primary: "#1EFFA1", // spring green
shadow: "rgba(30, 255, 161, 0.12)", // subtle green shadow
},
};

// Sample questions for each mode - wrapped in function to enable translation
Expand Down Expand Up @@ -86,6 +87,7 @@ const ModeCard = ({
onClick={() => onSelectMode(mode)}
disabled={isLoading}
className={`w-full transition-all duration-200 ${isLoading && !isSelected ? "opacity-50" : ""}`}
{...testId(`chat-mode-card-${mode}`)}
>
<Box
className={`
Expand All @@ -95,7 +97,9 @@ const ModeCard = ({
`}
style={{
backgroundColor: "var(--app-background)",
borderColor: isSelected ? colors.primary : "var(--mantine-color-gray-3)",
borderColor: isSelected
? colors.primary
: "var(--mantine-color-gray-3)",
boxShadow: isSelected ? `0 4px 16px ${colors.shadow}` : undefined,
}}
onMouseEnter={(e) => {
Expand Down Expand Up @@ -134,21 +138,21 @@ const ModeCard = ({
<Text fw={600} size="lg" style={{ color: "var(--app-text)" }}>
{title}
</Text>
{isBeta && (
<Badge
size="sm"
variant="outline"
styles={{
root: {
backgroundColor: "transparent",
borderColor: "var(--app-text)",
color: "var(--app-text)",
},
}}
>
<Trans>Beta</Trans>
</Badge>
)}
{isBeta && (
<Badge
size="sm"
variant="outline"
styles={{
root: {
backgroundColor: "transparent",
borderColor: "var(--app-text)",
color: "var(--app-text)",
},
}}
>
<Trans>Beta</Trans>
</Badge>
)}
</Group>
<Text size="sm" c="dimmed">
{subtitle}
Expand All @@ -159,15 +163,21 @@ const ModeCard = ({

{/* Example questions */}
<Stack gap="sm">
<Text size="xs" c="dimmed" fw={600} tt="uppercase" style={{ letterSpacing: 0.5 }}>
<Text
size="xs"
c="dimmed"
fw={600}
tt="uppercase"
style={{ letterSpacing: 0.5 }}
>
<Trans>Try asking</Trans>
</Text>
{examples.map((example) => (
<Group key={example} gap="sm" wrap="nowrap" align="flex-start">
<IconQuote
size={14}
color={colors.primary}
style={{ marginTop: 2, flexShrink: 0 }}
style={{ flexShrink: 0, marginTop: 2 }}
/>
<Text size="sm" c="dimmed" lh={1.5}>
{example}
Expand Down Expand Up @@ -234,11 +244,20 @@ export const ChatModeSelector = ({
const isLoading = initializeModeMutation.isPending || isCreating;

return (
<Box className="mx-auto w-full max-w-2xl px-6 py-8">
<Box
className="mx-auto w-full max-w-2xl px-6 py-8"
{...testId("chat-mode-selector")}
>
<Stack gap="xl">
{/* Header */}
<Stack gap={6} align="center">
<Title order={2} ta="center" style={{ color: "var(--app-text)" }} fw={600}>
<Title
order={2}
ta="center"
style={{ color: "var(--app-text)" }}
fw={600}
{...testId("chat-mode-selector-title")}
>
<Trans>What would you like to explore?</Trans>
</Title>
<Text size="md" c="dimmed" ta="center">
Expand All @@ -259,17 +278,17 @@ export const ChatModeSelector = ({
onSelectMode={handleSelectMode}
/>

<ModeCard
mode="overview"
title={t`Overview`}
subtitle={t`Explore themes & patterns across all conversations`}
examples={getOverviewExamples()}
icon={IconSparkles}
isBeta
selectedMode={selectedMode}
isLoading={isLoading}
onSelectMode={handleSelectMode}
/>
<ModeCard
mode="overview"
title={t`Overview`}
subtitle={t`Explore themes & patterns across all conversations`}
examples={getOverviewExamples()}
icon={IconSparkles}
isBeta
selectedMode={selectedMode}
isLoading={isLoading}
onSelectMode={handleSelectMode}
/>
</Stack>

{/* Loading message */}
Expand All @@ -284,4 +303,3 @@ export const ChatModeSelector = ({
</Box>
);
};

Loading
Loading