mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-18 02:26:21 +08:00
feat: refactor copilot module (#14537)
This commit is contained in:
@@ -1,146 +0,0 @@
|
||||
import { ScrollArea } from '@affine/admin/components/ui/scroll-area';
|
||||
import { Separator } from '@affine/admin/components/ui/separator';
|
||||
import { Textarea } from '@affine/admin/components/ui/textarea';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { RightPanelHeader } from '../header';
|
||||
import { useRightPanel } from '../panel/context';
|
||||
import type { Prompt } from './prompts';
|
||||
import { usePrompt } from './use-prompt';
|
||||
|
||||
export function EditPrompt({
|
||||
item,
|
||||
setCanSave,
|
||||
}: {
|
||||
item: Prompt;
|
||||
setCanSave: (changed: boolean) => void;
|
||||
}) {
|
||||
const { closePanel } = useRightPanel();
|
||||
|
||||
const [messages, setMessages] = useState(item.messages);
|
||||
const { updatePrompt } = usePrompt();
|
||||
|
||||
const disableSave = useMemo(
|
||||
() => JSON.stringify(messages) === JSON.stringify(item.messages),
|
||||
[item.messages, messages]
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLTextAreaElement>, index: number) => {
|
||||
const newMessages = [...messages];
|
||||
newMessages[index] = {
|
||||
...newMessages[index],
|
||||
content: e.target.value,
|
||||
};
|
||||
setMessages(newMessages);
|
||||
setCanSave(!disableSave);
|
||||
},
|
||||
[disableSave, messages, setCanSave]
|
||||
);
|
||||
const handleClose = useCallback(() => {
|
||||
setMessages(item.messages);
|
||||
closePanel();
|
||||
}, [closePanel, item.messages]);
|
||||
|
||||
const onConfirm = useCallback(() => {
|
||||
if (!disableSave) {
|
||||
updatePrompt({ name: item.name, messages });
|
||||
}
|
||||
handleClose();
|
||||
}, [disableSave, handleClose, item.name, messages, updatePrompt]);
|
||||
|
||||
useEffect(() => {
|
||||
setMessages(item.messages);
|
||||
}, [item.messages]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full gap-1">
|
||||
<RightPanelHeader
|
||||
title="Edit Prompt"
|
||||
handleClose={handleClose}
|
||||
handleConfirm={onConfirm}
|
||||
canSave={!disableSave}
|
||||
/>
|
||||
<ScrollArea>
|
||||
<div className="grid">
|
||||
<div className="px-5 py-4 overflow-y-auto space-y-[10px] flex flex-col gap-5">
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-medium">Name</div>
|
||||
<div className="text-sm font-normal text-muted-foreground">
|
||||
{item.name}
|
||||
</div>
|
||||
</div>
|
||||
{item.action ? (
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-medium">Action</div>
|
||||
<div className="text-sm font-normal text-muted-foreground">
|
||||
{item.action}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm font-medium">Model</div>
|
||||
<div className="text-sm font-normal text-muted-foreground">
|
||||
{item.model}
|
||||
</div>
|
||||
</div>
|
||||
{item.config ? (
|
||||
<div className="flex flex-col border rounded p-3">
|
||||
<div className="text-sm font-medium">Config</div>
|
||||
{Object.entries(item.config).map(([key, value], index) => (
|
||||
<div key={key} className="flex flex-col">
|
||||
{index !== 0 && <Separator />}
|
||||
<span className="text-sm font-normal">{key}</span>
|
||||
<span className="text-sm font-normal text-muted-foreground">
|
||||
{value?.toString()}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="px-5 py-4 overflow-y-auto space-y-[10px] flex flex-col">
|
||||
<div className="text-sm font-medium">Messages</div>
|
||||
{messages.map((message, index) => (
|
||||
<div key={message.content} className="flex flex-col gap-3">
|
||||
{index !== 0 && <Separator />}
|
||||
<div>
|
||||
<div className="text-sm font-normal">Role</div>
|
||||
<div className="text-sm font-normal text-muted-foreground">
|
||||
{message.role}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{message.params ? (
|
||||
<div>
|
||||
<div className="text-sm font-medium">Params</div>
|
||||
{Object.entries(message.params).map(
|
||||
([key, value], index) => (
|
||||
<div key={key} className="flex flex-col">
|
||||
{index !== 0 && <Separator />}
|
||||
<span className="text-sm font-normal">{key}</span>
|
||||
<span
|
||||
className="text-sm font-normal text-muted-foreground"
|
||||
style={{ overflowWrap: 'break-word' }}
|
||||
>
|
||||
{value.toString()}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
<div className="text-sm font-normal">Content</div>
|
||||
<Textarea
|
||||
className=" min-h-48"
|
||||
value={message.content}
|
||||
onChange={e => handleChange(e, index)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -32,7 +32,6 @@ function AiPage() {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* <Prompts /> */}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
className={cn(
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
import { Button } from '@affine/admin/components/ui/button';
|
||||
import { Separator } from '@affine/admin/components/ui/separator';
|
||||
import type { CopilotPromptMessageRole } from '@affine/graphql';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { DiscardChanges } from '../../components/shared/discard-changes';
|
||||
import { useRightPanel } from '../panel/context';
|
||||
import { EditPrompt } from './edit-prompt';
|
||||
import { usePrompt } from './use-prompt';
|
||||
|
||||
export type Prompt = {
|
||||
__typename?: 'CopilotPromptType';
|
||||
name: string;
|
||||
model: string;
|
||||
action: string | null;
|
||||
config: {
|
||||
__typename?: 'CopilotPromptConfigType';
|
||||
frequencyPenalty: number | null;
|
||||
presencePenalty: number | null;
|
||||
temperature: number | null;
|
||||
topP: number | null;
|
||||
} | null;
|
||||
messages: Array<{
|
||||
__typename?: 'CopilotPromptMessageType';
|
||||
role: CopilotPromptMessageRole;
|
||||
content: string;
|
||||
params: Record<string, string> | null;
|
||||
}>;
|
||||
};
|
||||
|
||||
export function Prompts() {
|
||||
const { prompts: list } = usePrompt();
|
||||
return (
|
||||
<div className="flex flex-col h-full gap-3 py-5 px-6 w-full">
|
||||
<div className="flex items-center">
|
||||
<span className="text-xl font-semibold">Prompts</span>
|
||||
</div>
|
||||
<div className="flex-grow overflow-y-auto space-y-[10px]">
|
||||
<div className="flex flex-col rounded-md border w-full">
|
||||
{list.map((item, index) => (
|
||||
<PromptRow
|
||||
key={`${item.name}-${index}`}
|
||||
item={item}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const PromptRow = ({ item, index }: { item: Prompt; index: number }) => {
|
||||
const { setPanelContent, openPanel, isOpen } = useRightPanel();
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [canSave, setCanSave] = useState(false);
|
||||
|
||||
const handleDiscardChangesCancel = useCallback(() => {
|
||||
setDialogOpen(false);
|
||||
setCanSave(false);
|
||||
}, []);
|
||||
|
||||
const handleConfirm = useCallback(
|
||||
(item: Prompt) => {
|
||||
setPanelContent(<EditPrompt item={item} setCanSave={setCanSave} />);
|
||||
if (dialogOpen) {
|
||||
handleDiscardChangesCancel();
|
||||
}
|
||||
|
||||
if (!isOpen) {
|
||||
openPanel();
|
||||
}
|
||||
},
|
||||
[dialogOpen, handleDiscardChangesCancel, isOpen, openPanel, setPanelContent]
|
||||
);
|
||||
|
||||
const handleEdit = useCallback(
|
||||
(item: Prompt) => {
|
||||
if (isOpen && canSave) {
|
||||
setDialogOpen(true);
|
||||
} else {
|
||||
handleConfirm(item);
|
||||
}
|
||||
},
|
||||
[canSave, handleConfirm, isOpen]
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
{index !== 0 && <Separator />}
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="flex flex-col gap-1 w-full items-start px-6 py-[14px] h-full "
|
||||
onClick={() => handleEdit(item)}
|
||||
>
|
||||
<div>{item.name}</div>
|
||||
<div className="text-left w-full opacity-50 overflow-hidden text-ellipsis whitespace-nowrap break-words text-nowrap">
|
||||
{item.messages.flatMap(message => message.content).join(' ')}
|
||||
</div>
|
||||
</Button>
|
||||
<DiscardChanges
|
||||
open={dialogOpen}
|
||||
onOpenChange={setDialogOpen}
|
||||
onClose={handleDiscardChangesCancel}
|
||||
onConfirm={() => handleConfirm(item)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,51 +0,0 @@
|
||||
import {
|
||||
useMutateQueryResource,
|
||||
useMutation,
|
||||
} from '@affine/admin/use-mutation';
|
||||
import { useQuery } from '@affine/admin/use-query';
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import { getPromptsQuery, updatePromptMutation } from '@affine/graphql';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import type { Prompt } from './prompts';
|
||||
|
||||
export const usePrompt = () => {
|
||||
const { data } = useQuery({
|
||||
query: getPromptsQuery,
|
||||
});
|
||||
|
||||
const { trigger } = useMutation({
|
||||
mutation: updatePromptMutation,
|
||||
});
|
||||
|
||||
const revalidate = useMutateQueryResource();
|
||||
|
||||
const updatePrompt = useAsyncCallback(
|
||||
async ({
|
||||
name,
|
||||
messages,
|
||||
}: {
|
||||
name: string;
|
||||
messages: Prompt['messages'];
|
||||
}) => {
|
||||
await trigger({
|
||||
name,
|
||||
messages,
|
||||
})
|
||||
.then(async () => {
|
||||
await revalidate(getPromptsQuery);
|
||||
toast.success('Prompt updated successfully');
|
||||
})
|
||||
.catch(e => {
|
||||
toast(e.message);
|
||||
console.error(e);
|
||||
});
|
||||
},
|
||||
[revalidate, trigger]
|
||||
);
|
||||
|
||||
return {
|
||||
prompts: data.listCopilotPrompts,
|
||||
updatePrompt,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user