mirror of
https://github.com/dataelement/bisheng.git
synced 2026-09-24 23:19:52 +08:00
fix: 恢复被忽略文件
This commit is contained in:
@@ -140,7 +140,6 @@ eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { imageExtRegex } from '~/data-provider/data-provider/src';
|
||||
import type { TAttachment, TFile, TAttachmentMetadata } from '~/data-provider/data-provider/src';
|
||||
import Image from '~/components/Chat/Messages/Content/Image';
|
||||
|
||||
export default function Attachment({ attachment }: { attachment?: TAttachment }) {
|
||||
if (!attachment) {
|
||||
return null;
|
||||
}
|
||||
const { width, height, filepath = null } = attachment as TFile & TAttachmentMetadata;
|
||||
const isImage =
|
||||
imageExtRegex.test(attachment.filename) && width != null && height != null && filepath != null;
|
||||
|
||||
if (isImage) {
|
||||
return (
|
||||
<Image altText={attachment.filename} imagePath={filepath} height={height} width={width} />
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import ProgressCircle from '~/components/Chat/Messages/Content/ProgressCircle';
|
||||
import CancelledIcon from '~/components/Chat/Messages/Content/CancelledIcon';
|
||||
|
||||
export const CodeInProgress = ({
|
||||
offset,
|
||||
circumference,
|
||||
radius,
|
||||
isSubmitting,
|
||||
progress,
|
||||
}: {
|
||||
progress: number;
|
||||
offset: number;
|
||||
circumference: number;
|
||||
radius: number;
|
||||
isSubmitting: boolean;
|
||||
}) => {
|
||||
if (progress < 1 && !isSubmitting) {
|
||||
return <CancelledIcon />;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="absolute left-0 top-0 flex h-full w-full items-center justify-center rounded-full bg-transparent text-white"
|
||||
style={{ opacity: 1, transform: 'none' }}
|
||||
data-projection-id="77"
|
||||
>
|
||||
<div className="absolute bottom-[1.5px] right-[1.5px]">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 20 20"
|
||||
width="20"
|
||||
height="20"
|
||||
style={{ transform: 'translate3d(0px, 0px, 0px)' }}
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<defs>
|
||||
<clipPath id="__lottie_element_11">
|
||||
<rect width="20" height="20" x="0" y="0" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clipPath="url(#__lottie_element_11)">
|
||||
<g
|
||||
style={{ display: 'block', transform: 'matrix(1,0,0,1,-2,-2)', opacity: 1 }}
|
||||
className="slide-from-left"
|
||||
>
|
||||
<g opacity="1" transform="matrix(1,0,0,1,7.026679992675781,8.834091186523438)">
|
||||
<path
|
||||
fill="rgb(177,98,253)"
|
||||
fillOpacity="1"
|
||||
d=" M1.2870399951934814,0.2207774966955185 C0.992609977722168,-0.07359249889850616 0.5152599811553955,-0.07359249889850616 0.22082999348640442,0.2207774966955185 C-0.07361000031232834,0.5151575207710266 -0.07361000031232834,0.992437481880188 0.22082999348640442,1.2868175506591797 C0.8473266959190369,1.9131841659545898 1.4738233089447021,2.53955078125 2.1003201007843018,3.16591739654541 C1.4738233089447021,3.7922842502593994 0.8473266959190369,4.4186506271362305 0.22082999348640442,5.045017719268799 C-0.07361000031232834,5.339417457580566 -0.07361000031232834,5.816617488861084 0.22082999348640442,6.11101770401001 C0.5152599811553955,6.405417442321777 0.992609977722168,6.405417442321777 1.2870399951934814,6.11101770401001 C2.091266632080078,5.306983947753906 2.895493268966675,4.502950668334961 3.6997199058532715,3.6989173889160156 C3.994119882583618,3.404517412185669 3.994119882583618,2.927217483520508 3.6997199058532715,2.6329174041748047 C2.895493268966675,1.8288708925247192 2.091266632080078,1.0248241424560547 1.2870399951934814,0.2207774966955185 C1.2870399951934814,0.2207774966955185 1.2870399951934814,0.2207774966955185 1.2870399951934814,0.2207774966955185 C1.2870399951934814,0.2207774966955185 1.2870399951934814,0.2207774966955185 1.2870399951934814,0.2207774966955185"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
fillOpacity="0"
|
||||
stroke="rgb(177,98,253)"
|
||||
strokeOpacity="1"
|
||||
strokeWidth="0.201031"
|
||||
d=" M1.2870399951934814,0.2207774966955185 C0.992609977722168,-0.07359249889850616 0.5152599811553955,-0.07359249889850616 0.22082999348640442,0.2207774966955185 C-0.07361000031232834,0.5151575207710266 -0.07361000031232834,0.992437481880188 0.22082999348640442,1.2868175506591797 C0.8473266959190369,1.9131841659545898 1.4738233089447021,2.53955078125 2.1003201007843018,3.16591739654541 C1.4738233089447021,3.7922842502593994 0.8473266959190369,4.4186506271362305 0.22082999348640442,5.045017719268799 C-0.07361000031232834,5.339417457580566 -0.07361000031232834,5.816617488861084 0.22082999348640442,6.11101770401001 C0.5152599811553955,6.405417442321777 0.992609977722168,6.405417442321777 1.2870399951934814,6.11101770401001 C2.091266632080078,5.306983947753906 2.895493268966675,4.502950668334961 3.6997199058532715,3.6989173889160156 C3.994119882583618,3.404517412185669 3.994119882583618,2.927217483520508 3.6997199058532715,2.6329174041748047 C2.895493268966675,1.8288708925247192 2.091266632080078,1.0248241424560547 1.2870399951934814,0.2207774966955185 C1.2870399951934814,0.2207774966955185 1.2870399951934814,0.2207774966955185 1.2870399951934814,0.2207774966955185 C1.2870399951934814,0.2207774966955185 1.2870399951934814,0.2207774966955185 1.2870399951934814,0.2207774966955185"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
style={{ display: 'block', transform: 'matrix(1,0,0,1,-2,-2)', opacity: 1 }}
|
||||
className="slide-to-down"
|
||||
>
|
||||
<g opacity="1" transform="matrix(1,0,0,1,11.79640007019043,13.512199401855469)">
|
||||
<path
|
||||
fill="rgb(177,98,253)"
|
||||
fillOpacity="1"
|
||||
d=" M4.3225998878479,0 C3.1498000621795654,0 1.9769999980926514,0 0.8041999936103821,0 C0.36010000109672546,0 0,0.36000001430511475 0,0.804099977016449 C0,1.2482000589370728 0.36010000109672546,1.6081000566482544 0.8041999936103821,1.6081000566482544 C1.9769999980926514,1.6081000566482544 3.1498000621795654,1.6081000566482544 4.3225998878479,1.6081000566482544 C4.7667999267578125,1.6081000566482544 5.126800060272217,1.2482000589370728 5.126800060272217,0.804099977016449 C5.126800060272217,0.36000001430511475 4.7667999267578125,0 4.3225998878479,0 C4.3225998878479,0 4.3225998878479,0 4.3225998878479,0 C4.3225998878479,0 4.3225998878479,0 4.3225998878479,0"
|
||||
/>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
fillOpacity="0"
|
||||
stroke="rgb(177,98,253)"
|
||||
strokeOpacity="1"
|
||||
strokeWidth="0.100515"
|
||||
d=" M4.3225998878479,0 C3.1498000621795654,0 1.9769999980926514,0 0.8041999936103821,0 C0.36010000109672546,0 0,0.36000001430511475 0,0.804099977016449 C0,1.2482000589370728 0.36010000109672546,1.6081000566482544 0.8041999936103821,1.6081000566482544 C1.9769999980926514,1.6081000566482544 3.1498000621795654,1.6081000566482544 4.3225998878479,1.6081000566482544 C4.7667999267578125,1.6081000566482544 5.126800060272217,1.2482000589370728 5.126800060272217,0.804099977016449 C5.126800060272217,0.36000001430511475 4.7667999267578125,0 4.3225998878479,0 C4.3225998878479,0 4.3225998878479,0 4.3225998878479,0 C4.3225998878479,0 4.3225998878479,0 4.3225998878479,0"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
<ProgressCircle radius={radius} circumference={circumference} offset={offset} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,192 @@
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { ContentTypes } from '~/data-provider/data-provider/src';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { useRef, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useUpdateMessageContentMutation } from '~/data-provider/data-provider/src/react-query';
|
||||
import type { TEditProps } from '~/common';
|
||||
import Container from '~/components/Chat/Messages/Content/Container';
|
||||
import { useChatContext, useAddedChatContext } from '~/Providers';
|
||||
import { TextareaAutosize } from '~/components/ui';
|
||||
import { cn, removeFocusRings } from '~/utils';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import store from '~/store';
|
||||
|
||||
const EditTextPart = ({
|
||||
text,
|
||||
index,
|
||||
messageId,
|
||||
isSubmitting,
|
||||
enterEdit,
|
||||
}: Omit<TEditProps, 'message' | 'ask'> & {
|
||||
index: number;
|
||||
messageId: string;
|
||||
}) => {
|
||||
const localize = useLocalize();
|
||||
const { addedIndex } = useAddedChatContext();
|
||||
const { getMessages, setMessages, conversation } = useChatContext();
|
||||
const [latestMultiMessage, setLatestMultiMessage] = useRecoilState(
|
||||
store.latestMessageFamily(addedIndex),
|
||||
);
|
||||
|
||||
const { conversationId = '' } = conversation ?? {};
|
||||
const message = useMemo(
|
||||
() => getMessages()?.find((msg) => msg.messageId === messageId),
|
||||
[getMessages, messageId],
|
||||
);
|
||||
|
||||
const textAreaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const updateMessageContentMutation = useUpdateMessageContentMutation(conversationId ?? '');
|
||||
|
||||
const chatDirection = useRecoilValue(store.chatDirection).toLowerCase();
|
||||
const isRTL = chatDirection === 'rtl';
|
||||
|
||||
const { register, handleSubmit, setValue } = useForm({
|
||||
defaultValues: {
|
||||
text: text ?? '',
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const textArea = textAreaRef.current;
|
||||
if (textArea) {
|
||||
const length = textArea.value.length;
|
||||
textArea.focus();
|
||||
textArea.setSelectionRange(length, length);
|
||||
}
|
||||
}, []);
|
||||
|
||||
/*
|
||||
const resubmitMessage = () => {
|
||||
showToast({
|
||||
status: 'warning',
|
||||
message: localize('com_warning_resubmit_unsupported'),
|
||||
});
|
||||
|
||||
// const resubmitMessage = (data: { text: string }) => {
|
||||
// Not supported by AWS Bedrock
|
||||
const messages = getMessages();
|
||||
const parentMessage = messages?.find((msg) => msg.messageId === message?.parentMessageId);
|
||||
|
||||
if (!parentMessage) {
|
||||
return;
|
||||
}
|
||||
ask(
|
||||
{ ...parentMessage },
|
||||
{
|
||||
editedText: data.text,
|
||||
editedMessageId: messageId,
|
||||
isRegenerate: true,
|
||||
isEdited: true,
|
||||
},
|
||||
);
|
||||
|
||||
setSiblingIdx((siblingIdx ?? 0) - 1);
|
||||
enterEdit(true);
|
||||
};
|
||||
*/
|
||||
|
||||
const updateMessage = (data: { text: string }) => {
|
||||
const messages = getMessages();
|
||||
if (!messages) {
|
||||
return;
|
||||
}
|
||||
updateMessageContentMutation.mutate({
|
||||
index,
|
||||
conversationId: conversationId ?? '',
|
||||
text: data.text,
|
||||
messageId,
|
||||
});
|
||||
|
||||
if (messageId === latestMultiMessage?.messageId) {
|
||||
setLatestMultiMessage({ ...latestMultiMessage, text: data.text });
|
||||
}
|
||||
|
||||
const isInMessages = messages.some((msg) => msg.messageId === messageId);
|
||||
if (!isInMessages) {
|
||||
return enterEdit(true);
|
||||
}
|
||||
|
||||
const updatedContent = message?.content?.map((part, idx) => {
|
||||
if (part.type === ContentTypes.TEXT && idx === index) {
|
||||
return { ...part, text: data.text };
|
||||
}
|
||||
return part;
|
||||
});
|
||||
|
||||
setMessages(
|
||||
messages.map((msg) =>
|
||||
msg.messageId === messageId
|
||||
? {
|
||||
...msg,
|
||||
content: updatedContent,
|
||||
}
|
||||
: msg,
|
||||
),
|
||||
);
|
||||
|
||||
enterEdit(true);
|
||||
};
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
enterEdit(true);
|
||||
}
|
||||
},
|
||||
[enterEdit],
|
||||
);
|
||||
|
||||
const { ref, ...registerProps } = register('text', {
|
||||
required: true,
|
||||
onChange: (e) => {
|
||||
setValue('text', e.target.value, { shouldValidate: true });
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Container message={message}>
|
||||
<div className="bg-token-main-surface-primary relative flex w-full flex-grow flex-col overflow-hidden rounded-2xl border border-border-medium text-text-primary [&:has(textarea:focus)]:border-border-heavy [&:has(textarea:focus)]:shadow-[0_2px_6px_rgba(0,0,0,.05)]">
|
||||
<TextareaAutosize
|
||||
{...registerProps}
|
||||
ref={(e) => {
|
||||
ref(e);
|
||||
textAreaRef.current = e;
|
||||
}}
|
||||
onKeyDown={handleKeyDown}
|
||||
data-testid="message-text-editor"
|
||||
className={cn(
|
||||
'markdown prose dark:prose-invert light whitespace-pre-wrap break-words pl-3 md:pl-4',
|
||||
'm-0 w-full resize-none border-0 bg-transparent py-[10px]',
|
||||
'placeholder-text-secondary focus:ring-0 focus-visible:ring-0 md:py-3.5',
|
||||
isRTL ? 'text-right' : 'text-left',
|
||||
'max-h-[65vh] pr-3 md:max-h-[75vh] md:pr-4',
|
||||
removeFocusRings,
|
||||
)}
|
||||
dir={isRTL ? 'rtl' : 'ltr'}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 flex w-full justify-center text-center">
|
||||
{/* <button
|
||||
className="btn btn-primary relative mr-2"
|
||||
disabled={isSubmitting}
|
||||
onClick={handleSubmit(resubmitMessage)}
|
||||
>
|
||||
{localize('com_ui_save_submit')}
|
||||
</button> */}
|
||||
<button
|
||||
className="btn btn-secondary relative mr-2"
|
||||
disabled={isSubmitting}
|
||||
onClick={handleSubmit(updateMessage)}
|
||||
>
|
||||
{localize('com_ui_save')}
|
||||
</button>
|
||||
<button className="btn btn-neutral relative" onClick={() => enterEdit(true)}>
|
||||
{localize('com_ui_cancel')}
|
||||
</button>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditTextPart;
|
||||
@@ -0,0 +1,113 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import type { TAttachment } from '~/data-provider/data-provider/src';
|
||||
import ProgressText from '~/components/Chat/Messages/Content/ProgressText';
|
||||
import FinishedIcon from '~/components/Chat/Messages/Content/FinishedIcon';
|
||||
import MarkdownLite from '~/components/Chat/Messages/Content/MarkdownLite';
|
||||
import { useProgress, useLocalize } from '~/hooks';
|
||||
import { CodeInProgress } from './CodeProgress';
|
||||
import Attachment from './Attachment';
|
||||
import LogContent from './LogContent';
|
||||
import store from '~/store';
|
||||
|
||||
interface ParsedArgs {
|
||||
lang: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
export function useParseArgs(args: string): ParsedArgs {
|
||||
return useMemo(() => {
|
||||
const langMatch = args.match(/"lang"\s*:\s*"(\w+)"/);
|
||||
const codeMatch = args.match(/"code"\s*:\s*"(.+?)(?="\s*,\s*"args"|$)/s);
|
||||
|
||||
let code = '';
|
||||
if (codeMatch) {
|
||||
code = codeMatch[1];
|
||||
if (code.endsWith('"}')) {
|
||||
code = code.slice(0, -2);
|
||||
}
|
||||
code = code.replace(/\\n/g, '\n').replace(/\\/g, '');
|
||||
}
|
||||
|
||||
return {
|
||||
lang: langMatch ? langMatch[1] : '',
|
||||
code,
|
||||
};
|
||||
}, [args]);
|
||||
}
|
||||
|
||||
const radius = 56.08695652173913;
|
||||
const circumference = 2 * Math.PI * radius;
|
||||
|
||||
export default function ExecuteCode({
|
||||
initialProgress = 0.1,
|
||||
args,
|
||||
output = '',
|
||||
isSubmitting,
|
||||
attachments,
|
||||
}: {
|
||||
initialProgress: number;
|
||||
args: string;
|
||||
output?: string;
|
||||
isSubmitting: boolean;
|
||||
attachments?: TAttachment[];
|
||||
}) {
|
||||
const localize = useLocalize();
|
||||
const showAnalysisCode = useRecoilValue(store.showCode);
|
||||
const [showCode, setShowCode] = useState(showAnalysisCode);
|
||||
|
||||
const { lang, code } = useParseArgs(args);
|
||||
const progress = useProgress(initialProgress);
|
||||
const offset = circumference - progress * circumference;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="my-2.5 flex items-center gap-2.5">
|
||||
<div className="relative h-5 w-5 shrink-0">
|
||||
{progress < 1 ? (
|
||||
<CodeInProgress
|
||||
offset={offset}
|
||||
radius={radius}
|
||||
progress={progress}
|
||||
isSubmitting={isSubmitting}
|
||||
circumference={circumference}
|
||||
/>
|
||||
) : (
|
||||
<FinishedIcon />
|
||||
)}
|
||||
</div>
|
||||
<ProgressText
|
||||
progress={progress}
|
||||
onClick={() => setShowCode((prev) => !prev)}
|
||||
inProgressText={localize('com_ui_analyzing')}
|
||||
finishedText={localize('com_ui_analyzing_finished')}
|
||||
hasInput={!!code.length}
|
||||
isExpanded={showCode}
|
||||
/>
|
||||
</div>
|
||||
{showCode && (
|
||||
<div className="code-analyze-block mb-3 mt-0.5 overflow-hidden rounded-xl bg-black">
|
||||
<MarkdownLite
|
||||
content={code ? `\`\`\`${lang}\n${code}\n\`\`\`` : ''}
|
||||
codeExecution={false}
|
||||
/>
|
||||
{output.length > 0 && (
|
||||
<div className="bg-gray-700 p-4 text-xs">
|
||||
<div
|
||||
className="prose flex flex-col-reverse text-white"
|
||||
style={{
|
||||
color: 'white',
|
||||
}}
|
||||
>
|
||||
<pre className="shrink-0">
|
||||
<LogContent output={output} attachments={attachments} />
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{attachments?.map((attachment, index) => <Attachment attachment={attachment} key={index} />)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import { isAfter } from 'date-fns';
|
||||
import React, { useMemo } from 'react';
|
||||
import { imageExtRegex } from '~/data-provider/data-provider/src';
|
||||
import type { TFile, TAttachment, TAttachmentMetadata } from '~/data-provider/data-provider/src';
|
||||
import Image from '~/components/Chat/Messages/Content/Image';
|
||||
import { useLocalize } from '~/hooks';
|
||||
import LogLink from './LogLink';
|
||||
|
||||
interface LogContentProps {
|
||||
output?: string;
|
||||
renderImages?: boolean;
|
||||
attachments?: TAttachment[];
|
||||
}
|
||||
|
||||
type ImageAttachment = TFile &
|
||||
TAttachmentMetadata & {
|
||||
height: number;
|
||||
width: number;
|
||||
};
|
||||
|
||||
const LogContent: React.FC<LogContentProps> = ({ output = '', renderImages, attachments }) => {
|
||||
const localize = useLocalize();
|
||||
|
||||
const processedContent = useMemo(() => {
|
||||
if (!output) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const parts = output.split('Generated files:');
|
||||
return parts[0].trim();
|
||||
}, [output]);
|
||||
|
||||
const { imageAttachments, nonImageAttachments } = useMemo(() => {
|
||||
const imageAtts: ImageAttachment[] = [];
|
||||
const nonImageAtts: TAttachment[] = [];
|
||||
|
||||
attachments?.forEach((attachment) => {
|
||||
const { width, height, filepath = null } = attachment as TFile & TAttachmentMetadata;
|
||||
const isImage =
|
||||
imageExtRegex.test(attachment.filename) &&
|
||||
width != null &&
|
||||
height != null &&
|
||||
filepath != null;
|
||||
if (isImage) {
|
||||
imageAtts.push(attachment as ImageAttachment);
|
||||
} else {
|
||||
nonImageAtts.push(attachment);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
imageAttachments: renderImages === true ? imageAtts : null,
|
||||
nonImageAttachments: nonImageAtts,
|
||||
};
|
||||
}, [attachments, renderImages]);
|
||||
|
||||
const renderAttachment = (file: TAttachment) => {
|
||||
const now = new Date();
|
||||
const expiresAt = typeof file.expiresAt === 'number' ? new Date(file.expiresAt) : null;
|
||||
const isExpired = expiresAt ? isAfter(now, expiresAt) : false;
|
||||
|
||||
if (isExpired) {
|
||||
return `${file.filename} ${localize('com_download_expired')}`;
|
||||
}
|
||||
|
||||
// const expirationText = expiresAt
|
||||
// ? ` ${localize('com_download_expires', { 0: format(expiresAt, 'MM/dd/yy HH:mm') })}`
|
||||
// : ` ${localize('com_click_to_download')}`;
|
||||
|
||||
return (
|
||||
<LogLink href={file.filepath} filename={file.filename}>
|
||||
{'- '}
|
||||
{file.filename} {localize('com_click_to_download')}
|
||||
</LogLink>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{processedContent && <div>{processedContent}</div>}
|
||||
{nonImageAttachments.length > 0 && (
|
||||
<div>
|
||||
<p>{localize('com_generated_files')}</p>
|
||||
{nonImageAttachments.map((file, index) => (
|
||||
<React.Fragment key={file.filepath}>
|
||||
{renderAttachment(file)}
|
||||
{index < nonImageAttachments.length - 1 && ', '}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{imageAttachments?.map((attachment, index) => {
|
||||
const { width, height, filepath } = attachment;
|
||||
return (
|
||||
<Image
|
||||
key={index}
|
||||
altText={attachment.filename}
|
||||
imagePath={filepath}
|
||||
height={height}
|
||||
width={width}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogContent;
|
||||
@@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import { useCodeOutputDownload } from '~/data-provider';
|
||||
import { useToastContext } from '~/Providers';
|
||||
|
||||
interface LogLinkProps {
|
||||
href: string;
|
||||
filename: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const LogLink: React.FC<LogLinkProps> = ({ href, filename, children }) => {
|
||||
const { showToast } = useToastContext();
|
||||
const { refetch: downloadFile } = useCodeOutputDownload(href);
|
||||
|
||||
const handleDownload = async (event: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
event.preventDefault();
|
||||
try {
|
||||
const stream = await downloadFile();
|
||||
if (stream.data == null || stream.data === '') {
|
||||
console.error('Error downloading file: No data found');
|
||||
showToast({
|
||||
status: 'error',
|
||||
message: 'Error downloading file',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const link = document.createElement('a');
|
||||
link.href = stream.data;
|
||||
link.setAttribute('download', filename);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(stream.data);
|
||||
} catch (error) {
|
||||
console.error('Error downloading file:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
onClick={handleDownload}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="!text-blue-400 visited:!text-purple-400 hover:underline"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogLink;
|
||||
@@ -0,0 +1,41 @@
|
||||
import { memo, useMemo } from 'react';
|
||||
import { ContentTypes } from '~/data-provider/data-provider/src';
|
||||
import { ThinkingContent } from '~/components/Artifacts/Thinking';
|
||||
import { useMessageContext } from '~/Providers';
|
||||
import { cn } from '~/utils';
|
||||
|
||||
type ReasoningProps = {
|
||||
reasoning: string;
|
||||
};
|
||||
|
||||
const Reasoning = memo(({ reasoning }: ReasoningProps) => {
|
||||
const { isExpanded, nextType } = useMessageContext();
|
||||
const reasoningText = useMemo(() => {
|
||||
return reasoning
|
||||
.replace(/^<think>\s*/, '')
|
||||
.replace(/\s*<\/think>$/, '')
|
||||
.trim();
|
||||
}, [reasoning]);
|
||||
|
||||
if (!reasoningText) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'grid transition-all duration-300 ease-out',
|
||||
nextType !== ContentTypes.THINK && isExpanded && 'mb-8',
|
||||
)}
|
||||
style={{
|
||||
gridTemplateRows: isExpanded ? '1fr' : '0fr',
|
||||
}}
|
||||
>
|
||||
<div className="overflow-hidden">
|
||||
<ThinkingContent isPart={true}>{reasoningText}</ThinkingContent>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default Reasoning;
|
||||
@@ -0,0 +1,57 @@
|
||||
import { memo, useMemo, ReactElement } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import MarkdownLite from '~/components/Chat/Messages/Content/MarkdownLite';
|
||||
import Markdown from '~/components/Chat/Messages/Content/Markdown';
|
||||
import { useChatContext, useMessageContext } from '~/Providers';
|
||||
import { cn } from '~/utils';
|
||||
import store from '~/store';
|
||||
|
||||
type TextPartProps = {
|
||||
text: string;
|
||||
showCursor: boolean;
|
||||
isCreatedByUser: boolean;
|
||||
};
|
||||
|
||||
type ContentType =
|
||||
| ReactElement<React.ComponentProps<typeof Markdown>>
|
||||
| ReactElement<React.ComponentProps<typeof MarkdownLite>>
|
||||
| ReactElement;
|
||||
|
||||
const TextPart = memo(({ text, isCreatedByUser, showCursor }: TextPartProps) => {
|
||||
const { messageId } = useMessageContext();
|
||||
const { isSubmitting, latestMessage } = useChatContext();
|
||||
const enableUserMsgMarkdown = useRecoilValue(store.enableUserMsgMarkdown);
|
||||
const showCursorState = useMemo(() => showCursor && isSubmitting, [showCursor, isSubmitting]);
|
||||
const isLatestMessage = useMemo(
|
||||
() => messageId === latestMessage?.messageId,
|
||||
[messageId, latestMessage?.messageId],
|
||||
);
|
||||
|
||||
const content: ContentType = useMemo(() => {
|
||||
if (!isCreatedByUser) {
|
||||
return (
|
||||
<Markdown content={text} showCursor={showCursorState} isLatestMessage={isLatestMessage} />
|
||||
);
|
||||
} else if (enableUserMsgMarkdown) {
|
||||
return <MarkdownLite content={text} />;
|
||||
} else {
|
||||
return <>{text}</>;
|
||||
}
|
||||
}, [isCreatedByUser, enableUserMsgMarkdown, text, showCursorState, isLatestMessage]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
isSubmitting ? 'submitting' : '',
|
||||
showCursorState && !!text.length ? 'result-streaming' : '',
|
||||
'markdown prose message-content dark:prose-invert light w-full break-words',
|
||||
isCreatedByUser && !enableUserMsgMarkdown && 'whitespace-pre-wrap',
|
||||
isCreatedByUser ? 'dark:text-gray-20' : 'dark:text-gray-100',
|
||||
)}
|
||||
>
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default TextPart;
|
||||
Reference in New Issue
Block a user