mirror of https://github.com/buster-so/buster.git
Refactor chat permissions and enhance edit capabilities
- Introduced `useChatPermission` hook to manage chat permissions. - Updated components to utilize the new permission logic, allowing conditional rendering of edit options. - Adjusted `ChatInput` to display a view-only message when the user lacks edit permissions. - Cleaned up `package.json` formatting and added missing exports in context files.
This commit is contained in:
parent
eff58a11f1
commit
b1b21e30a4
|
@ -4,7 +4,7 @@ import type { IBusterChat } from '@/api/asset_interfaces';
|
||||||
import { useGetChat } from '@/api/buster_rest/chats';
|
import { useGetChat } from '@/api/buster_rest/chats';
|
||||||
import { Dropdown, type IDropdownItems } from '@/components/ui/dropdown';
|
import { Dropdown, type IDropdownItems } from '@/components/ui/dropdown';
|
||||||
import { useGetChatId } from '@/context/Chats/useGetChatId';
|
import { useGetChatId } from '@/context/Chats/useGetChatId';
|
||||||
import { getIsEffectiveOwner } from '@/lib/share';
|
import { canEdit, getIsEffectiveOwner } from '@/lib/share';
|
||||||
import {
|
import {
|
||||||
useDeleteChatSelectMenu,
|
useDeleteChatSelectMenu,
|
||||||
useDuplicateChatSelectMenu,
|
useDuplicateChatSelectMenu,
|
||||||
|
@ -32,6 +32,7 @@ export const ChatContainerHeaderDropdown: React.FC<{
|
||||||
const deleteChat = useDeleteChatSelectMenu({ chatId });
|
const deleteChat = useDeleteChatSelectMenu({ chatId });
|
||||||
|
|
||||||
const isOwnerEffective = getIsEffectiveOwner(permission);
|
const isOwnerEffective = getIsEffectiveOwner(permission);
|
||||||
|
const canEditChat = canEdit(permission);
|
||||||
|
|
||||||
const menuItem: IDropdownItems = useMemo(() => {
|
const menuItem: IDropdownItems = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
|
@ -40,10 +41,20 @@ export const ChatContainerHeaderDropdown: React.FC<{
|
||||||
favoriteChat,
|
favoriteChat,
|
||||||
openInNewTab,
|
openInNewTab,
|
||||||
{ type: 'divider' },
|
{ type: 'divider' },
|
||||||
duplicateChat,
|
canEditChat && duplicateChat,
|
||||||
deleteChat,
|
canEditChat && deleteChat,
|
||||||
].filter(Boolean) as IDropdownItems;
|
].filter(Boolean) as IDropdownItems;
|
||||||
}, [chatId, duplicateChat, deleteChat, duplicateChat]);
|
}, [
|
||||||
|
chatId,
|
||||||
|
isOwnerEffective,
|
||||||
|
canEditChat,
|
||||||
|
shareMenu,
|
||||||
|
renameChatTitle,
|
||||||
|
favoriteChat,
|
||||||
|
openInNewTab,
|
||||||
|
duplicateChat,
|
||||||
|
deleteChat,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown align="end" items={menuItem}>
|
<Dropdown align="end" items={menuItem}>
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
export * from './useChatPermission';
|
||||||
export * from './useGetActiveChat';
|
export * from './useGetActiveChat';
|
||||||
export * from './useIsStreamingMessage';
|
export * from './useIsStreamingMessage';
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import type { ShareRole } from '@buster/server-shared/share';
|
||||||
|
import type { IBusterChat } from '../../api/asset_interfaces';
|
||||||
|
import { useGetChat } from '../../api/buster_rest/chats';
|
||||||
|
|
||||||
|
const stablePermissionSelector = (chat: IBusterChat): ShareRole => chat.permission;
|
||||||
|
|
||||||
|
export const useChatPermission = (chatId: string | undefined): ShareRole | undefined => {
|
||||||
|
const { data: permission } = useGetChat(
|
||||||
|
{ id: chatId || '' },
|
||||||
|
{
|
||||||
|
select: stablePermissionSelector,
|
||||||
|
enabled: !!chatId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return permission;
|
||||||
|
};
|
|
@ -1,8 +1,11 @@
|
||||||
import React, { type ChangeEvent, useEffect, useMemo, useRef, useState } from 'react';
|
import React, { type ChangeEvent, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { InputTextAreaButton } from '@/components/ui/inputs/InputTextAreaButton';
|
import { InputTextAreaButton } from '@/components/ui/inputs/InputTextAreaButton';
|
||||||
import { useIsStreamingMessage } from '@/context/Chats/useIsStreamingMessage';
|
import { Text } from '@/components/ui/typography';
|
||||||
|
import { useChatPermission, useIsStreamingMessage } from '@/context/Chats';
|
||||||
|
import { useGetChatId } from '@/context/Chats/useGetChatId';
|
||||||
import { useIsChatMode } from '@/context/Chats/useMode';
|
import { useIsChatMode } from '@/context/Chats/useMode';
|
||||||
import { cn } from '@/lib/classMerge';
|
import { cn } from '@/lib/classMerge';
|
||||||
|
import { canEdit } from '@/lib/share';
|
||||||
import { inputHasText } from '@/lib/text';
|
import { inputHasText } from '@/lib/text';
|
||||||
import { useChatInputFlow } from '../../../../context/Chats/useChatInputFlow';
|
import { useChatInputFlow } from '../../../../context/Chats/useChatInputFlow';
|
||||||
import { AIWarning } from './AIWarning';
|
import { AIWarning } from './AIWarning';
|
||||||
|
@ -11,12 +14,15 @@ export const ChatInput: React.FC = React.memo(() => {
|
||||||
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const isStreamingMessage = useIsStreamingMessage();
|
const isStreamingMessage = useIsStreamingMessage();
|
||||||
const hasChat = useIsChatMode();
|
const hasChat = useIsChatMode();
|
||||||
|
const chatId = useGetChatId();
|
||||||
|
const permission = useChatPermission(chatId);
|
||||||
|
const canEditChat = canEdit(permission);
|
||||||
|
|
||||||
const [inputValue, setInputValue] = useState('');
|
const [inputValue, setInputValue] = useState('');
|
||||||
|
|
||||||
const disableSubmit = useMemo(() => {
|
const disableSubmit = useMemo(() => {
|
||||||
return !inputHasText(inputValue) && !isStreamingMessage;
|
return (!inputHasText(inputValue) && !isStreamingMessage) || !canEditChat;
|
||||||
}, [inputValue, isStreamingMessage]);
|
}, [inputValue, isStreamingMessage, canEditChat]);
|
||||||
|
|
||||||
const { onSubmitPreflight, onStopChat } = useChatInputFlow({
|
const { onSubmitPreflight, onStopChat } = useChatInputFlow({
|
||||||
disableSubmit,
|
disableSubmit,
|
||||||
|
@ -44,22 +50,31 @@ export const ChatInput: React.FC = React.memo(() => {
|
||||||
'z-10 mx-3 mt-0.5 mb-2 flex min-h-fit flex-col items-center space-y-1.5 overflow-visible'
|
'z-10 mx-3 mt-0.5 mb-2 flex min-h-fit flex-col items-center space-y-1.5 overflow-visible'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<InputTextAreaButton
|
{!canEditChat ? (
|
||||||
placeholder="Ask Buster a question..."
|
<div className="w-full p-4 bg-muted/50 rounded-lg border">
|
||||||
minRows={2}
|
<Text variant="secondary" size="sm" className="text-center">
|
||||||
maxRows={16}
|
This chat is view-only. You don't have permission to send messages.
|
||||||
onSubmit={onSubmitPreflight}
|
</Text>
|
||||||
onChange={onChange}
|
</div>
|
||||||
onStop={onStopChat}
|
) : (
|
||||||
loading={isStreamingMessage}
|
<>
|
||||||
value={inputValue}
|
<InputTextAreaButton
|
||||||
disabled={!hasChat}
|
placeholder="Ask Buster a question..."
|
||||||
disabledSubmit={disableSubmit}
|
minRows={2}
|
||||||
autoFocus
|
maxRows={16}
|
||||||
ref={textAreaRef}
|
onSubmit={onSubmitPreflight}
|
||||||
/>
|
onChange={onChange}
|
||||||
|
onStop={onStopChat}
|
||||||
<AIWarning />
|
loading={isStreamingMessage}
|
||||||
|
value={inputValue}
|
||||||
|
disabled={!hasChat || !canEditChat}
|
||||||
|
disabledSubmit={disableSubmit}
|
||||||
|
autoFocus
|
||||||
|
ref={textAreaRef}
|
||||||
|
/>
|
||||||
|
<AIWarning />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,8 +12,10 @@ import { ThumbsDown as ThumbsDownFilled } from '@/components/ui/icons/NucleoIcon
|
||||||
import { AppTooltip } from '@/components/ui/tooltip';
|
import { AppTooltip } from '@/components/ui/tooltip';
|
||||||
import { Text } from '@/components/ui/typography';
|
import { Text } from '@/components/ui/typography';
|
||||||
import { useBusterNotifications } from '@/context/BusterNotifications';
|
import { useBusterNotifications } from '@/context/BusterNotifications';
|
||||||
|
import { useChatPermission } from '@/context/Chats';
|
||||||
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
|
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
|
||||||
import { formatDate } from '@/lib/date';
|
import { formatDate } from '@/lib/date';
|
||||||
|
import { canEdit } from '@/lib/share';
|
||||||
import { timeout } from '@/lib/timeout';
|
import { timeout } from '@/lib/timeout';
|
||||||
|
|
||||||
export const ChatMessageOptions: React.FC<{
|
export const ChatMessageOptions: React.FC<{
|
||||||
|
@ -24,6 +26,8 @@ export const ChatMessageOptions: React.FC<{
|
||||||
const { openConfirmModal } = useBusterNotifications();
|
const { openConfirmModal } = useBusterNotifications();
|
||||||
const { mutateAsync: duplicateChat, isPending: isCopying } = useDuplicateChat();
|
const { mutateAsync: duplicateChat, isPending: isCopying } = useDuplicateChat();
|
||||||
const { mutateAsync: updateChatMessageFeedback } = useUpdateChatMessageFeedback();
|
const { mutateAsync: updateChatMessageFeedback } = useUpdateChatMessageFeedback();
|
||||||
|
const permission = useChatPermission(chatId);
|
||||||
|
const canEditChat = canEdit(permission);
|
||||||
const { data: feedback } = useGetChatMessage(messageId, {
|
const { data: feedback } = useGetChatMessage(messageId, {
|
||||||
select: ({ feedback }) => feedback,
|
select: ({ feedback }) => feedback,
|
||||||
});
|
});
|
||||||
|
@ -65,14 +69,16 @@ export const ChatMessageOptions: React.FC<{
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<AppTooltip title="Duplicate chat from this message">
|
{canEditChat && (
|
||||||
<Button
|
<AppTooltip title="Duplicate chat from this message">
|
||||||
variant="ghost"
|
<Button
|
||||||
prefix={<DuplicatePlus />}
|
variant="ghost"
|
||||||
loading={isCopying}
|
prefix={<DuplicatePlus />}
|
||||||
onClick={warnBeforeDuplicate}
|
loading={isCopying}
|
||||||
/>
|
onClick={warnBeforeDuplicate}
|
||||||
</AppTooltip>
|
/>
|
||||||
|
</AppTooltip>
|
||||||
|
)}
|
||||||
<AppTooltip title="Report message">
|
<AppTooltip title="Report message">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|
|
@ -7,9 +7,11 @@ import { InputTextArea } from '@/components/ui/inputs/InputTextArea';
|
||||||
import { Tooltip } from '@/components/ui/tooltip';
|
import { Tooltip } from '@/components/ui/tooltip';
|
||||||
import { Paragraph } from '@/components/ui/typography';
|
import { Paragraph } from '@/components/ui/typography';
|
||||||
import { useBusterNotifications } from '@/context/BusterNotifications';
|
import { useBusterNotifications } from '@/context/BusterNotifications';
|
||||||
|
import { useChatPermission } from '@/context/Chats';
|
||||||
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
|
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
|
||||||
import { useMount } from '@/hooks/useMount';
|
import { useMount } from '@/hooks/useMount';
|
||||||
import { cn } from '@/lib/classMerge';
|
import { cn } from '@/lib/classMerge';
|
||||||
|
import { canEdit } from '@/lib/share';
|
||||||
import { useChat } from '../../../context/Chats/useChat';
|
import { useChat } from '../../../context/Chats/useChat';
|
||||||
import { MessageContainer } from './MessageContainer';
|
import { MessageContainer } from './MessageContainer';
|
||||||
|
|
||||||
|
@ -22,6 +24,8 @@ export const ChatUserMessage: React.FC<{
|
||||||
const { openSuccessMessage } = useBusterNotifications();
|
const { openSuccessMessage } = useBusterNotifications();
|
||||||
const [isTooltipOpen, setIsTooltipOpen] = useState(false);
|
const [isTooltipOpen, setIsTooltipOpen] = useState(false);
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
|
const permission = useChatPermission(chatId);
|
||||||
|
const canEditChat = canEdit(permission);
|
||||||
|
|
||||||
const { sender_avatar, sender_id, sender_name, request } = requestMessage;
|
const { sender_avatar, sender_id, sender_name, request } = requestMessage;
|
||||||
|
|
||||||
|
@ -67,7 +71,7 @@ export const ChatUserMessage: React.FC<{
|
||||||
{request}
|
{request}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</div>
|
</div>
|
||||||
{isStreamFinished && (
|
{isStreamFinished && canEditChat && (
|
||||||
<RequestMessageTooltip
|
<RequestMessageTooltip
|
||||||
isTooltipOpen={isTooltipOpen}
|
isTooltipOpen={isTooltipOpen}
|
||||||
setIsEditing={setIsEditing}
|
setIsEditing={setIsEditing}
|
||||||
|
|
|
@ -66,12 +66,10 @@
|
||||||
"packageManager": "pnpm@10.15.1",
|
"packageManager": "pnpm@10.15.1",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"peerDependencyRules": {
|
"peerDependencyRules": {
|
||||||
"ignoreMissing": [
|
"ignoreMissing": ["shiki"],
|
||||||
"shiki"
|
|
||||||
],
|
|
||||||
"allowedVersions": {
|
"allowedVersions": {
|
||||||
"shiki": "3"
|
"shiki": "3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { and, eq, isNull } from 'drizzle-orm';
|
import { and, eq, isNull } from 'drizzle-orm';
|
||||||
import { type InferSelectModel } from 'drizzle-orm';
|
import type { InferSelectModel } from 'drizzle-orm';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { db } from '../../connection';
|
import { db } from '../../connection';
|
||||||
import { reportFiles } from '../../schema';
|
import { reportFiles } from '../../schema';
|
||||||
|
|
Loading…
Reference in New Issue