File size: 6,016 Bytes
5cfaefc 3578090 07eaad0 6043dbd 3578090 77ca676 6043dbd 3578090 77ca676 3578090 dcae38a 75d0b6d b79b057 f57fcd2 bd39ed2 5edb4ed 07eaad0 3578090 07eaad0 3578090 07eaad0 dcae38a 07eaad0 3578090 07eaad0 dcae38a 07eaad0 dcae38a 07eaad0 5edb4ed 07eaad0 9273670 85b7cce 9273670 311e37f 9273670 3578090 07eaad0 dcae38a 3578090 3be437a 3578090 1600ec8 5cfaefc b79b057 75d0b6d 1600ec8 75d0b6d 1600ec8 75d0b6d 5cfaefc 75d0b6d 5cfaefc 6043dbd f57fcd2 abf0e97 e70144e f57fcd2 e70144e f57fcd2 e70144e f57fcd2 6043dbd f57fcd2 6860011 f57fcd2 6043dbd 6860011 6043dbd f57fcd2 6043dbd f57fcd2 6043dbd bd39ed2 6043dbd f57fcd2 6043dbd bd39ed2 f57fcd2 e70144e f57fcd2 6043dbd 3578090 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
import { useCallback, useEffect } from 'react'
import { AsyncSelect } from '@/components/ui/AsyncSelect'
import { useSettingsStore } from '@/stores/settings'
import { useGraphStore } from '@/stores/graph'
import { labelListLimit, controlButtonVariant } from '@/lib/constants'
import MiniSearch from 'minisearch'
import { useTranslation } from 'react-i18next'
import { RefreshCw } from 'lucide-react'
import Button from '@/components/ui/Button'
const GraphLabels = () => {
const { t } = useTranslation()
const label = useSettingsStore.use.queryLabel()
const allDatabaseLabels = useGraphStore.use.allDatabaseLabels()
const labelsFetchAttempted = useGraphStore.use.labelsFetchAttempted()
// Remove initial label fetch effect as it's now handled by fetchGraph based on lastSuccessfulQueryLabel
const getSearchEngine = useCallback(() => {
// Create search engine
const searchEngine = new MiniSearch({
idField: 'id',
fields: ['value'],
searchOptions: {
prefix: true,
fuzzy: 0.2,
boost: {
label: 2
}
}
})
// Add documents
const documents = allDatabaseLabels.map((str, index) => ({ id: index, value: str }))
searchEngine.addAll(documents)
return {
labels: allDatabaseLabels,
searchEngine
}
}, [allDatabaseLabels])
const fetchData = useCallback(
async (query?: string): Promise<string[]> => {
const { labels, searchEngine } = getSearchEngine()
let result: string[] = labels
if (query) {
// Search labels using MiniSearch
result = searchEngine.search(query).map((r: { id: number }) => labels[r.id])
// Add middle-content matching if results are few
// This enables matching content in the middle of text, not just from the beginning
if (result.length < 15) {
// Get already matched labels to avoid duplicates
const matchedLabels = new Set(result)
// Perform middle-content matching on all labels
const middleMatchResults = labels.filter(label => {
// Skip already matched labels
if (matchedLabels.has(label)) return false
// Match if label contains query string but doesn't start with it
return label &&
typeof label === 'string' &&
!label.toLowerCase().startsWith(query.toLowerCase()) &&
label.toLowerCase().includes(query.toLowerCase())
})
// Merge results
result = [...result, ...middleMatchResults]
}
}
return result.length <= labelListLimit
? result
: [...result.slice(0, labelListLimit), '...']
},
[getSearchEngine]
)
// Validate label
useEffect(() => {
if (labelsFetchAttempted) {
if (allDatabaseLabels.length > 1) {
if (label && label !== '*' && !allDatabaseLabels.includes(label)) {
console.log(`Label "${label}" not in available labels, setting to "*"`);
useSettingsStore.getState().setQueryLabel('*');
} else {
console.log(`Label "${label}" is valid`);
}
} else if (label && allDatabaseLabels.length <= 1 && label && label !== '*') {
console.log('Available labels list is empty, setting label to empty');
useSettingsStore.getState().setQueryLabel('');
}
useGraphStore.getState().setLabelsFetchAttempted(false)
}
}, [allDatabaseLabels, label, labelsFetchAttempted]);
const handleRefresh = useCallback(() => {
// Reset fetch status flags
useGraphStore.getState().setLabelsFetchAttempted(false)
useGraphStore.getState().setGraphDataFetchAttempted(false)
// Clear last successful query label to ensure labels are fetched
useGraphStore.getState().setLastSuccessfulQueryLabel('')
// Get current label
const currentLabel = useSettingsStore.getState().queryLabel
// If current label is empty, use default label '*'
if (!currentLabel) {
useSettingsStore.getState().setQueryLabel('*')
} else {
// Trigger data reload
useSettingsStore.getState().setQueryLabel('')
setTimeout(() => {
useSettingsStore.getState().setQueryLabel(currentLabel)
}, 0)
}
}, []);
return (
<div className="flex items-center">
{/* Always show refresh button */}
<Button
size="icon"
variant={controlButtonVariant}
onClick={handleRefresh}
tooltip={t('graphPanel.graphLabels.refreshTooltip')}
className="mr-2"
>
<RefreshCw className="h-4 w-4" />
</Button>
<AsyncSelect<string>
className="min-w-[300px]"
triggerClassName="max-h-8"
searchInputClassName="max-h-8"
triggerTooltip={t('graphPanel.graphLabels.selectTooltip')}
fetcher={fetchData}
renderOption={(item) => <div>{item}</div>}
getOptionValue={(item) => item}
getDisplayValue={(item) => <div>{item}</div>}
notFound={<div className="py-6 text-center text-sm">No labels found</div>}
label={t('graphPanel.graphLabels.label')}
placeholder={t('graphPanel.graphLabels.placeholder')}
value={label !== null ? label : '*'}
onChange={(newLabel) => {
const currentLabel = useSettingsStore.getState().queryLabel;
// select the last item means query all
if (newLabel === '...') {
newLabel = '*';
}
// Handle reselecting the same label
if (newLabel === currentLabel && newLabel !== '*') {
newLabel = '*';
}
// Reset graphDataFetchAttempted flag to ensure data fetch is triggered
useGraphStore.getState().setGraphDataFetchAttempted(false);
// Update the label to trigger data loading
useSettingsStore.getState().setQueryLabel(newLabel);
}}
clearable={false} // Prevent clearing value on reselect
/>
</div>
)
}
export default GraphLabels
|