Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "joplin-plugin-note-categorization",
"version": "0.1.7",
"version": "0.1.8",
"scripts": {
"dist": "webpack --env joplin-plugin-config=buildMain && webpack --env joplin-plugin-config=buildExtraScripts && npm run copyAssets && webpack --env joplin-plugin-config=createArchive",
"prepare": "npm run dist",
Expand Down
2 changes: 1 addition & 1 deletion src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"manifest_version": 1,
"id": "com.harsh16gupta.notecategorization",
"app_min_version": "3.5",
"version": "0.1.7",
"version": "0.1.8",
"name": "Note Categorization Plugin",
"description": "AI-based note categorisation: clusters notes semantically, suggests tags and notebook structures, and detects stale notes.",
"author": "Harsh Gupta",
Expand Down
13 changes: 3 additions & 10 deletions src/pipeline/clustering/autoK.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { DistanceFn, silhouetteScore } from './metrics';
import { kmeans } from './kmeans';
// NOTE: kmedoids is not used in the default pipeline (too slow), but kept here for manual benchmarking
import { kmedoids } from './kmedoids';
import { log } from '../../utils/logger';

/** Absolute minimum K to try (silhouette needs at least 2 clusters). */
Expand Down Expand Up @@ -107,23 +105,18 @@ export function computeKRange(n: number): [number, number] {
* produces more useful note categories.
*
* @param vectors Input data points (N x D), already UMAP-reduced if applicable
* @param algorithm Which algorithm to use: 'kmeans' or 'kmedoids' (note: kmedoids is not used in the default pipeline)
* @param algorithm Which algorithm to use: 'kmeans'
* @param distFn Distance function (cosine or euclidean)
* @param seed Seed for reproducible initialization
* @returns The optimal K, its assignments, and its silhouette score
*/
export function findOptimalK(
vectors: number[][],
algorithm: 'kmeans' | 'kmedoids',
distFn: DistanceFn,
seed: number,
): AutoKResult {
export function findOptimalK(vectors: number[][], algorithm: 'kmeans', distFn: DistanceFn, seed: number): AutoKResult {
const n = vectors.length;
const [minK, maxK] = computeKRange(n);

log(`Auto-K: sweeping K=${minK}..${maxK} for ${algorithm} (N=${n})`);

const clusterFn = algorithm === 'kmeans' ? kmeans : kmedoids;
const clusterFn = kmeans;

// Collect all valid (k, score, assignments) candidates
const candidates: { k: number; score: number; assignments: number[] }[] = [];
Expand Down
6 changes: 1 addition & 5 deletions src/pipeline/clustering/benchmark.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { CategorizationConfig, BenchmarkResult, ClusteringStrategy } from '../../types/cluster';
import { DistanceFn, getDistanceFn, silhouetteScore, euclideanDistance } from './metrics';
import { kmeans } from './kmeans';
// NOTE: kmedoids is not used in the default pipeline (too slow), but kept here for manual benchmarking
import { kmedoids } from './kmedoids';
import { hdbscan } from './hdbscan';
import { findOptimalK } from './autoK';
import { UmapProjector } from '../UmapProjector';
Expand All @@ -26,8 +24,6 @@ export function runStrategy(
switch (strategy.algorithm) {
case 'kmeans':
return kmeans(vectors, strategy.K ?? DEFAULT_K, distFn, seed);
case 'kmedoids': // NOTE: not used in default pipeline strategies (too slow)
return kmedoids(vectors, strategy.K ?? DEFAULT_K, distFn, seed);
case 'hdbscan':
return hdbscan(vectors, strategy.minClusterSize ?? DEFAULT_MIN_CLUSTER_SIZE, strategy.minSamples, distFn);
default:
Expand Down Expand Up @@ -150,7 +146,7 @@ export function benchmark(
let assignments: number[];
let score: number;

if (strategy.K === 'auto' && (strategy.algorithm === 'kmeans' || strategy.algorithm === 'kmedoids')) {
if (strategy.K === 'auto' && strategy.algorithm === 'kmeans') {
// Auto-K: sweep K range and pick the best
const autoResult = findOptimalK(clusteringVectors, strategy.algorithm, clusterDistFn, config.seed);
assignments = autoResult.assignments;
Expand Down
138 changes: 0 additions & 138 deletions src/pipeline/clustering/kmedoids.ts

This file was deleted.

5 changes: 2 additions & 3 deletions src/types/cluster.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
// NOTE: 'kmedoids' is kept in the type for compatibility but is not used in the default pipeline strategies (too slow)
export type ClusteringAlgorithm = 'kmeans' | 'kmedoids' | 'hdbscan';
export type ClusteringAlgorithm = 'kmeans' | 'hdbscan';

export interface ClusteringStrategy {
/** Human-readable label for this run, e.g. 'kmeans-5' */
name: string;
algorithm: ClusteringAlgorithm;
/** Number of clusters (kmeans / kmedoids). Use 'auto' for automatic selection via silhouette sweep. Note: kmedoids is not active in the default pipeline. */
/** Number of clusters (kmeans). Use 'auto' for automatic selection via silhouette sweep. */
K?: number | 'auto';
/** Minimum points to form a cluster (hdbscan, default: 3) */
minClusterSize?: number;
Expand Down
2 changes: 1 addition & 1 deletion src/webview/pages/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const SettingsPage: React.FC = () => {
• <strong>Embedding Model:</strong> all-MiniLM-L6-v2 (384-dim)
</div>
<div className="config-card-item">
• <strong>Clustering Strategies:</strong> Auto K-Means, Auto K-Medoids, HDBSCAN
• <strong>Clustering Strategies:</strong> Auto K-Means, HDBSCAN
</div>
</div>
</div>
Expand Down
4 changes: 1 addition & 3 deletions test/pipeline/clustering/autoK.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,9 @@ describe('autoK findOptimalK', () => {
expect(res1).toEqual(res2);
});

it('works with both kmeans and kmedoids', () => {
it('finds optimal K using kmeans', () => {
const resKmeans = findOptimalK(THREE_CLUSTERS, 'kmeans', euclideanDistance, 42);
const resKmedoids = findOptimalK(THREE_CLUSTERS, 'kmedoids', euclideanDistance, 42);
expect(resKmeans.bestK).toBe(3);
expect(resKmedoids.bestK).toBe(3);
});

it('falls back to K=1 when no valid clustering is possible', () => {
Expand Down
78 changes: 0 additions & 78 deletions test/pipeline/clustering/kmedoids.test.ts

This file was deleted.

Loading