feat: update codex live chat workflow
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { CopyOutlined, FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons';
|
||||
import { CopyOutlined, DownloadOutlined, FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons';
|
||||
import { Button, Empty, Select, message } from 'antd';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
@@ -136,6 +136,22 @@ async function copyText(text: string) {
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
|
||||
function downloadBlob(content: BlobPart, fileName: string, mimeType = 'text/plain;charset=utf-8') {
|
||||
if (typeof document === 'undefined') {
|
||||
throw new Error('다운로드를 사용할 수 없습니다.');
|
||||
}
|
||||
|
||||
const blob = new Blob([content], { type: mimeType });
|
||||
const objectUrl = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = objectUrl;
|
||||
link.download = fileName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
}
|
||||
|
||||
function resolveCopyValue({ type, value }: Pick<PreviewerUIProps, 'type' | 'value'>) {
|
||||
switch (type) {
|
||||
case 'json':
|
||||
@@ -147,6 +163,45 @@ function resolveCopyValue({ type, value }: Pick<PreviewerUIProps, 'type' | 'valu
|
||||
}
|
||||
}
|
||||
|
||||
function resolveDownloadValue({
|
||||
type,
|
||||
value,
|
||||
downloadValue,
|
||||
}: Pick<PreviewerUIProps, 'type' | 'value' | 'downloadValue'>) {
|
||||
if (typeof downloadValue === 'string') {
|
||||
return downloadValue;
|
||||
}
|
||||
|
||||
if (type === 'image') {
|
||||
return String(value ?? '');
|
||||
}
|
||||
|
||||
return resolveCopyValue({ type, value });
|
||||
}
|
||||
|
||||
function resolveDownloadFileName({
|
||||
type,
|
||||
language,
|
||||
downloadFileName,
|
||||
}: Pick<PreviewerUIProps, 'type' | 'language' | 'downloadFileName'>) {
|
||||
if (downloadFileName?.trim()) {
|
||||
return downloadFileName.trim();
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'json':
|
||||
return 'preview.json';
|
||||
case 'markdown':
|
||||
return 'preview.md';
|
||||
case 'code':
|
||||
return `preview.${language || 'txt'}`;
|
||||
case 'image':
|
||||
return 'preview';
|
||||
default:
|
||||
return 'preview.txt';
|
||||
}
|
||||
}
|
||||
|
||||
function renderContent({
|
||||
type,
|
||||
value,
|
||||
@@ -212,6 +267,10 @@ export function PreviewerUI({
|
||||
value,
|
||||
copyValue,
|
||||
copyable = true,
|
||||
downloadable = true,
|
||||
downloadValue,
|
||||
downloadUrl,
|
||||
downloadFileName,
|
||||
maximizable = true,
|
||||
language = 'text',
|
||||
format = 'auto',
|
||||
@@ -226,8 +285,11 @@ export function PreviewerUI({
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const hasLanguageSelector = type === 'code' && languageOptions && languageOptions.length > 0;
|
||||
const resolvedCopyValue = copyValue ?? resolveCopyValue({ type, value });
|
||||
const resolvedDownloadValue = resolveDownloadValue({ type, value, downloadValue });
|
||||
const resolvedDownloadFileName = resolveDownloadFileName({ type, language, downloadFileName });
|
||||
const canCopy = copyable && resolvedCopyValue.trim().length > 0;
|
||||
const shouldShowActions = hasLanguageSelector || canCopy || maximizable || Boolean(toolbar);
|
||||
const canDownload = downloadable && (Boolean(downloadUrl) || resolvedDownloadValue.trim().length > 0);
|
||||
const shouldShowActions = hasLanguageSelector || canCopy || canDownload || maximizable || Boolean(toolbar);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isExpanded || typeof document === 'undefined') {
|
||||
@@ -271,6 +333,37 @@ export function PreviewerUI({
|
||||
setIsExpanded((previous) => !previous);
|
||||
}
|
||||
|
||||
function handleDownload() {
|
||||
if (!canDownload) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (downloadUrl) {
|
||||
if (typeof document === 'undefined') {
|
||||
throw new Error('다운로드를 사용할 수 없습니다.');
|
||||
}
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = downloadUrl;
|
||||
link.download = resolvedDownloadFileName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
} else {
|
||||
const mimeType =
|
||||
type === 'json'
|
||||
? 'application/json;charset=utf-8'
|
||||
: type === 'markdown'
|
||||
? 'text/markdown;charset=utf-8'
|
||||
: 'text/plain;charset=utf-8';
|
||||
downloadBlob(resolvedDownloadValue, resolvedDownloadFileName, mimeType);
|
||||
}
|
||||
} catch {
|
||||
messageApi.error('다운로드에 실패했습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
const actionContent = (
|
||||
<>
|
||||
{hasLanguageSelector ? (
|
||||
@@ -286,15 +379,27 @@ export function PreviewerUI({
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
className="previewer-ui__action-button"
|
||||
aria-label="복사"
|
||||
icon={<CopyOutlined />}
|
||||
onClick={() => void handleCopy()}
|
||||
/>
|
||||
) : null}
|
||||
{canDownload ? (
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
className="previewer-ui__action-button"
|
||||
aria-label="다운로드"
|
||||
icon={<DownloadOutlined />}
|
||||
onClick={handleDownload}
|
||||
/>
|
||||
) : null}
|
||||
{maximizable ? (
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
className="previewer-ui__action-button"
|
||||
aria-label={isExpanded ? '최대화 해제' : '최대화'}
|
||||
icon={isExpanded ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
|
||||
onClick={() => void toggleFullscreen()}
|
||||
|
||||
Reference in New Issue
Block a user