Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
7 changes: 4 additions & 3 deletions apps/desktop/src/components/editor-area/floating-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ export function FloatingButton({
);

const showRefresh = !showRaw && (isHovered || showTemplatePopover) && showRefreshIcon;
const shouldShowProgress = showProgress && progress < 1.0;

return (
<div className="flex w-fit flex-row items-center group hover:scale-105 transition-transform duration-200">
Expand Down Expand Up @@ -227,7 +228,7 @@ export function FloatingButton({
? (
<div className="flex items-center gap-2">
<XIcon size={20} />
{showProgress && (
{shouldShowProgress && (
<span className="text-xs font-mono">
{Math.round(progress * 100)}%
</span>
Expand All @@ -236,10 +237,10 @@ export function FloatingButton({
)
: (
<div className="flex items-center gap-2">
{showProgress
{shouldShowProgress
? <AnimatedEnhanceIcon size={20} />
: <EnhanceWIP size={20} strokeWidth={2} />}
{showProgress && (
{shouldShowProgress && (
<span className="text-xs font-mono">
{Math.round(progress * 100)}%
</span>
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/components/editor-area/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ export default function EditorArea({
session={sessionStore.session}
isError={enhance.status === "error"}
progress={progress}
showProgress={llmConnectionQuery.data?.type !== "HyprLocal" && sessionId !== onboardingSessionId}
showProgress={llmConnectionQuery.data?.type === "HyprLocal" && sessionId !== onboardingSessionId}
/>
</div>
</motion.div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
import { Trans } from "@lingui/react/macro";
import { Folder } from "lucide-react";

import { commands as windowsCommands } from "@hypr/plugin-windows";
import { Button } from "@hypr/ui/components/ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "@hypr/ui/components/ui/tooltip";

// Custom SVG icon component to replace the folder icon
function CustomFinderIcon({ size = 16 }: { size?: number }) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
width={size}
height={size}
>
<path d="M21.001 3C21.5533 3 22.001 3.44772 22.001 4V20C22.001 20.5523 21.5533 21 21.001 21H3.00098C2.44869 21 2.00098 20.5523 2.00098 20V4C2.00098 3.44772 2.44869 3 3.00098 3H21.001ZM10.4817 4.99884L4.00098 5V19L12.747 18.9997C12.6851 18.6562 12.6308 18.3163 12.5844 17.98C12.2874 17.9933 12.0929 18 12.001 18C9.79308 18 7.60332 17.2701 5.44628 15.8321L6.55568 14.1679C8.39863 15.3966 10.2089 16 12.001 16C12.1337 16 12.2664 15.9967 12.3993 15.9901C12.3747 15.4926 12.3747 14.5797 12.4064 14H9.50098V13C9.50098 9.72527 9.82146 7.06094 10.4817 4.99884ZM12.601 4.99851C11.9358 6.58176 11.5567 9.41121 11.5119 12H14.6338L14.4933 13.124C14.3927 13.9288 14.3567 14.7687 14.3857 15.6439C15.3987 15.3449 16.4174 14.8539 17.4463 14.1679L18.5557 15.8321C17.2358 16.7119 15.9038 17.3267 14.5628 17.6714C14.62 18.1052 14.6937 18.5482 14.7819 18.999L20.001 19V5L12.601 4.99851ZM7.00098 7C7.55326 7 8.00098 7.44772 8.00098 8V9C8.00098 9.55228 7.55326 10 7.00098 10C6.44869 10 6.00098 9.55228 6.00098 9V8C6.00098 7.44772 6.44869 7 7.00098 7ZM17.001 7C17.5533 7 18.001 7.44772 18.001 8V9C18.001 9.55228 17.5533 10 17.001 10C16.4487 10 16.001 9.55228 16.001 9V8C16.001 7.44772 16.4487 7 17.001 7Z">
</path>
</svg>
);
}

export function FinderButton() {
const handleClickFinder = () => {
windowsCommands.windowShow({ type: "finder" });
Expand All @@ -19,7 +34,7 @@ export function FinderButton() {
onClick={handleClickFinder}
className="hover:bg-neutral-200"
>
<Folder size={16} />
<CustomFinderIcon size={18} />
</Button>
</TooltipTrigger>
<TooltipContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useRightPanel } from "@/contexts";
import { commands as dbCommands } from "@hypr/plugin-db";
import { Badge } from "@hypr/ui/components/ui/badge";
import { Button } from "@hypr/ui/components/ui/button";
import { BadgeType } from "../../views";
import { BadgeType } from "../../types/chat-types";

interface ChatInputProps {
inputValue: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { cn } from "@hypr/ui/lib/utils";
import { Trans } from "@lingui/react/macro";
import { MessageContent } from "./message-content";
import { Message } from "./types";

Expand All @@ -11,16 +9,34 @@ interface ChatMessageProps {
}

export function ChatMessage({ message, sessionTitle, hasEnhancedNote, onApplyMarkdown }: ChatMessageProps) {
if (message.isUser) {
return (
<div className="w-full mb-4 flex justify-end">
<div className="max-w-[80%]">
<div className="border border-input rounded-lg overflow-clip bg-white">
<div className="px-3 py-2">
<MessageContent
message={message}
sessionTitle={sessionTitle}
hasEnhancedNote={hasEnhancedNote}
onApplyMarkdown={onApplyMarkdown}
/>
</div>
</div>
{/* Timestamp below the message */}
<div className="text-xs text-neutral-500 mt-1 text-right">
{message.timestamp.toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
})}
</div>
</div>
</div>
);
}

return (
<div className="w-full mb-4">
<div
className={cn(
"font-semibold text-xs mb-1",
message.isUser ? "text-neutral-700" : "text-amber-700",
)}
>
{message.isUser ? <Trans>User:</Trans> : <Trans>Assistant:</Trans>}
</div>
<MessageContent
message={message}
sessionTitle={sessionTitle}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const EmptyChatState = memo(({ onQuickAction, onFocusInput }: EmptyChatSt
>
<div className="flex items-center gap-2 mb-4">
<h3 className="text-lg font-medium">
<Trans>Chat with meeting notes</Trans>
<Trans>Chat with this meeting</Trans>
</h3>
<Badge variant="secondary" className="text-[10px] px-1.5 py-0.5 bg-blue-100 text-blue-800 border-blue-200">
Beta
Expand Down
Original file line number Diff line number Diff line change
@@ -1,51 +1,67 @@
import { PlusIcon } from "lucide-react";
import { ClockIcon, PlusIcon } from "lucide-react";
import { useState } from "react";

import { Button } from "@hypr/ui/components/ui/button";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@hypr/ui/components/ui/tooltip";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@hypr/ui/components/ui/dropdown-menu";

interface FloatingActionButtonsProps {
onNewChat: () => void;
onViewHistory: () => void;
chatGroups?: Array<{ id: string; created_at: string; firstMessage?: string }>;
onSelectChatGroup?: (groupId: string) => void;
}

export function FloatingActionButtons({ onNewChat, onViewHistory }: FloatingActionButtonsProps) {
export function FloatingActionButtons(
{ onNewChat, onViewHistory, chatGroups, onSelectChatGroup }: FloatingActionButtonsProps,
) {
const [isDropdownOpen, setIsDropdownOpen] = useState(false);

return (
<TooltipProvider>
<div className="absolute top-4 right-4 z-10 flex group border rounded-lg overflow-clip divide-x bg-background/40 transition-colors">
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
className="opacity-20 group-hover:opacity-100 transition-opacity rounded-none hover:bg-white"
onClick={onNewChat}
>
<PlusIcon className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent side="bottom" align="end">
<p>New Chat</p>
</TooltipContent>
</Tooltip>
<div className="absolute top-4 left-4 z-10 flex group border rounded-lg overflow-clip divide-x bg-background/40 transition-colors">
<Button
variant="ghost"
size="icon"
className="opacity-20 group-hover:opacity-100 transition-opacity rounded-none hover:bg-white"
onClick={onNewChat}
>
<PlusIcon className="h-4 w-4" />
</Button>

{
/* <Tooltip>
<TooltipTrigger asChild>
{chatGroups && chatGroups.length > 0 && onSelectChatGroup && (
<DropdownMenu open={isDropdownOpen} onOpenChange={setIsDropdownOpen}>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
className="opacity-20 group-hover:opacity-100 transition-opacity rounded-none hover:bg-white"
onClick={onViewHistory}
>
<ClockIcon className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent side="bottom" align="end">
<p>View History</p>
</TooltipContent>
</Tooltip> */
}
</div>
</TooltipProvider>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-48">
{chatGroups.map((group, index) => (
<DropdownMenuItem
key={group.id}
onClick={() => {
onSelectChatGroup(group.id);
setIsDropdownOpen(false);
}}
>
{group.firstMessage
? (group.firstMessage.length > 25
? group.firstMessage.substring(0, 25) + "..."
: group.firstMessage)
: `Chat Group ${index + 1}`}
</DropdownMenuItem>
))}
</DropdownMenuContent>
</DropdownMenu>
)}
</div>
);
}
80 changes: 80 additions & 0 deletions apps/desktop/src/components/right-panel/hooks/useActiveEntity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { useMatch } from "@tanstack/react-router";
import { useEffect, useState } from "react";

import type { ActiveEntityInfo, Message } from "../types/chat-types";

interface UseActiveEntityProps {
setMessages: (messages: Message[]) => void;
setInputValue: (value: string) => void;
setShowHistory: (show: boolean) => void;
setHasChatStarted: (started: boolean) => void;
setIsGenerating?: (generating: boolean) => void;
}

export function useActiveEntity({
setMessages,
setInputValue,
setShowHistory,
setHasChatStarted,
setIsGenerating,
}: UseActiveEntityProps) {
const [activeEntity, setActiveEntity] = useState<ActiveEntityInfo | null>(null);

const noteMatch = useMatch({ from: "/app/note/$id", shouldThrow: false });
const humanMatch = useMatch({ from: "/app/human/$id", shouldThrow: false });
const organizationMatch = useMatch({ from: "/app/organization/$id", shouldThrow: false });

const sessionId = activeEntity?.type === "note" ? activeEntity.id : null;

useEffect(() => {
let newEntity = null;

if (noteMatch) {
const noteId = noteMatch.params.id;
newEntity = {
id: noteId,
type: "note" as const,
};
} else if (humanMatch) {
const humanId = humanMatch.params.id;
newEntity = {
id: humanId,
type: "human" as const,
};
} else if (organizationMatch) {
const orgId = organizationMatch.params.id;
newEntity = {
id: orgId,
type: "organization" as const,
};
}

const isDifferentSession = !activeEntity
|| (newEntity && (activeEntity.id !== newEntity.id || activeEntity.type !== newEntity.type));

if (isDifferentSession) {
setActiveEntity(newEntity);
setMessages([]);
setInputValue("");
setShowHistory(false);
setHasChatStarted(false);
setIsGenerating?.(false);
}
}, [
noteMatch,
humanMatch,
organizationMatch,
activeEntity,
setMessages,
setInputValue,
setShowHistory,
setHasChatStarted,
setIsGenerating,
]);

return {
activeEntity,
sessionId,
setActiveEntity,
};
}
Loading
Loading