File size: 4,644 Bytes
5611aed
3578090
 
07eaad0
 
3578090
77ca676
3578090
 
77ca676
3578090
dcae38a
5611aed
2e179f3
5611aed
 
2e179f3
5611aed
 
 
 
2e179f3
5611aed
 
 
 
 
2e179f3
5611aed
2e179f3
5611aed
 
 
 
 
 
 
 
 
 
 
 
 
3578090
5edb4ed
07eaad0
 
 
 
 
 
 
 
 
3578090
07eaad0
 
3578090
07eaad0
dcae38a
07eaad0
3578090
07eaad0
dcae38a
07eaad0
 
dcae38a
07eaad0
 
 
5edb4ed
07eaad0
 
 
 
85b7cce
3578090
 
07eaad0
 
dcae38a
3578090
3be437a
3578090
 
 
 
 
 
 
77ca676
3578090
 
 
 
 
77ca676
 
dcae38a
 
 
3be437a
9defefd
dcae38a
 
 
2e179f3
d3a437a
 
2e179f3
d3a437a
 
 
 
2e179f3
d3a437a
 
 
 
 
 
2e179f3
dcae38a
9defefd
dcae38a
 
 
 
 
aa0d4c6
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
import { useCallback, useEffect, useRef } from 'react'
import { AsyncSelect } from '@/components/ui/AsyncSelect'
import { useSettingsStore } from '@/stores/settings'
import { useGraphStore } from '@/stores/graph'
import { labelListLimit } from '@/lib/constants'
import MiniSearch from 'minisearch'
import { useTranslation } from 'react-i18next'

const GraphLabels = () => {
  const { t } = useTranslation()
  const label = useSettingsStore.use.queryLabel()
  const allDatabaseLabels = useGraphStore.use.allDatabaseLabels()
  const labelsLoadedRef = useRef(false)

  // Track if a fetch is in progress to prevent multiple simultaneous fetches
  const fetchInProgressRef = useRef(false)

  // Fetch labels once on component mount, using global flag to prevent duplicates
  useEffect(() => {
    // Check if we've already attempted to fetch labels in this session
    const labelsFetchAttempted = useGraphStore.getState().labelsFetchAttempted

    // Only fetch if we haven't attempted in this session and no fetch is in progress
    if (!labelsFetchAttempted && !fetchInProgressRef.current) {
      fetchInProgressRef.current = true
      // Set global flag to indicate we've attempted to fetch in this session
      useGraphStore.getState().setLabelsFetchAttempted(true)

      console.log('Fetching graph labels (once per session)...')

      useGraphStore.getState().fetchAllDatabaseLabels()
        .then(() => {
          labelsLoadedRef.current = true
          fetchInProgressRef.current = false
        })
        .catch((error) => {
          console.error('Failed to fetch labels:', error)
          fetchInProgressRef.current = false
          // Reset global flag to allow retry
          useGraphStore.getState().setLabelsFetchAttempted(false)
        })
    }
  }, []) // Empty dependency array ensures this only runs once on mount

  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
        result = searchEngine.search(query).map((r: { id: number }) => labels[r.id])
      }

      return result.length <= labelListLimit
        ? result
        : [...result.slice(0, labelListLimit), '...']
    },
    [getSearchEngine]
  )

  return (
    <AsyncSelect<string>
      className="ml-2"
      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 = '*'
        }

        // Reset the fetch attempted flag to force a new data fetch
        useGraphStore.getState().setGraphDataFetchAttempted(false)

        // Clear current graph data to ensure complete reload when label changes
        if (newLabel !== currentLabel) {
          const graphStore = useGraphStore.getState();
          graphStore.clearSelection();

          // Reset the graph state but preserve the instance
          if (graphStore.sigmaGraph) {
            const nodes = Array.from(graphStore.sigmaGraph.nodes());
            nodes.forEach(node => graphStore.sigmaGraph?.dropNode(node));
          }
        }

        if (newLabel === currentLabel && newLabel !== '*') {
          // reselect the same itme means qery all
          useSettingsStore.getState().setQueryLabel('*')
        } else {
          useSettingsStore.getState().setQueryLabel(newLabel)
        }
      }}
      clearable={false}  // Prevent clearing value on reselect
    />
  )
}

export default GraphLabels