吸收数字员工产物访问治理
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type CSSProperties } from 'react';
|
||||
import { Download } from 'lucide-react';
|
||||
import { Download, Eye, FolderOpen } from 'lucide-react';
|
||||
import GlassCard from '@/components/digital/GlassCard';
|
||||
import CardTitleBar from '@/components/digital/CardTitleBar';
|
||||
import {
|
||||
getDigitalEmployeeWorkday,
|
||||
postDigitalEmployeeWorkdayAction,
|
||||
accessArtifact,
|
||||
} from '@/lib/api';
|
||||
import type {
|
||||
DigitalEmployeeDailyReport,
|
||||
@@ -181,20 +182,70 @@ function DetailList({ details }: { details: DigitalEmployeeRunDetail[] }) {
|
||||
}
|
||||
|
||||
function ArtifactSourceList({ artifacts }: { artifacts: DigitalEmployeeReportArtifactSource[] }) {
|
||||
const [busyKey, setBusyKey] = useState<string | null>(null);
|
||||
const [notice, setNotice] = useState<string | null>(null);
|
||||
const [preview, setPreview] = useState<{ title: string; text: string } | null>(null);
|
||||
|
||||
if (artifacts.length === 0) {
|
||||
return <p className="de-empty-text" style={{ padding: '18px 0 6px' }}>运行产物出现后,这里会展示文件、输出和来源记录。</p>;
|
||||
}
|
||||
|
||||
const runAccess = async (artifact: DigitalEmployeeReportArtifactSource, action: 'preview' | 'download' | 'open_location') => {
|
||||
if (!artifact.id || busyKey) return;
|
||||
const key = `${artifact.id}:${action}`;
|
||||
setBusyKey(key);
|
||||
setNotice(null);
|
||||
try {
|
||||
const result = await accessArtifact(artifact.id, action);
|
||||
if (!result?.ok) {
|
||||
setNotice(result?.error || '产物访问被拒绝');
|
||||
return;
|
||||
}
|
||||
if (action === 'preview') {
|
||||
const data = result.preview || {};
|
||||
setPreview({
|
||||
title: String(data.name || artifact.name || '产物预览'),
|
||||
text: String(data.text || `${data.mime || artifact.kind || 'artifact'} · ${data.size ?? 'unknown'}`),
|
||||
});
|
||||
} else {
|
||||
setNotice(action === 'download' ? '已打开产物' : '已打开位置');
|
||||
}
|
||||
} catch (error) {
|
||||
setNotice(error instanceof Error ? error.message : '产物访问失败');
|
||||
} finally {
|
||||
setBusyKey(null);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="de-artifact-source-list" aria-label="日报产物来源">
|
||||
{artifacts.map((artifact) => (
|
||||
<div key={artifact.id} className="de-artifact-source-row">
|
||||
<strong>{artifact.name}</strong>
|
||||
<span>{artifact.source_label || artifact.source_type || '未知来源'}</span>
|
||||
{artifact.uri ? <code>{artifact.uri}</code> : <em>{artifact.kind || 'artifact'}</em>}
|
||||
<>
|
||||
<div className="de-artifact-source-list" aria-label="日报产物来源">
|
||||
{artifacts.map((artifact) => {
|
||||
const previewable = Boolean(artifact.access?.previewable);
|
||||
const downloadable = Boolean(artifact.access?.downloadable);
|
||||
const openable = Boolean(artifact.access?.openable);
|
||||
return (
|
||||
<div key={artifact.id} className="de-artifact-source-row">
|
||||
<strong>{artifact.name}</strong>
|
||||
<span>{artifact.source_label || artifact.source_type || '未知来源'}</span>
|
||||
<div className="de-artifact-source-actions">
|
||||
<button type="button" className="de-icon-btn" disabled={!previewable || busyKey !== null} onClick={() => runAccess(artifact, 'preview')} title="预览" aria-label="预览"><Eye size={15} /></button>
|
||||
<button type="button" className="de-icon-btn" disabled={!downloadable || busyKey !== null} onClick={() => runAccess(artifact, 'download')} title="下载" aria-label="下载"><Download size={15} /></button>
|
||||
<button type="button" className="de-icon-btn" disabled={!openable || busyKey !== null} onClick={() => runAccess(artifact, 'open_location')} title="位置" aria-label="位置"><FolderOpen size={15} /></button>
|
||||
</div>
|
||||
{artifact.uri ? <code>{artifact.uri}</code> : <em>{artifact.kind || 'artifact'}</em>}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{notice ? <p className="de-inline-notice">{notice}</p> : null}
|
||||
{preview ? (
|
||||
<div className="de-artifact-preview">
|
||||
<div><strong>{preview.title}</strong><button type="button" className="de-btn-secondary" onClick={() => setPreview(null)}>关闭</button></div>
|
||||
<pre>{preview.text}</pre>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user