Skip to content

Commit

Permalink
Fix client side error after using AI filter and typing UX (#361)
Browse files Browse the repository at this point in the history
* Feat: support type view

* Fix: AI filters are taking label id instead of label name
  • Loading branch information
harshithmullapudi authored Jan 13, 2025
1 parent 2b53c82 commit 13b13ca
Show file tree
Hide file tree
Showing 15 changed files with 552 additions and 25 deletions.
6 changes: 1 addition & 5 deletions apps/webapp/src/common/layouts/app-layout/team-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,7 @@ export const TeamList = observer(() => {
}

return (
<AccordionItem
value={team.id}
key={team.identifier}
className="mb-1"
>
<AccordionItem value={team.id} key={team.id} className="mb-1">
<AccordionTrigger className="flex justify-between [&[data-state=open]>div>div>svg]:rotate-90 w-fit rounded-md min-w-0">
<div className="w-full justify-start flex items-center gap-1">
<div>
Expand Down
5 changes: 3 additions & 2 deletions apps/webapp/src/modules/issues/single-issue/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ import { HeaderLayout } from 'common/header-layout';

import { useCurrentTeam } from 'hooks/teams';

import { IssueOptionsDropdown } from './issue-actions/issue-options-dropdown';

export const Header = observer(() => {
const team = useCurrentTeam();

const {
query: { issueId, workspaceSlug },
} = useRouter();

// Use the keyboard shortcuts hook for Accept (A) and Decline (D)

return (
<HeaderLayout>
<Breadcrumb>
Expand All @@ -38,6 +38,7 @@ export const Header = observer(() => {
<BreadcrumbLink>{issueId}</BreadcrumbLink>
</BreadcrumbItem>
</Breadcrumb>
<IssueOptionsDropdown />
</HeaderLayout>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { WorkflowCategoryEnum } from '@tegonhq/types';
import { ScrollArea } from '@tegonhq/ui/components/scroll-area';
import { observer } from 'mobx-react-lite';
import * as React from 'react';
import { useDebouncedCallback } from 'use-debounce';

import { type WorkflowType } from 'common/types';

import { useIssueData } from 'hooks/issues';
import { useTeamWithId } from 'hooks/teams';
import { useTeamWorkflows } from 'hooks/workflows';

import { useUpdateIssueMutation } from 'services/issues';

import { IssueTitle } from './issue-title';
import { SimilarIssuesView } from './similar-issues-view';
import { SupportChat } from './support-chat';

export const LeftSideSupport = observer(() => {
const issue = useIssueData();
const team = useTeamWithId(issue.teamId);

const workflows = useTeamWorkflows(team.identifier);
const triageWorkflow = workflows.find(
(workflow: WorkflowType) =>
workflow.category === WorkflowCategoryEnum.TRIAGE,
);
const isTriageView = issue.stateId === triageWorkflow.id;

const { mutate: updateIssue } = useUpdateIssueMutation({});

const onIssueChange = useDebouncedCallback((content: string) => {
updateIssue({
title: content,
teamId: issue.teamId,
id: issue.id,
});
}, 1000);

return (
<ScrollArea className="grow flex h-full justify-center w-full">
<div className="flex h-full justify-center w-full">
<div className="grow flex flex-col gap-2 h-full max-w-[97ch]">
<div className="py-6 flex flex-col">
{isTriageView && <SimilarIssuesView issueId={issue.id} />}

<IssueTitle value={issue.title} onChange={onIssueChange} />

<SupportChat />
</div>
</div>
</div>
</ScrollArea>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,6 @@ export const LeftSide = observer(() => {
<ScrollArea className="grow flex h-full justify-center w-full">
<div className="flex h-full justify-center w-full">
<div className="grow flex flex-col gap-2 h-full max-w-[97ch]">
{/* <div className="flex xl:hidden px-6 py-2 border-b">
<FilterSmall />
</div> */}
<div className="py-6 flex flex-col">
{isTriageView && <SimilarIssuesView issueId={issue.id} />}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './support-chat';
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {
ChatMessageList,
ChatBubble,
ChatBubbleMessage,
ChatBubbleAvatar,
} from '@tegonhq/ui/components/chat/index';
import { observer } from 'mobx-react-lite';

import { IssueComment } from '../activity/comments-activity/issue-comment';

export const SupportChat = observer(() => {
return (
// Wrap with ChatMessageList
<div>
<ChatMessageList>
<ChatBubble variant="sent">
<ChatBubbleAvatar fallback="US" />
<ChatBubbleMessage variant="sent">
Hello, how has your day been? I hope you are doing well.
</ChatBubbleMessage>
</ChatBubble>

<ChatBubble variant="received">
<ChatBubbleAvatar fallback="AI" />
<ChatBubbleMessage variant="received">
Hi, I am doing well, thank you for asking. How can I help you today?
</ChatBubbleMessage>
</ChatBubble>

<ChatBubble variant="received">
<ChatBubbleAvatar fallback="AI" />
<ChatBubbleMessage isLoading />
</ChatBubble>
</ChatMessageList>
<IssueComment />
</div>
);
});
1 change: 0 additions & 1 deletion apps/webapp/src/services/issues/ai/ai-filter-issues.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { ajaxPost } from 'services/utils';
export interface AIFilterIssuesParams {
text: string;
teamId?: string;

workspaceId: string;
}

Expand Down
2 changes: 1 addition & 1 deletion apps/webapp/src/store/labels/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const LabelsStore: IAnyStateTreeNode = types
label.name.toLowerCase() === key.toLowerCase(),
);

return label?.id;
return label?.name;
})
.filter(Boolean);
},
Expand Down
203 changes: 203 additions & 0 deletions packages/ui/src/components/ui/chat/chat-bubble.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import { cva, type VariantProps } from 'class-variance-authority';
import * as React from 'react';

import MessageLoading from './message-loading';
import { cn } from '../../../lib/utils';
import { Avatar, AvatarImage, AvatarFallback } from '../avatar';
import { Button, type ButtonProps } from '../button';

// ChatBubble
const chatBubbleVariant = cva(
'flex gap-2 max-w-[60%] items-end relative group',
{
variants: {
variant: {
received: 'self-start',
sent: 'self-end flex-row-reverse',
},
layout: {
default: '',
ai: 'max-w-full w-full items-center',
},
},
defaultVariants: {
variant: 'received',
layout: 'default',
},
},
);

interface ChatBubbleProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof chatBubbleVariant> {}

const ChatBubble = React.forwardRef<HTMLDivElement, ChatBubbleProps>(
({ className, variant, layout, children, ...props }, ref) => (
<div
className={cn(
chatBubbleVariant({ variant, layout, className }),
'relative group',
)}
ref={ref}
{...props}
>
{React.Children.map(children, (child) =>
React.isValidElement(child) && typeof child.type !== 'string'
? React.cloneElement(child, {
variant,
layout,
} as React.ComponentProps<typeof child.type>)
: child,
)}
</div>
),
);
ChatBubble.displayName = 'ChatBubble';

// ChatBubbleAvatar
interface ChatBubbleAvatarProps {
src?: string;
fallback?: string;
className?: string;
}

const ChatBubbleAvatar: React.FC<ChatBubbleAvatarProps> = ({
src,
fallback,
className,
}) => (
<Avatar className={cn('rounded-sm text-xs', className)}>
<AvatarImage src={src} alt="Avatar" />
<AvatarFallback>{fallback}</AvatarFallback>
</Avatar>
);

// ChatBubbleMessage
const chatBubbleMessageVariants = cva('p-4', {
variants: {
variant: {
received:
'bg-grayAlpha-100 text-secondary-foreground rounded-r-lg rounded-tl-lg',
sent: 'bg-grayAlpha-100 text-foreground rounded-l-lg rounded-tr-lg',
},
layout: {
default: '',
ai: 'border-t w-full rounded-none bg-transparent',
},
},
defaultVariants: {
variant: 'received',
layout: 'default',
},
});

interface ChatBubbleMessageProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof chatBubbleMessageVariants> {
isLoading?: boolean;
}

const ChatBubbleMessage = React.forwardRef<
HTMLDivElement,
ChatBubbleMessageProps
>(
(
{ className, variant, layout, isLoading = false, children, ...props },
ref,
) => (
<div
className={cn(
chatBubbleMessageVariants({ variant, layout, className }),
'break-words max-w-full whitespace-pre-wrap',
)}
ref={ref}
{...props}
>
{isLoading ? (
<div className="flex items-center space-x-2">
<MessageLoading />
</div>
) : (
children
)}
</div>
),
);
ChatBubbleMessage.displayName = 'ChatBubbleMessage';

// ChatBubbleTimestamp
interface ChatBubbleTimestampProps
extends React.HTMLAttributes<HTMLDivElement> {
timestamp: string;
}

const ChatBubbleTimestamp: React.FC<ChatBubbleTimestampProps> = ({
timestamp,
className,
...props
}) => (
<div className={cn('text-xs mt-2 text-right', className)} {...props}>
{timestamp}
</div>
);

// ChatBubbleAction
type ChatBubbleActionProps = ButtonProps & {
icon: React.ReactNode;
};

const ChatBubbleAction: React.FC<ChatBubbleActionProps> = ({
icon,
onClick,
className,
variant = 'ghost',
size = 'icon',
...props
}) => (
<Button
variant={variant}
size={size}
className={className}
onClick={onClick}
{...props}
>
{icon}
</Button>
);

interface ChatBubbleActionWrapperProps
extends React.HTMLAttributes<HTMLDivElement> {
variant?: 'sent' | 'received';
className?: string;
}

const ChatBubbleActionWrapper = React.forwardRef<
HTMLDivElement,
ChatBubbleActionWrapperProps
>(({ variant, className, children, ...props }, ref) => (
<div
ref={ref}
className={cn(
'absolute top-1/2 -translate-y-1/2 flex opacity-0 group-hover:opacity-100 transition-opacity duration-200',
variant === 'sent'
? '-left-1 -translate-x-full flex-row-reverse'
: '-right-1 translate-x-full',
className,
)}
{...props}
>
{children}
</div>
));
ChatBubbleActionWrapper.displayName = 'ChatBubbleActionWrapper';

export {
ChatBubble,
ChatBubbleAvatar,
ChatBubbleMessage,
ChatBubbleTimestamp,
chatBubbleVariant,
chatBubbleMessageVariants,
ChatBubbleAction,
ChatBubbleActionWrapper,
};
Loading

0 comments on commit 13b13ca

Please sign in to comment.