11import React , { useEffect , useMemo , useState } from 'react' ;
22import { useAppStore } from '../store/useAppStore' ;
3- import type { DatasetRecord } from '../types' ;
3+ import { getDatasetProfile } from '../data/datasetProfiles' ;
4+ import type { DatasetRecord , DatasetProfile } from '../types' ;
45
56type AxisKey = 'length' | 'domain' | 'difficulty' ;
67type TargetConfig = Record < AxisKey , Record < string , number > > ;
@@ -12,6 +13,7 @@ const DEFAULT_TARGETS: TargetConfig = {
1213} ;
1314
1415const STORAGE_KEY = 'stratified-targets' ;
16+ const buildStorageKey = ( profileId : string ) => `${ STORAGE_KEY } -${ profileId || 'default' } ` ;
1517
1618const clamp = ( value : number ) => Math . max ( 0 , Math . min ( 100 , Number . isFinite ( value ) ? value : 0 ) ) ;
1719
@@ -42,43 +44,110 @@ const getDifficulty = (record: DatasetRecord): string => {
4244 return ( record as any ) ?. metadata ?. difficulty || 'unlabeled' ;
4345} ;
4446
47+ const buildDomainTargets = ( profile : DatasetProfile | null , records : DatasetRecord [ ] ) : Record < string , number > => {
48+ const fromTopics = profile ?. topics && profile . topics . length > 0
49+ ? profile . topics . map ( topic => topic . toString ( ) . toLowerCase ( ) )
50+ : null ;
51+ if ( fromTopics ) {
52+ const even = 100 / fromTopics . length ;
53+ return fromTopics . reduce < Record < string , number > > ( ( acc , key ) => {
54+ acc [ key ] = Number ( even . toFixed ( 2 ) ) ;
55+ return acc ;
56+ } , { } ) ;
57+ }
58+
59+ const domainCounts : Record < string , number > = { } ;
60+ records . forEach ( record => {
61+ const domainKey = getDomain ( record ) . toString ( ) . toLowerCase ( ) ;
62+ domainCounts [ domainKey ] = ( domainCounts [ domainKey ] || 0 ) + 1 ;
63+ } ) ;
64+ const domainKeys = Object . keys ( domainCounts ) ;
65+ if ( domainKeys . length === 0 ) return DEFAULT_TARGETS . domain ;
66+
67+ const evenSplit = domainKeys . reduce < Record < string , number > > ( ( acc , key ) => {
68+ acc [ key ] = Number ( ( 100 / domainKeys . length ) . toFixed ( 2 ) ) ;
69+ return acc ;
70+ } , { } ) ;
71+
72+ return evenSplit ;
73+ } ;
74+
4575export const StratifiedPlannerView : React . FC = ( ) => {
4676 const dataset = useAppStore ( state => state . dataset ) ;
77+ const datasetProfileId = useAppStore ( state => state . datasetProfileId ) ;
4778 const updateConfig = useAppStore ( state => state . updateConfig ) ;
4879 const autoInject = useAppStore ( state => state . config . autoInjectStrataPrompt ?? false ) ;
4980 const lastHint = useAppStore ( state => state . config . stratifiedNextAsk ?? '' ) ;
5081
51- const [ targets , setTargets ] = useState < TargetConfig > ( ( ) => {
52- if ( typeof window === 'undefined' ) return DEFAULT_TARGETS ;
82+ const filteredDataset = useMemo ( ( ) => {
83+ return dataset . filter ( record => ( record as any ) ?. profileId === datasetProfileId ) ;
84+ } , [ dataset , datasetProfileId ] ) ;
85+
86+ const storageKey = useMemo ( ( ) => buildStorageKey ( String ( datasetProfileId ?? 'default' ) ) , [ datasetProfileId ] ) ;
87+
88+ const [ targets , setTargets ] = useState < TargetConfig > ( DEFAULT_TARGETS ) ;
89+ const [ hasCustomTargets , setHasCustomTargets ] = useState < boolean > ( false ) ;
90+ const [ promptPreview , setPromptPreview ] = useState < string > ( '' ) ;
91+ const [ autoInjectEnabled , setAutoInjectEnabled ] = useState < boolean > ( autoInject ) ;
92+ const activeProfile = useMemo ( ( ) => getDatasetProfile ( datasetProfileId ) , [ datasetProfileId ] ) ;
93+
94+ useEffect ( ( ) => {
95+ if ( typeof window === 'undefined' ) return ;
5396 try {
54- const raw = window . localStorage . getItem ( STORAGE_KEY ) ;
55- if ( ! raw ) return DEFAULT_TARGETS ;
97+ const raw = window . localStorage . getItem ( storageKey ) ;
98+ if ( ! raw ) {
99+ setTargets ( {
100+ ...DEFAULT_TARGETS ,
101+ domain : buildDomainTargets ( activeProfile , filteredDataset ) ,
102+ } ) ;
103+ setHasCustomTargets ( false ) ;
104+ return ;
105+ }
56106 const parsed = JSON . parse ( raw ) ;
57- return { ...DEFAULT_TARGETS , ...parsed } ;
107+ setTargets ( { ...DEFAULT_TARGETS , ...parsed } ) ;
108+ setHasCustomTargets ( true ) ;
58109 } catch {
59- return DEFAULT_TARGETS ;
110+ setTargets ( {
111+ ...DEFAULT_TARGETS ,
112+ domain : buildDomainTargets ( activeProfile , filteredDataset ) ,
113+ } ) ;
114+ setHasCustomTargets ( false ) ;
60115 }
61- } ) ;
62- const [ promptPreview , setPromptPreview ] = useState < string > ( '' ) ;
63- const [ autoInjectEnabled , setAutoInjectEnabled ] = useState < boolean > ( autoInject ) ;
116+ } , [ storageKey , filteredDataset , activeProfile ] ) ;
64117
65118 useEffect ( ( ) => {
66119 if ( typeof window !== 'undefined' ) {
67- window . localStorage . setItem ( STORAGE_KEY , JSON . stringify ( targets ) ) ;
120+ window . localStorage . setItem ( storageKey , JSON . stringify ( targets ) ) ;
68121 }
69- } , [ targets ] ) ;
122+ } , [ targets , storageKey ] ) ;
70123
71124 useEffect ( ( ) => {
72125 setAutoInjectEnabled ( autoInject ) ;
73126 } , [ autoInject ] ) ;
74127
128+ useEffect ( ( ) => {
129+ if ( hasCustomTargets ) return ;
130+ if ( ! filteredDataset . length && ! ( activeProfile ?. topics ?. length ) ) return ;
131+ const dynamicDefaults = {
132+ ...DEFAULT_TARGETS ,
133+ domain : buildDomainTargets ( activeProfile , filteredDataset ) ,
134+ } ;
135+ const currentDomain = targets . domain ;
136+ const nextDomain = dynamicDefaults . domain ;
137+ const sameKeys = Object . keys ( currentDomain ) . length === Object . keys ( nextDomain ) . length &&
138+ Object . keys ( currentDomain ) . every ( key => Math . abs ( ( currentDomain [ key ] ?? 0 ) - ( nextDomain [ key ] ?? 0 ) ) < 0.001 ) ;
139+ if ( ! sameKeys ) {
140+ setTargets ( prev => ( { ...prev , domain : nextDomain } ) ) ;
141+ }
142+ } , [ filteredDataset , hasCustomTargets , targets . domain , activeProfile ] ) ;
143+
75144 const stats = useMemo ( ( ) => {
76145 const totals : Record < AxisKey , Record < string , number > > = {
77146 length : { } ,
78147 domain : { } ,
79148 difficulty : { } ,
80149 } ;
81- dataset . forEach ( record => {
150+ filteredDataset . forEach ( record => {
82151 const lengthBucket = bucketLength ( record ) ;
83152 totals . length [ lengthBucket ] = ( totals . length [ lengthBucket ] || 0 ) + 1 ;
84153
@@ -88,7 +157,7 @@ export const StratifiedPlannerView: React.FC = () => {
88157 const difficulty = getDifficulty ( record ) . toString ( ) . toLowerCase ( ) ;
89158 totals . difficulty [ difficulty ] = ( totals . difficulty [ difficulty ] || 0 ) + 1 ;
90159 } ) ;
91- const totalRecords = dataset . length || 1 ;
160+ const totalRecords = filteredDataset . length || 1 ;
92161 const toPercent = ( counts : Record < string , number > ) =>
93162 Object . fromEntries (
94163 Object . entries ( counts ) . map ( ( [ k , v ] ) => [ k , Number ( ( ( v / totalRecords ) * 100 ) . toFixed ( 2 ) ) ] )
@@ -101,7 +170,7 @@ export const StratifiedPlannerView: React.FC = () => {
101170 difficulty : toPercent ( totals . difficulty ) ,
102171 } ,
103172 } ;
104- } , [ dataset ] ) ;
173+ } , [ filteredDataset ] ) ;
105174
106175 const computeGaps = ( axis : AxisKey ) => {
107176 const desired = targets [ axis ] ;
@@ -148,7 +217,7 @@ export const StratifiedPlannerView: React.FC = () => {
148217 useEffect ( ( ) => {
149218 recomputePrompt ( ) ;
150219 // eslint-disable-next-line react-hooks/exhaustive-deps
151- } , [ dataset , targets ] ) ;
220+ } , [ filteredDataset , targets ] ) ;
152221
153222 useEffect ( ( ) => {
154223 if ( promptPreview && promptPreview !== lastHint ) {
@@ -158,6 +227,7 @@ export const StratifiedPlannerView: React.FC = () => {
158227 } , [ promptPreview ] ) ;
159228
160229 const handleTargetChange = ( axis : AxisKey , bucket : string , value : number ) => {
230+ setHasCustomTargets ( true ) ;
161231 setTargets ( prev => ( {
162232 ...prev ,
163233 [ axis ] : {
@@ -241,7 +311,7 @@ export const StratifiedPlannerView: React.FC = () => {
241311 </ p >
242312 </ div >
243313 < div className = "bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 text-sm text-gray-200" >
244- Dataset size: < span className = "font-semibold text-white" > { dataset . length . toLocaleString ( ) } </ span > records
314+ Dataset size: < span className = "font-semibold text-white" > { filteredDataset . length . toLocaleString ( ) } </ span > records
245315 </ div >
246316 </ header >
247317
0 commit comments