feat(client): show digital artifact sources
This commit is contained in:
@@ -1324,7 +1324,14 @@ function buildArtifacts(snapshot: QimingclawSnapshot | null): ArtifactEntry[] {
|
||||
const artifacts = [
|
||||
...runtimeArtifacts(snapshot).map(artifactFromRuntimeRecord),
|
||||
...runtimeRuns(snapshot).flatMap((run) => artifactsForRun(run, runtimeEvents(snapshot))),
|
||||
...runtimeEvents(snapshot).flatMap((event) => artifactsFromPayload(event.id, event.payload, event.occurredAt)),
|
||||
...runtimeEvents(snapshot).flatMap((event) => artifactsFromPayload(event.id, event.payload, event.occurredAt, {
|
||||
sourceType: 'event',
|
||||
sourceEventId: event.id,
|
||||
sourceLabel: `Event ${event.kind}`,
|
||||
planId: event.planId,
|
||||
taskId: event.taskId,
|
||||
runId: event.runId,
|
||||
})),
|
||||
];
|
||||
return dedupeArtifacts(artifacts);
|
||||
}
|
||||
@@ -1343,6 +1350,8 @@ function artifactFromRuntimeRecord(artifact: QimingclawArtifactRecord): Artifact
|
||||
run_id: artifact.runId ?? null,
|
||||
created_at: artifact.createdAt,
|
||||
source: 'qimingclaw-runtime',
|
||||
source_type: 'artifact',
|
||||
source_label: artifactSourceLabel('artifact', artifact.id),
|
||||
sync_status: artifact.syncStatus,
|
||||
sync_error: artifact.syncError,
|
||||
payload: artifact.payload,
|
||||
@@ -1351,16 +1360,43 @@ function artifactFromRuntimeRecord(artifact: QimingclawArtifactRecord): Artifact
|
||||
|
||||
function artifactsForRun(run: QimingclawRunRecord, events: QimingclawEventRecord[]): ArtifactEntry[] {
|
||||
return dedupeArtifacts([
|
||||
...artifactsFromPayload(run.id, run.payload, run.finishedAt ?? run.startedAt ?? run.createdAt),
|
||||
...artifactsFromPayload(run.id, run.payload, run.finishedAt ?? run.startedAt ?? run.createdAt, {
|
||||
sourceType: 'run',
|
||||
sourceLabel: artifactSourceLabel('run', run.id),
|
||||
planId: run.planId,
|
||||
taskId: run.taskId,
|
||||
runId: run.id,
|
||||
}),
|
||||
...events
|
||||
.filter((event) => event.runId === run.id)
|
||||
.flatMap((event) => artifactsFromPayload(run.id, event.payload, event.occurredAt)),
|
||||
.flatMap((event) => artifactsFromPayload(run.id, event.payload, event.occurredAt, {
|
||||
sourceType: 'event',
|
||||
sourceEventId: event.id,
|
||||
sourceLabel: `Event ${event.kind}`,
|
||||
planId: event.planId,
|
||||
taskId: event.taskId,
|
||||
runId: event.runId,
|
||||
})),
|
||||
]);
|
||||
}
|
||||
|
||||
function artifactsFromPayload(subjectId: string, payload: unknown, createdAt?: string | null): ArtifactEntry[] {
|
||||
interface ArtifactSourceContext {
|
||||
sourceType: string;
|
||||
sourceLabel: string;
|
||||
sourceEventId?: string | null;
|
||||
planId?: string | null;
|
||||
taskId?: string | null;
|
||||
runId?: string | null;
|
||||
}
|
||||
|
||||
function artifactsFromPayload(
|
||||
subjectId: string,
|
||||
payload: unknown,
|
||||
createdAt?: string | null,
|
||||
sourceContext?: ArtifactSourceContext,
|
||||
): ArtifactEntry[] {
|
||||
const record = asRecord(payload);
|
||||
if (!record) return typeof payload === 'string' ? [artifactFromValue(subjectId, payload, 0, createdAt)] : [];
|
||||
if (!record) return typeof payload === 'string' ? [artifactFromValue(subjectId, payload, 0, createdAt, sourceContext)] : [];
|
||||
|
||||
const explicitArtifacts = [
|
||||
...arrayValue(record.artifacts),
|
||||
@@ -1368,16 +1404,22 @@ function artifactsFromPayload(subjectId: string, payload: unknown, createdAt?: s
|
||||
...arrayValue(record.files),
|
||||
...arrayValue(record.attachments),
|
||||
];
|
||||
const artifacts = explicitArtifacts.map((item, index) => artifactFromValue(subjectId, item, index, createdAt));
|
||||
const artifacts = explicitArtifacts.map((item, index) => artifactFromValue(subjectId, item, index, createdAt, sourceContext));
|
||||
|
||||
for (const key of ['artifact', 'outputPath', 'output_path', 'filePath', 'file_path', 'path', 'uri', 'url']) {
|
||||
if (record[key]) artifacts.push(artifactFromValue(subjectId, record[key], artifacts.length, createdAt));
|
||||
if (record[key]) artifacts.push(artifactFromValue(subjectId, record[key], artifacts.length, createdAt, sourceContext));
|
||||
}
|
||||
|
||||
return artifacts.filter((artifact) => artifact.name || artifact.uri || artifact.value);
|
||||
}
|
||||
|
||||
function artifactFromValue(subjectId: string, value: unknown, index: number, createdAt?: string | null): ArtifactEntry {
|
||||
function artifactFromValue(
|
||||
subjectId: string,
|
||||
value: unknown,
|
||||
index: number,
|
||||
createdAt?: string | null,
|
||||
sourceContext?: ArtifactSourceContext,
|
||||
): ArtifactEntry {
|
||||
const record = asRecord(value);
|
||||
if (record) {
|
||||
const uri = stringValue(record.uri ?? record.url ?? record.path ?? record.filePath ?? record.file_path ?? record.outputPath ?? record.output_path);
|
||||
@@ -1391,6 +1433,12 @@ function artifactFromValue(subjectId: string, value: unknown, index: number, cre
|
||||
value: stringValue(record.value ?? record.summary ?? record.text) || uri,
|
||||
created_at: (stringValue(record.created_at ?? record.createdAt) || createdAt) ?? undefined,
|
||||
source: 'qimingclaw-runtime',
|
||||
source_type: stringValue(record.source_type) || sourceContext?.sourceType || 'payload',
|
||||
source_event_id: stringValue(record.source_event_id) || sourceContext?.sourceEventId || null,
|
||||
source_label: stringValue(record.source_label) || sourceContext?.sourceLabel || artifactSourceLabel('payload', subjectId),
|
||||
plan_id: stringValue(record.plan_id) || sourceContext?.planId || null,
|
||||
task_id: stringValue(record.task_id) || sourceContext?.taskId || null,
|
||||
run_id: stringValue(record.run_id) || sourceContext?.runId || null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1404,9 +1452,27 @@ function artifactFromValue(subjectId: string, value: unknown, index: number, cre
|
||||
value: text,
|
||||
created_at: createdAt ?? undefined,
|
||||
source: 'qimingclaw-runtime',
|
||||
source_type: sourceContext?.sourceType || 'payload',
|
||||
source_event_id: sourceContext?.sourceEventId || null,
|
||||
source_label: sourceContext?.sourceLabel || artifactSourceLabel('payload', subjectId),
|
||||
plan_id: sourceContext?.planId || null,
|
||||
task_id: sourceContext?.taskId || null,
|
||||
run_id: sourceContext?.runId || null,
|
||||
};
|
||||
}
|
||||
|
||||
function artifactSourceLabel(sourceType: string, sourceId?: string | null): string {
|
||||
const suffix = sourceId ? ` ${sourceId}` : '';
|
||||
switch (sourceType) {
|
||||
case 'artifact': return `Artifact${suffix}`;
|
||||
case 'event': return `Event${suffix}`;
|
||||
case 'run': return `Run${suffix}`;
|
||||
case 'task': return `Task${suffix}`;
|
||||
case 'plan': return `Plan${suffix}`;
|
||||
default: return `Payload${suffix}`;
|
||||
}
|
||||
}
|
||||
|
||||
function dedupeArtifacts(artifacts: ArtifactEntry[]): ArtifactEntry[] {
|
||||
const seen = new Set<string>();
|
||||
return artifacts.filter((artifact) => {
|
||||
@@ -1636,6 +1702,7 @@ function buildWorkday(date: string, snapshot: QimingclawSnapshot | null): Digita
|
||||
const liveRecordCount = runtimePlans(snapshot).length + runtimeTasks(snapshot).length;
|
||||
const hasLiveRuntime = hasRuntimeRecords(snapshot);
|
||||
const sync = snapshot?.sync;
|
||||
const artifactSources = reportArtifactSources(buildArtifacts(snapshot));
|
||||
|
||||
return {
|
||||
source: 'live',
|
||||
@@ -1709,6 +1776,13 @@ function buildWorkday(date: string, snapshot: QimingclawSnapshot | null): Digita
|
||||
],
|
||||
artifacts: [
|
||||
{ name: '数字员工运行摘要', status: generatedAt ? 'ready' : 'pending' },
|
||||
...artifactSources.slice(0, 4).map((artifact) => ({
|
||||
name: artifact.name,
|
||||
status: generatedAt ? 'ready' : 'available',
|
||||
uri: artifact.uri ?? null,
|
||||
source_label: artifact.source_label ?? null,
|
||||
source_type: artifact.source_type ?? null,
|
||||
})),
|
||||
],
|
||||
},
|
||||
daily_report: {
|
||||
@@ -1732,12 +1806,29 @@ function buildWorkday(date: string, snapshot: QimingclawSnapshot | null): Digita
|
||||
},
|
||||
task_sections: executionSummaries,
|
||||
detail_lines: events.map((event) => `${event.time} ${event.task_title}:${event.detail}`),
|
||||
artifact_sources: artifactSources,
|
||||
pdf_available: Boolean(generatedAt),
|
||||
pdf_url: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function reportArtifactSources(artifacts: ArtifactEntry[]) {
|
||||
return artifacts.slice(0, 20).map((artifact) => ({
|
||||
id: stringValue(artifact.artifact_id) || `${stringValue(artifact.subject_id)}:${stringValue(artifact.uri ?? artifact.name)}`,
|
||||
name: stringValue(artifact.name) || stringValue(artifact.uri) || stringValue(artifact.value) || '未命名产物',
|
||||
kind: stringValue(artifact.kind) || null,
|
||||
uri: stringValue(artifact.uri) || null,
|
||||
source_label: stringValue(artifact.source_label) || null,
|
||||
source_type: stringValue(artifact.source_type) || null,
|
||||
source_event_id: stringValue(artifact.source_event_id) || null,
|
||||
plan_id: stringValue(artifact.plan_id) || null,
|
||||
task_id: stringValue(artifact.task_id) || null,
|
||||
run_id: stringValue(artifact.run_id) || null,
|
||||
created_at: stringValue(artifact.created_at) || null,
|
||||
}));
|
||||
}
|
||||
|
||||
async function handleWorkdayAction(body: DigitalEmployeeWorkdayActionRequest, snapshot: QimingclawSnapshot | null): Promise<DigitalEmployeeWorkdayProjection> {
|
||||
const state = snapshot?.uiState ?? await readBridgeUiState() ?? readState();
|
||||
const date = body.date || todayInputDate();
|
||||
|
||||
Reference in New Issue
Block a user