Skip to content

Commit 054b373

Browse files
committed
Summarize folder document makeup
1 parent 8aa048d commit 054b373

2 files changed

Lines changed: 145 additions & 3 deletions

File tree

App.tsx

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import InfoView from './components/InfoView';
1818
import UpdateNotification from './components/UpdateNotification';
1919
import CreateFromTemplateModal from './components/CreateFromTemplateModal';
2020
import DocumentHistoryView from './components/PromptHistoryView';
21-
import FolderOverview, { FolderOverviewMetrics, FolderSearchResult, RecentDocumentSummary } from './components/FolderOverview';
21+
import FolderOverview, { type FolderOverviewMetrics, type FolderSearchResult, type RecentDocumentSummary, type DocTypeCount, type LanguageCount } from './components/FolderOverview';
2222
import { PlusIcon, FolderPlusIcon, TrashIcon, GearIcon, InfoIcon, TerminalIcon, DocumentDuplicateIcon, PencilIcon, CopyIcon, CommandIcon, CodeIcon, FolderDownIcon, FormatIcon, SparklesIcon } from './components/Icons';
2323
import AboutModal from './components/AboutModal';
2424
import Header from './components/Header';
@@ -27,7 +27,7 @@ import ConfirmModal from './components/ConfirmModal';
2727
import FatalError from './components/FatalError';
2828
import ContextMenu, { MenuItem } from './components/ContextMenu';
2929
import NewCodeFileModal from './components/NewCodeFileModal';
30-
import type { DocumentOrFolder, Command, LogMessage, DiscoveredLLMModel, DiscoveredLLMService, Settings, DocumentTemplate, ViewMode } from './types';
30+
import type { DocumentOrFolder, Command, LogMessage, DiscoveredLLMModel, DiscoveredLLMService, Settings, DocumentTemplate, ViewMode, DocType } from './types';
3131
import { IconProvider } from './contexts/IconContext';
3232
import { storageService } from './services/storageService';
3333
import { llmDiscoveryService } from './services/llmDiscoveryService';
@@ -371,6 +371,55 @@ const MainApp: React.FC = () => {
371371
return node.type === 'folder' ? 'Untitled Folder' : 'Untitled Document';
372372
};
373373

374+
const toTitleCase = (value: string) => {
375+
return value
376+
.split(/[-_\s]+/)
377+
.filter(Boolean)
378+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
379+
.join(' ');
380+
};
381+
382+
const addDocTypeCount = (map: Map<DocType, number>, docType: DocType) => {
383+
map.set(docType, (map.get(docType) ?? 0) + 1);
384+
};
385+
386+
const addLanguageCount = (
387+
map: Map<string, { label: string; count: number }>,
388+
value?: string | null,
389+
) => {
390+
const trimmed = (value ?? '').trim();
391+
const key = trimmed ? trimmed.toLowerCase() : 'unknown';
392+
const label = trimmed ? toTitleCase(trimmed) : 'Unknown';
393+
const existing = map.get(key);
394+
if (existing) {
395+
existing.count += 1;
396+
} else {
397+
map.set(key, { label, count: 1 });
398+
}
399+
};
400+
401+
const finalizeDocTypeCounts = (map: Map<DocType, number>): DocTypeCount[] =>
402+
Array.from(map.entries())
403+
.map(([type, count]) => ({ type, count }))
404+
.sort((a, b) => {
405+
if (a.count !== b.count) {
406+
return b.count - a.count;
407+
}
408+
return a.type.localeCompare(b.type);
409+
});
410+
411+
const finalizeLanguageCounts = (
412+
map: Map<string, { label: string; count: number }>,
413+
): LanguageCount[] =>
414+
Array.from(map.values())
415+
.map(({ label, count }) => ({ label, count }))
416+
.sort((a, b) => {
417+
if (a.count !== b.count) {
418+
return b.count - a.count;
419+
}
420+
return a.label.localeCompare(b.label);
421+
});
422+
374423
const computeFromTree = (folderNode: DocumentNode) => {
375424
const recordLatest = (() => {
376425
let latest: Date | null = null;
@@ -400,17 +449,24 @@ const MainApp: React.FC = () => {
400449
parentPath: [],
401450
}));
402451
const allDocuments: RecentDocumentSummary[] = [];
452+
const docTypeMap = new Map<DocType, number>();
453+
const languageMap = new Map<string, { label: string; count: number }>();
403454

404455
while (stack.length > 0) {
405456
const { node: current, parentPath } = stack.pop()!;
406457
recordLatest.update(current.updatedAt);
407458
if (current.type === 'document') {
408459
totalDocumentCount += 1;
460+
const docType = (current.doc_type ?? 'prompt') as DocType;
461+
addDocTypeCount(docTypeMap, docType);
462+
addLanguageCount(languageMap, current.language_hint);
409463
allDocuments.push({
410464
id: current.id,
411465
title: current.title,
412466
updatedAt: current.updatedAt,
413467
parentPath,
468+
docType,
469+
languageHint: current.language_hint ?? null,
414470
});
415471
} else if (current.type === 'folder') {
416472
totalFolderCount += 1;
@@ -438,6 +494,8 @@ const MainApp: React.FC = () => {
438494
totalItemCount: totalDocumentCount + totalFolderCount,
439495
lastUpdated: latestDate ? latestDate.toISOString() : null,
440496
recentDocuments,
497+
docTypeCounts: finalizeDocTypeCounts(docTypeMap),
498+
languageCounts: finalizeLanguageCounts(languageMap),
441499
},
442500
documents: allDocuments,
443501
};
@@ -486,17 +544,24 @@ const MainApp: React.FC = () => {
486544
parentPath: [],
487545
}));
488546
const allDocuments: RecentDocumentSummary[] = [];
547+
const docTypeMap = new Map<DocType, number>();
548+
const languageMap = new Map<string, { label: string; count: number }>();
489549

490550
while (stack.length > 0) {
491551
const { node: current, parentPath } = stack.pop()!;
492552
recordLatest.update(current.updatedAt);
493553
if (current.type === 'document') {
494554
totalDocumentCount += 1;
555+
const docType = (current.doc_type ?? 'prompt') as DocType;
556+
addDocTypeCount(docTypeMap, docType);
557+
addLanguageCount(languageMap, current.language_hint);
495558
allDocuments.push({
496559
id: current.id,
497560
title: current.title,
498561
updatedAt: current.updatedAt,
499562
parentPath,
563+
docType,
564+
languageHint: current.language_hint ?? null,
500565
});
501566
} else {
502567
totalFolderCount += 1;
@@ -524,6 +589,8 @@ const MainApp: React.FC = () => {
524589
totalItemCount: totalDocumentCount + totalFolderCount,
525590
lastUpdated: latestDate ? latestDate.toISOString() : null,
526591
recentDocuments,
592+
docTypeCounts: finalizeDocTypeCounts(docTypeMap),
593+
languageCounts: finalizeLanguageCounts(languageMap),
527594
},
528595
documents: allDocuments,
529596
};
@@ -1493,6 +1560,8 @@ const MainApp: React.FC = () => {
14931560
totalItemCount: 0,
14941561
lastUpdated: activeNode.updatedAt,
14951562
recentDocuments: [],
1563+
docTypeCounts: [],
1564+
languageCounts: [],
14961565
};
14971566
return (
14981567
<FolderOverview

components/FolderOverview.tsx

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
11
import React, { useRef } from 'react';
2-
import type { DocumentOrFolder } from '../types';
2+
import type { DocType, DocumentOrFolder } from '../types';
33
import Button from './Button';
44
import { FolderIcon, FileIcon, InfoIcon, PlusIcon, FolderPlusIcon, FolderDownIcon, PencilIcon, SearchIcon, XIcon } from './Icons';
55

6+
export interface DocTypeCount {
7+
type: DocType;
8+
count: number;
9+
}
10+
11+
export interface LanguageCount {
12+
label: string;
13+
count: number;
14+
}
15+
616
export interface FolderOverviewMetrics {
717
directDocumentCount: number;
818
directFolderCount: number;
@@ -11,13 +21,17 @@ export interface FolderOverviewMetrics {
1121
totalItemCount: number;
1222
lastUpdated: string | null;
1323
recentDocuments: RecentDocumentSummary[];
24+
docTypeCounts: DocTypeCount[];
25+
languageCounts: LanguageCount[];
1426
}
1527

1628
export interface RecentDocumentSummary {
1729
id: string;
1830
title: string;
1931
updatedAt: string | null;
2032
parentPath: string[];
33+
docType?: DocType;
34+
languageHint?: string | null;
2135
}
2236

2337
export interface FolderSearchResult extends RecentDocumentSummary {
@@ -70,6 +84,15 @@ const highlightMatches = (text: string, term: string): React.ReactNode => {
7084
});
7185
};
7286

87+
const DOC_TYPE_LABELS: Record<DocType, string> = {
88+
prompt: 'Prompts',
89+
source_code: 'Source code',
90+
pdf: 'PDFs',
91+
image: 'Images',
92+
};
93+
94+
const formatDocTypeLabel = (docType: DocType) => DOC_TYPE_LABELS[docType] ?? docType.replace(/_/g, ' ');
95+
7396
const StatCard: React.FC<{ label: string; value: number; icon: React.ReactNode }> = ({ label, value, icon }) => (
7497
<div className="flex items-center gap-4 rounded-lg border border-border-color bg-background/80 px-4 py-5 shadow-sm">
7598
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-primary/10 text-primary">
@@ -102,9 +125,13 @@ const FolderOverview: React.FC<FolderOverviewProps> = ({
102125
totalItemCount,
103126
lastUpdated,
104127
recentDocuments,
128+
docTypeCounts,
129+
languageCounts,
105130
} = metrics;
106131

107132
const hasChildren = totalItemCount > 0;
133+
const hasDocTypeSummary = docTypeCounts.some(({ count }) => count > 0);
134+
const hasLanguageSummary = languageCounts.some(({ count }) => count > 0);
108135
const fileInputRef = useRef<HTMLInputElement | null>(null);
109136

110137
const handleImportClick = () => {
@@ -348,6 +375,52 @@ const FolderOverview: React.FC<FolderOverviewProps> = ({
348375
</div>
349376
</div>
350377

378+
<div className="mt-8 rounded-lg border border-border-color bg-background/70 p-6">
379+
<h2 className="text-sm font-semibold uppercase tracking-wide text-text-secondary">Contents at a glance</h2>
380+
<div className="mt-4 grid gap-6 md:grid-cols-2">
381+
<div>
382+
<h3 className="text-xs font-semibold uppercase tracking-wide text-text-tertiary">Document types</h3>
383+
{hasDocTypeSummary ? (
384+
<div className="mt-3 flex flex-wrap gap-2">
385+
{docTypeCounts.map(({ type, count }) => (
386+
<span
387+
key={type}
388+
className="inline-flex items-center gap-2 rounded-full bg-primary/10 px-3 py-1 text-xs font-medium uppercase tracking-wide text-primary"
389+
>
390+
<span>{formatDocTypeLabel(type)}</span>
391+
<span className="rounded-full bg-primary/20 px-2 py-0.5 text-[10px] font-semibold text-primary">
392+
{count}
393+
</span>
394+
</span>
395+
))}
396+
</div>
397+
) : (
398+
<p className="mt-3 text-sm text-text-secondary">No documents yet.</p>
399+
)}
400+
</div>
401+
<div>
402+
<h3 className="text-xs font-semibold uppercase tracking-wide text-text-tertiary">Languages</h3>
403+
{hasLanguageSummary ? (
404+
<div className="mt-3 flex flex-wrap gap-2">
405+
{languageCounts.map(({ label, count }) => (
406+
<span
407+
key={label.toLowerCase()}
408+
className="inline-flex items-center gap-2 rounded-full bg-text-secondary/10 px-3 py-1 text-xs font-medium uppercase tracking-wide text-text-secondary"
409+
>
410+
<span>{label}</span>
411+
<span className="rounded-full bg-text-secondary/20 px-2 py-0.5 text-[10px] font-semibold text-text-secondary">
412+
{count}
413+
</span>
414+
</span>
415+
))}
416+
</div>
417+
) : (
418+
<p className="mt-3 text-sm text-text-secondary">No language information yet.</p>
419+
)}
420+
</div>
421+
</div>
422+
</div>
423+
351424
<div className="mt-10 rounded-lg border border-border-color bg-background/70 p-6">
352425
<h2 className="text-sm font-semibold uppercase tracking-wide text-text-secondary">
353426
Recently updated in this folder

0 commit comments

Comments
 (0)