mirror of
https://github.com/xCyanGrizzly/DragonsStash.git
synced 2026-09-21 05:21:43 +00:00
feat(stls): flat package list, drop heuristic auto-grouping
Render the STL view as a flat per-package list (listDisplayItems no longer collapses packages into group rows) and hide the Ungrouped tab, now that the creator filter organizes the list. Remove the worker's heuristic auto-grouping passes (rule/time/pattern/creator/zip-path/reply-chain/caption); album grouping is kept. Existing groups and manual grouping actions are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -500,14 +500,9 @@ export function StlTable({
|
|||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="ungrouped" className="gap-1.5">
|
{/* "Ungrouped" tab hidden: the STL list is now flat and grouping is
|
||||||
Ungrouped
|
no longer surfaced here. The tab content below is kept (unreachable)
|
||||||
{ungroupedTotalCount > 0 && (
|
to avoid churn; remove it and its data fetch if grouping is dropped. */}
|
||||||
<Badge variant="secondary" className="h-5 px-1.5 text-[10px]">
|
|
||||||
{ungroupedTotalCount}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</TabsTrigger>
|
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="packages" className="space-y-4">
|
<TabsContent value="packages" className="space-y-4">
|
||||||
|
|||||||
+12
-12
@@ -135,31 +135,31 @@ export async function listDisplayItems(options: {
|
|||||||
const sortCol = sortBy === "fileName" ? `"fileName"` : sortBy === "fileSize" ? `"fileSize"` : `"indexedAt"`;
|
const sortCol = sortBy === "fileName" ? `"fileName"` : sortBy === "fileSize" ? `"fileSize"` : `"indexedAt"`;
|
||||||
const sortDir = order === "asc" ? "ASC" : "DESC";
|
const sortDir = order === "asc" ? "ASC" : "DESC";
|
||||||
|
|
||||||
// Step 1: Count display items
|
// NOTE: The STL list is intentionally FLAT — every package is its own display
|
||||||
|
// row regardless of packageGroupId. Grouping is no longer surfaced in this
|
||||||
|
// view (the creator column/filter organizes the list instead). PackageGroup
|
||||||
|
// rows and the manual grouping actions still exist in the DB/UI; they just
|
||||||
|
// don't drive this list's layout anymore.
|
||||||
|
|
||||||
|
// Step 1: Count display items (one per package)
|
||||||
const countResult = await prisma.$queryRawUnsafe<[{ count: bigint }]>(
|
const countResult = await prisma.$queryRawUnsafe<[{ count: bigint }]>(
|
||||||
`SELECT COUNT(*) AS count FROM (
|
`SELECT COUNT(*) AS count FROM packages p ${whereClause}`,
|
||||||
SELECT DISTINCT COALESCE(p."packageGroupId", p."id") AS display_id
|
|
||||||
FROM packages p
|
|
||||||
${whereClause}
|
|
||||||
) AS display_items`,
|
|
||||||
...params
|
...params
|
||||||
);
|
);
|
||||||
const total = Number(countResult[0].count);
|
const total = Number(countResult[0].count);
|
||||||
|
|
||||||
// Step 2: Get display item IDs for this page
|
// Step 2: Get package IDs for this page
|
||||||
const limitParam = paramIdx++;
|
const limitParam = paramIdx++;
|
||||||
const offsetParam = paramIdx++;
|
const offsetParam = paramIdx++;
|
||||||
const displayRows = await prisma.$queryRawUnsafe<
|
const displayRows = await prisma.$queryRawUnsafe<
|
||||||
{ display_id: string; display_type: string }[]
|
{ display_id: string; display_type: string }[]
|
||||||
>(
|
>(
|
||||||
`SELECT
|
`SELECT
|
||||||
COALESCE(p."packageGroupId", p."id") AS display_id,
|
p."id" AS display_id,
|
||||||
CASE WHEN p."packageGroupId" IS NOT NULL THEN 'group' ELSE 'package' END AS display_type,
|
'package' AS display_type,
|
||||||
MAX(p.${sortCol}) AS sort_value
|
p.${sortCol} AS sort_value
|
||||||
FROM packages p
|
FROM packages p
|
||||||
${whereClause}
|
${whereClause}
|
||||||
GROUP BY COALESCE(p."packageGroupId", p."id"),
|
|
||||||
CASE WHEN p."packageGroupId" IS NOT NULL THEN 'group' ELSE 'package' END
|
|
||||||
ORDER BY sort_value ${sortDir}
|
ORDER BY sort_value ${sortDir}
|
||||||
LIMIT $${limitParam} OFFSET $${offsetParam}`,
|
LIMIT $${limitParam} OFFSET $${offsetParam}`,
|
||||||
...params, limit, (page - 1) * limit
|
...params, limit, (page - 1) * limit
|
||||||
|
|||||||
+8
-29
@@ -65,7 +65,7 @@ import { readRarContents } from "./archive/rar-reader.js";
|
|||||||
import { read7zContents } from "./archive/sevenz-reader.js";
|
import { read7zContents } from "./archive/sevenz-reader.js";
|
||||||
import { byteLevelSplit, concatenateFiles } from "./archive/split.js";
|
import { byteLevelSplit, concatenateFiles } from "./archive/split.js";
|
||||||
import { uploadToChannel, UploadStallError } from "./upload/channel.js";
|
import { uploadToChannel, UploadStallError } from "./upload/channel.js";
|
||||||
import { processAlbumGroups, processRuleBasedGroups, processTimeWindowGroups, processPatternGroups, processCreatorGroups, processZipPathGroups, processReplyChainGroups, processCaptionGroups, detectGroupingConflicts, type IndexedPackageRef } from "./grouping.js";
|
import { processAlbumGroups, detectGroupingConflicts, type IndexedPackageRef } from "./grouping.js";
|
||||||
import { db } from "./db/client.js";
|
import { db } from "./db/client.js";
|
||||||
import type { TelegramAccount, TelegramChannel } from "@prisma/client";
|
import type { TelegramAccount, TelegramChannel } from "@prisma/client";
|
||||||
import type { Client } from "tdl";
|
import type { Client } from "tdl";
|
||||||
@@ -1479,34 +1479,13 @@ async function processArchiveSets(
|
|||||||
scanResult.photos
|
scanResult.photos
|
||||||
);
|
);
|
||||||
|
|
||||||
// Auto-grouping passes (gated by per-channel flag)
|
// Heuristic auto-grouping passes (rule/time/pattern/creator/zip-path/
|
||||||
const channelRecord = await db.telegramChannel.findUnique({
|
// reply-chain/caption) were removed: the STL view is now a flat list
|
||||||
where: { id: channel.id },
|
// organized by the creator filter, so automatically inventing groups at
|
||||||
select: { autoGroupEnabled: true },
|
// ingestion is no longer wanted. Album grouping above is kept because it
|
||||||
});
|
// reflects real upload structure (files posted together as one Telegram
|
||||||
|
// album), not a heuristic guess. Existing groups and the manual grouping
|
||||||
if (channelRecord?.autoGroupEnabled !== false) {
|
// actions in the UI are unaffected.
|
||||||
// Learned rule-based grouping (from manual overrides)
|
|
||||||
await processRuleBasedGroups(channel.id, indexedPackageRefs);
|
|
||||||
|
|
||||||
// Time-window grouping for remaining ungrouped packages
|
|
||||||
await processTimeWindowGroups(channel.id, indexedPackageRefs);
|
|
||||||
|
|
||||||
// Pattern-based grouping (date patterns, project slugs)
|
|
||||||
await processPatternGroups(channel.id, indexedPackageRefs);
|
|
||||||
|
|
||||||
// Creator-based grouping (3+ files from same creator)
|
|
||||||
await processCreatorGroups(channel.id, indexedPackageRefs);
|
|
||||||
|
|
||||||
// ZIP path prefix grouping (shared root folder inside archives)
|
|
||||||
await processZipPathGroups(channel.id, indexedPackageRefs);
|
|
||||||
|
|
||||||
// Reply chain grouping (messages replying to same root)
|
|
||||||
await processReplyChainGroups(channel.id, indexedPackageRefs);
|
|
||||||
|
|
||||||
// Caption fuzzy match grouping
|
|
||||||
await processCaptionGroups(channel.id, indexedPackageRefs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for potential grouping conflicts
|
// Check for potential grouping conflicts
|
||||||
await detectGroupingConflicts(channel.id, indexedPackageRefs);
|
await detectGroupingConflicts(channel.id, indexedPackageRefs);
|
||||||
|
|||||||
Reference in New Issue
Block a user