stagent 0.1.12 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -50
- package/package.json +1 -1
- package/public/readme/cost-usage-list.png +0 -0
- package/public/readme/dashboard-bulk-select.png +0 -0
- package/public/readme/dashboard-card-edit.png +0 -0
- package/public/readme/dashboard-create-form-ai-applied.png +0 -0
- package/public/readme/dashboard-create-form-ai-assist.png +0 -0
- package/public/readme/dashboard-create-form-empty.png +0 -0
- package/public/readme/dashboard-create-form-filled.png +0 -0
- package/public/readme/dashboard-filtered.png +0 -0
- package/public/readme/dashboard-list.png +0 -0
- package/public/readme/dashboard-workflow-confirm.png +0 -0
- package/public/readme/home-below-fold.png +0 -0
- package/public/readme/home-list.png +0 -0
- package/public/readme/inbox-list.png +0 -0
- package/public/readme/playbook-list.png +0 -0
- package/public/readme/profiles-list.png +0 -0
- package/public/readme/settings-list.png +0 -0
- package/public/readme/workflows-list.png +0 -0
- package/src/app/api/tasks/[id]/route.ts +54 -3
- package/src/app/api/workflows/[id]/route.ts +43 -4
- package/src/app/api/workflows/[id]/status/route.ts +70 -2
- package/src/app/api/workflows/from-assist/route.ts +6 -32
- package/src/app/dashboard/page.tsx +59 -21
- package/src/app/documents/[id]/page.tsx +10 -8
- package/src/app/globals.css +11 -0
- package/src/app/page.tsx +60 -3
- package/src/app/tasks/[id]/page.tsx +22 -2
- package/src/components/costs/cost-dashboard.tsx +1 -1
- package/src/components/dashboard/greeting.tsx +3 -1
- package/src/components/dashboard/priority-queue.tsx +58 -9
- package/src/components/dashboard/stats-cards.tsx +16 -2
- package/src/components/documents/document-chip-bar.tsx +183 -0
- package/src/components/documents/document-content-renderer.tsx +146 -0
- package/src/components/documents/document-detail-view.tsx +16 -239
- package/src/components/documents/image-zoom-view.tsx +60 -0
- package/src/components/documents/smart-extracted-text.tsx +47 -0
- package/src/components/documents/utils.ts +70 -0
- package/src/components/notifications/inbox-list.tsx +4 -5
- package/src/components/notifications/notification-item.tsx +72 -8
- package/src/components/notifications/pending-approval-host.tsx +7 -4
- package/src/components/playbook/playbook-detail-view.tsx +6 -4
- package/src/components/profiles/profile-browser.tsx +1 -0
- package/src/components/profiles/profile-card.tsx +16 -8
- package/src/components/profiles/profile-detail-view.tsx +6 -1
- package/src/components/shared/app-sidebar.tsx +2 -2
- package/src/components/tasks/__tests__/kanban-board-accessibility.test.tsx +1 -1
- package/src/components/tasks/ai-assist-panel.tsx +108 -78
- package/src/components/tasks/content-preview.tsx +2 -1
- package/src/components/tasks/kanban-board.tsx +57 -5
- package/src/components/tasks/kanban-column.tsx +34 -23
- package/src/components/tasks/task-bento-cell.tsx +50 -0
- package/src/components/tasks/task-bento-grid.tsx +155 -0
- package/src/components/tasks/task-card.tsx +14 -16
- package/src/components/tasks/task-chip-bar.tsx +207 -0
- package/src/components/tasks/task-detail-view.tsx +42 -190
- package/src/components/tasks/task-result-renderer.tsx +33 -0
- package/src/components/workflows/blueprint-gallery.tsx +19 -12
- package/src/components/workflows/blueprint-preview.tsx +8 -1
- package/src/components/workflows/loop-status-view.tsx +2 -8
- package/src/components/workflows/swarm-dashboard.tsx +2 -3
- package/src/components/workflows/workflow-confirmation-view.tsx +2 -7
- package/src/components/workflows/workflow-full-output.tsx +80 -0
- package/src/components/workflows/workflow-kanban-card.tsx +121 -0
- package/src/components/workflows/workflow-list.tsx +47 -42
- package/src/components/workflows/workflow-status-view.tsx +160 -20
- package/src/lib/agents/learning-session.ts +138 -18
- package/src/lib/constants/card-icons.tsx +202 -0
- package/src/lib/constants/prose-styles.ts +7 -0
- package/src/lib/constants/task-status.ts +3 -0
- package/src/lib/docs/reader.ts +8 -3
- package/src/lib/documents/context-builder.ts +41 -0
- package/src/lib/queries/chart-data.ts +20 -1
- package/src/lib/workflows/engine.ts +57 -61
- package/src/lib/workflows/types.ts +2 -0
- package/tsconfig.json +2 -1
- package/src/components/documents/document-preview.tsx +0 -68
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
openLearningSession,
|
|
21
21
|
closeLearningSession,
|
|
22
22
|
} from "@/lib/agents/learning-session";
|
|
23
|
+
import { buildWorkflowDocumentContext } from "@/lib/documents/context-builder";
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Execute a workflow by advancing through its steps according to the pattern.
|
|
@@ -36,6 +37,9 @@ export async function executeWorkflow(workflowId: string): Promise<void> {
|
|
|
36
37
|
const definition: WorkflowDefinition = JSON.parse(workflow.definition);
|
|
37
38
|
const state = createInitialState(definition);
|
|
38
39
|
|
|
40
|
+
// Extract parent task ID for document context propagation to child steps
|
|
41
|
+
const parentTaskId: string | undefined = definition.sourceTaskId ?? undefined;
|
|
42
|
+
|
|
39
43
|
await updateWorkflowState(workflowId, state, "active");
|
|
40
44
|
|
|
41
45
|
await db.insert(agentLogs).values({
|
|
@@ -56,8 +60,6 @@ export async function executeWorkflow(workflowId: string): Promise<void> {
|
|
|
56
60
|
try {
|
|
57
61
|
await executeLoop(workflowId, definition);
|
|
58
62
|
|
|
59
|
-
await syncSourceTaskStatus(workflowId, "completed");
|
|
60
|
-
|
|
61
63
|
await db.insert(agentLogs).values({
|
|
62
64
|
id: crypto.randomUUID(),
|
|
63
65
|
taskId: null,
|
|
@@ -67,8 +69,6 @@ export async function executeWorkflow(workflowId: string): Promise<void> {
|
|
|
67
69
|
timestamp: new Date(),
|
|
68
70
|
});
|
|
69
71
|
} catch (error) {
|
|
70
|
-
await syncSourceTaskStatus(workflowId, "failed");
|
|
71
|
-
|
|
72
72
|
await db.insert(agentLogs).values({
|
|
73
73
|
id: crypto.randomUUID(),
|
|
74
74
|
taskId: null,
|
|
@@ -92,19 +92,19 @@ export async function executeWorkflow(workflowId: string): Promise<void> {
|
|
|
92
92
|
try {
|
|
93
93
|
switch (definition.pattern) {
|
|
94
94
|
case "sequence":
|
|
95
|
-
await executeSequence(workflowId, definition, state);
|
|
95
|
+
await executeSequence(workflowId, definition, state, parentTaskId);
|
|
96
96
|
break;
|
|
97
97
|
case "planner-executor":
|
|
98
|
-
await executePlannerExecutor(workflowId, definition, state);
|
|
98
|
+
await executePlannerExecutor(workflowId, definition, state, parentTaskId);
|
|
99
99
|
break;
|
|
100
100
|
case "checkpoint":
|
|
101
|
-
await executeCheckpoint(workflowId, definition, state);
|
|
101
|
+
await executeCheckpoint(workflowId, definition, state, parentTaskId);
|
|
102
102
|
break;
|
|
103
103
|
case "parallel":
|
|
104
|
-
await executeParallel(workflowId, definition, state);
|
|
104
|
+
await executeParallel(workflowId, definition, state, parentTaskId);
|
|
105
105
|
break;
|
|
106
106
|
case "swarm":
|
|
107
|
-
await executeSwarm(workflowId, definition, state);
|
|
107
|
+
await executeSwarm(workflowId, definition, state, parentTaskId);
|
|
108
108
|
break;
|
|
109
109
|
}
|
|
110
110
|
|
|
@@ -112,9 +112,6 @@ export async function executeWorkflow(workflowId: string): Promise<void> {
|
|
|
112
112
|
state.completedAt = new Date().toISOString();
|
|
113
113
|
await updateWorkflowState(workflowId, state, "completed");
|
|
114
114
|
|
|
115
|
-
// Sync parent task status
|
|
116
|
-
await syncSourceTaskStatus(workflowId, "completed");
|
|
117
|
-
|
|
118
115
|
await db.insert(agentLogs).values({
|
|
119
116
|
id: crypto.randomUUID(),
|
|
120
117
|
taskId: null,
|
|
@@ -127,9 +124,6 @@ export async function executeWorkflow(workflowId: string): Promise<void> {
|
|
|
127
124
|
state.status = "failed";
|
|
128
125
|
await updateWorkflowState(workflowId, state, "failed");
|
|
129
126
|
|
|
130
|
-
// Sync parent task status
|
|
131
|
-
await syncSourceTaskStatus(workflowId, "failed");
|
|
132
|
-
|
|
133
127
|
await db.insert(agentLogs).values({
|
|
134
128
|
id: crypto.randomUUID(),
|
|
135
129
|
taskId: null,
|
|
@@ -155,7 +149,8 @@ export async function executeWorkflow(workflowId: string): Promise<void> {
|
|
|
155
149
|
async function executeSequence(
|
|
156
150
|
workflowId: string,
|
|
157
151
|
definition: WorkflowDefinition,
|
|
158
|
-
state: WorkflowState
|
|
152
|
+
state: WorkflowState,
|
|
153
|
+
parentTaskId?: string
|
|
159
154
|
): Promise<void> {
|
|
160
155
|
let previousOutput = "";
|
|
161
156
|
|
|
@@ -175,7 +170,8 @@ async function executeSequence(
|
|
|
175
170
|
contextPrompt,
|
|
176
171
|
state,
|
|
177
172
|
step.assignedAgent,
|
|
178
|
-
step.agentProfile
|
|
173
|
+
step.agentProfile,
|
|
174
|
+
parentTaskId
|
|
179
175
|
);
|
|
180
176
|
|
|
181
177
|
if (result.status === "failed") {
|
|
@@ -192,7 +188,8 @@ async function executeSequence(
|
|
|
192
188
|
async function executePlannerExecutor(
|
|
193
189
|
workflowId: string,
|
|
194
190
|
definition: WorkflowDefinition,
|
|
195
|
-
state: WorkflowState
|
|
191
|
+
state: WorkflowState,
|
|
192
|
+
parentTaskId?: string
|
|
196
193
|
): Promise<void> {
|
|
197
194
|
if (definition.steps.length < 2) {
|
|
198
195
|
throw new Error("Planner-Executor requires at least 2 steps (planner + executor)");
|
|
@@ -208,7 +205,8 @@ async function executePlannerExecutor(
|
|
|
208
205
|
plannerStep.prompt,
|
|
209
206
|
state,
|
|
210
207
|
plannerStep.assignedAgent,
|
|
211
|
-
plannerStep.agentProfile
|
|
208
|
+
plannerStep.agentProfile,
|
|
209
|
+
parentTaskId
|
|
212
210
|
);
|
|
213
211
|
|
|
214
212
|
if (planResult.status === "failed") {
|
|
@@ -228,7 +226,8 @@ async function executePlannerExecutor(
|
|
|
228
226
|
contextPrompt,
|
|
229
227
|
state,
|
|
230
228
|
step.assignedAgent,
|
|
231
|
-
step.agentProfile
|
|
229
|
+
step.agentProfile,
|
|
230
|
+
parentTaskId
|
|
232
231
|
);
|
|
233
232
|
|
|
234
233
|
if (result.status === "failed") {
|
|
@@ -243,7 +242,8 @@ async function executePlannerExecutor(
|
|
|
243
242
|
async function executeCheckpoint(
|
|
244
243
|
workflowId: string,
|
|
245
244
|
definition: WorkflowDefinition,
|
|
246
|
-
state: WorkflowState
|
|
245
|
+
state: WorkflowState,
|
|
246
|
+
parentTaskId?: string
|
|
247
247
|
): Promise<void> {
|
|
248
248
|
let previousOutput = "";
|
|
249
249
|
|
|
@@ -275,7 +275,8 @@ async function executeCheckpoint(
|
|
|
275
275
|
contextPrompt,
|
|
276
276
|
state,
|
|
277
277
|
step.assignedAgent,
|
|
278
|
-
step.agentProfile
|
|
278
|
+
step.agentProfile,
|
|
279
|
+
parentTaskId
|
|
279
280
|
);
|
|
280
281
|
|
|
281
282
|
if (result.status === "failed") {
|
|
@@ -292,7 +293,8 @@ async function executeCheckpoint(
|
|
|
292
293
|
async function executeParallel(
|
|
293
294
|
workflowId: string,
|
|
294
295
|
definition: WorkflowDefinition,
|
|
295
|
-
state: WorkflowState
|
|
296
|
+
state: WorkflowState,
|
|
297
|
+
parentTaskId?: string
|
|
296
298
|
): Promise<void> {
|
|
297
299
|
const structure = getParallelWorkflowStructure(definition);
|
|
298
300
|
if (!structure) {
|
|
@@ -356,7 +358,8 @@ async function executeParallel(
|
|
|
356
358
|
step.name,
|
|
357
359
|
step.prompt,
|
|
358
360
|
step.assignedAgent,
|
|
359
|
-
step.agentProfile
|
|
361
|
+
step.agentProfile,
|
|
362
|
+
parentTaskId
|
|
360
363
|
);
|
|
361
364
|
|
|
362
365
|
const completedAt = new Date().toISOString();
|
|
@@ -429,7 +432,8 @@ async function executeParallel(
|
|
|
429
432
|
synthesisStep.name,
|
|
430
433
|
synthesisPrompt,
|
|
431
434
|
synthesisStep.assignedAgent,
|
|
432
|
-
synthesisStep.agentProfile
|
|
435
|
+
synthesisStep.agentProfile,
|
|
436
|
+
parentTaskId
|
|
433
437
|
);
|
|
434
438
|
|
|
435
439
|
await commitState((draft) => {
|
|
@@ -464,7 +468,8 @@ async function executeParallel(
|
|
|
464
468
|
async function executeSwarm(
|
|
465
469
|
workflowId: string,
|
|
466
470
|
definition: WorkflowDefinition,
|
|
467
|
-
state: WorkflowState
|
|
471
|
+
state: WorkflowState,
|
|
472
|
+
parentTaskId?: string
|
|
468
473
|
): Promise<void> {
|
|
469
474
|
const structure = getSwarmWorkflowStructure(definition);
|
|
470
475
|
if (!structure) {
|
|
@@ -490,7 +495,8 @@ async function executeSwarm(
|
|
|
490
495
|
mayorStep.prompt,
|
|
491
496
|
state,
|
|
492
497
|
mayorStep.assignedAgent,
|
|
493
|
-
mayorStep.agentProfile
|
|
498
|
+
mayorStep.agentProfile,
|
|
499
|
+
parentTaskId
|
|
494
500
|
);
|
|
495
501
|
|
|
496
502
|
if (mayorResult.status === "failed") {
|
|
@@ -552,7 +558,8 @@ async function executeSwarm(
|
|
|
552
558
|
step.name,
|
|
553
559
|
workerPrompt,
|
|
554
560
|
step.assignedAgent,
|
|
555
|
-
step.agentProfile
|
|
561
|
+
step.agentProfile,
|
|
562
|
+
parentTaskId
|
|
556
563
|
);
|
|
557
564
|
|
|
558
565
|
const completedAt = new Date().toISOString();
|
|
@@ -604,6 +611,7 @@ async function executeSwarm(
|
|
|
604
611
|
stepName: worker.step.name,
|
|
605
612
|
result: worker.result.result ?? "",
|
|
606
613
|
})),
|
|
614
|
+
parentTaskId,
|
|
607
615
|
});
|
|
608
616
|
}
|
|
609
617
|
|
|
@@ -637,6 +645,7 @@ async function runSwarmRefinery(input: {
|
|
|
637
645
|
};
|
|
638
646
|
refineryIndex: number;
|
|
639
647
|
workerOutputs: Array<{ stepName: string; result: string }>;
|
|
648
|
+
parentTaskId?: string;
|
|
640
649
|
}): Promise<void> {
|
|
641
650
|
const {
|
|
642
651
|
workflowId,
|
|
@@ -646,6 +655,7 @@ async function runSwarmRefinery(input: {
|
|
|
646
655
|
refineryStep,
|
|
647
656
|
refineryIndex,
|
|
648
657
|
workerOutputs,
|
|
658
|
+
parentTaskId,
|
|
649
659
|
} = input;
|
|
650
660
|
|
|
651
661
|
state.currentStepIndex = refineryIndex;
|
|
@@ -669,7 +679,8 @@ async function runSwarmRefinery(input: {
|
|
|
669
679
|
refineryStep.name,
|
|
670
680
|
refineryPrompt,
|
|
671
681
|
refineryStep.assignedAgent,
|
|
672
|
-
refineryStep.agentProfile
|
|
682
|
+
refineryStep.agentProfile,
|
|
683
|
+
parentTaskId
|
|
673
684
|
);
|
|
674
685
|
|
|
675
686
|
refineryState.taskId = refineryResult.taskId;
|
|
@@ -704,7 +715,8 @@ export async function executeChildTask(
|
|
|
704
715
|
name: string,
|
|
705
716
|
prompt: string,
|
|
706
717
|
assignedAgent?: string,
|
|
707
|
-
agentProfile?: string
|
|
718
|
+
agentProfile?: string,
|
|
719
|
+
parentTaskId?: string
|
|
708
720
|
): Promise<{ taskId: string; status: string; result?: string; error?: string }> {
|
|
709
721
|
const [workflow] = await db
|
|
710
722
|
.select()
|
|
@@ -717,6 +729,16 @@ export async function executeChildTask(
|
|
|
717
729
|
? classifyTaskProfile(name, prompt, assignedAgent)
|
|
718
730
|
: agentProfile;
|
|
719
731
|
|
|
732
|
+
// Inject parent task's document context into step prompt so file attachments
|
|
733
|
+
// from the original task are visible to every workflow child step
|
|
734
|
+
let enrichedPrompt = prompt;
|
|
735
|
+
if (parentTaskId) {
|
|
736
|
+
const docContext = await buildWorkflowDocumentContext(parentTaskId);
|
|
737
|
+
if (docContext) {
|
|
738
|
+
enrichedPrompt = `${docContext}\n\n${prompt}`;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
720
742
|
const taskId = crypto.randomUUID();
|
|
721
743
|
await db.insert(tasks).values({
|
|
722
744
|
id: taskId,
|
|
@@ -724,7 +746,7 @@ export async function executeChildTask(
|
|
|
724
746
|
workflowId,
|
|
725
747
|
scheduleId: null,
|
|
726
748
|
title: `[Workflow] ${name}`,
|
|
727
|
-
description:
|
|
749
|
+
description: enrichedPrompt,
|
|
728
750
|
status: "queued",
|
|
729
751
|
priority: 1,
|
|
730
752
|
assignedAgent: assignedAgent ?? null,
|
|
@@ -769,7 +791,8 @@ async function executeStep(
|
|
|
769
791
|
prompt: string,
|
|
770
792
|
state: WorkflowState,
|
|
771
793
|
assignedAgent?: string,
|
|
772
|
-
agentProfile?: string
|
|
794
|
+
agentProfile?: string,
|
|
795
|
+
parentTaskId?: string
|
|
773
796
|
): Promise<StepState> {
|
|
774
797
|
const stepState = state.stepStates.find((s) => s.stepId === stepId);
|
|
775
798
|
if (!stepState) throw new Error(`Step ${stepId} not found in state`);
|
|
@@ -783,7 +806,8 @@ async function executeStep(
|
|
|
783
806
|
stepName,
|
|
784
807
|
prompt,
|
|
785
808
|
assignedAgent,
|
|
786
|
-
agentProfile
|
|
809
|
+
agentProfile,
|
|
810
|
+
parentTaskId
|
|
787
811
|
);
|
|
788
812
|
|
|
789
813
|
stepState.taskId = result.taskId;
|
|
@@ -846,34 +870,6 @@ async function waitForApproval(
|
|
|
846
870
|
return false; // Timeout — treat as denied
|
|
847
871
|
}
|
|
848
872
|
|
|
849
|
-
/**
|
|
850
|
-
* Sync the parent (source) task's status with the workflow's final status.
|
|
851
|
-
* The parent task is linked via `sourceTaskId` in the workflow's definition JSON.
|
|
852
|
-
*/
|
|
853
|
-
async function syncSourceTaskStatus(
|
|
854
|
-
workflowId: string,
|
|
855
|
-
status: "completed" | "failed"
|
|
856
|
-
): Promise<void> {
|
|
857
|
-
try {
|
|
858
|
-
const result = await db
|
|
859
|
-
.select()
|
|
860
|
-
.from(workflows)
|
|
861
|
-
.where(eq(workflows.id, workflowId));
|
|
862
|
-
|
|
863
|
-
const workflow = Array.isArray(result) ? result[0] : undefined;
|
|
864
|
-
if (!workflow) return;
|
|
865
|
-
|
|
866
|
-
const def = JSON.parse(workflow.definition);
|
|
867
|
-
if (!def.sourceTaskId) return;
|
|
868
|
-
|
|
869
|
-
await db
|
|
870
|
-
.update(tasks)
|
|
871
|
-
.set({ status, updatedAt: new Date() })
|
|
872
|
-
.where(eq(tasks.id, def.sourceTaskId));
|
|
873
|
-
} catch (error) {
|
|
874
|
-
console.error(`[workflow-engine] Failed to sync source task status for workflow ${workflowId}:`, error);
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
873
|
|
|
878
874
|
/**
|
|
879
875
|
* Update workflow state in the database.
|
|
@@ -33,6 +33,8 @@ export interface WorkflowDefinition {
|
|
|
33
33
|
steps: WorkflowStep[];
|
|
34
34
|
loopConfig?: LoopConfig;
|
|
35
35
|
swarmConfig?: SwarmConfig;
|
|
36
|
+
/** Parent task ID — set when workflow is created from AI assist, used to propagate document context */
|
|
37
|
+
sourceTaskId?: string;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export type LoopStopReason =
|
package/tsconfig.json
CHANGED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import ReactMarkdown from "react-markdown";
|
|
4
|
-
import remarkGfm from "remark-gfm";
|
|
5
|
-
import type { DocumentWithRelations } from "./types";
|
|
6
|
-
|
|
7
|
-
interface DocumentPreviewProps {
|
|
8
|
-
document: DocumentWithRelations;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function DocumentPreview({ document: doc }: DocumentPreviewProps) {
|
|
12
|
-
const isImage = doc.mimeType.startsWith("image/");
|
|
13
|
-
const isPdf = doc.mimeType === "application/pdf";
|
|
14
|
-
const isMarkdown = doc.mimeType === "text/markdown";
|
|
15
|
-
const isText =
|
|
16
|
-
doc.mimeType.startsWith("text/") ||
|
|
17
|
-
doc.mimeType === "application/json";
|
|
18
|
-
|
|
19
|
-
if (isImage) {
|
|
20
|
-
return (
|
|
21
|
-
<div className="rounded-md overflow-hidden border border-border bg-muted/30 flex items-center justify-center">
|
|
22
|
-
<img
|
|
23
|
-
src={`/api/documents/${doc.id}/file?inline=1`}
|
|
24
|
-
alt={doc.originalName}
|
|
25
|
-
className="max-h-64 object-contain"
|
|
26
|
-
/>
|
|
27
|
-
</div>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (isPdf) {
|
|
32
|
-
return (
|
|
33
|
-
<div className="rounded-md overflow-hidden border border-border">
|
|
34
|
-
<iframe
|
|
35
|
-
src={`/api/documents/${doc.id}/file?inline=1`}
|
|
36
|
-
className="w-full h-64"
|
|
37
|
-
title={doc.originalName}
|
|
38
|
-
/>
|
|
39
|
-
</div>
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (isMarkdown && doc.extractedText) {
|
|
44
|
-
return (
|
|
45
|
-
<div className="rounded-md border border-border p-3 prose prose-sm dark:prose-invert max-h-64 overflow-y-auto">
|
|
46
|
-
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
|
47
|
-
{doc.extractedText.slice(0, 5000)}
|
|
48
|
-
</ReactMarkdown>
|
|
49
|
-
</div>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (isText && doc.extractedText) {
|
|
54
|
-
return (
|
|
55
|
-
<pre className="text-xs bg-muted p-3 rounded-md max-h-64 overflow-y-auto whitespace-pre-wrap break-words border border-border">
|
|
56
|
-
{doc.extractedText.slice(0, 5000)}
|
|
57
|
-
</pre>
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Fallback — no preview
|
|
62
|
-
return (
|
|
63
|
-
<div className="rounded-md border border-border p-6 text-center text-muted-foreground">
|
|
64
|
-
<p className="text-sm">No preview available for this file type.</p>
|
|
65
|
-
<p className="text-xs mt-1">Download the file to view its contents.</p>
|
|
66
|
-
</div>
|
|
67
|
-
);
|
|
68
|
-
}
|