@smart-cloud/ai-kit-ui 1.3.7 → 1.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smart-cloud/ai-kit-ui",
3
- "version": "1.3.7",
3
+ "version": "1.3.9",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -140,6 +140,7 @@ const DocSearchBase: FC<Props> = (props) => {
140
140
  useAiRun<SearchResult>();
141
141
 
142
142
  const autoRunOnceRef = useRef(false);
143
+ const prevSelectedCategoriesRef = useRef<string[]>([]);
143
144
 
144
145
  const sessionId = result?.sessionId;
145
146
  const citationDocs = result?.citations?.docs ?? [];
@@ -188,6 +189,31 @@ const DocSearchBase: FC<Props> = (props) => {
188
189
  return Boolean((text && text.trim().length > 0) || audioBlob);
189
190
  }, [inputText, busy, audioBlob, featureOpen]);
190
191
 
192
+ const hasValidFilterOptions = useMemo(() => {
193
+ if (!metadataOptions) return false;
194
+ const hasCategories =
195
+ Object.keys(metadataOptions.allowedCategories).length > 0;
196
+ const hasTags = metadataOptions.allowedTags.length > 0;
197
+ return hasCategories || hasTags;
198
+ }, [metadataOptions]);
199
+
200
+ const subcategories = useMemo(() => {
201
+ if (selectedCategories.length === 0 || !metadataOptions) {
202
+ return [];
203
+ }
204
+ return selectedCategories
205
+ .flatMap(
206
+ (cat) =>
207
+ metadataOptions.allowedCategories[
208
+ cat
209
+ ] || [],
210
+ )
211
+ .filter(
212
+ (subcat, index, self) =>
213
+ self.indexOf(subcat) === index,
214
+ )
215
+ }, [selectedCategories, metadataOptions]);
216
+
191
217
  const startRecording = useCallback(async () => {
192
218
  try {
193
219
  // Clear query input when starting audio recording
@@ -390,7 +416,7 @@ const DocSearchBase: FC<Props> = (props) => {
390
416
  "Reusing cached audio (no re-upload needed within",
391
417
  Math.round(
392
418
  (AUDIO_CACHE_TTL - (now - audioCacheRef.current!.uploadTimestamp)) /
393
- 1000,
419
+ 1000,
394
420
  ),
395
421
  "seconds)",
396
422
  );
@@ -405,14 +431,15 @@ const DocSearchBase: FC<Props> = (props) => {
405
431
  ...(audioBlob && { audio: audioBlob }), // Pass Blob directly
406
432
  topK,
407
433
  // Include user-selected filters if enabled
408
- ...(enableUserFilters &&
409
- selectedCategories.length > 0 && {
410
- userSelectedCategories: selectedCategories,
411
- }),
434
+ // Always send userSelectedCategories array when enableUserFilters is true (even if empty)
435
+ // to prevent backend from applying its own kb-filter
436
+ ...(enableUserFilters && {
437
+ userSelectedCategories: selectedCategories,
438
+ }),
412
439
  ...(enableUserFilters &&
413
440
  selectedSubcategories.length > 0 && {
414
- userSelectedSubcategories: selectedSubcategories,
415
- }),
441
+ userSelectedSubcategories: selectedSubcategories,
442
+ }),
416
443
  ...(enableUserFilters &&
417
444
  selectedTags.length > 0 && { userSelectedTags: selectedTags }),
418
445
  },
@@ -458,6 +485,24 @@ const DocSearchBase: FC<Props> = (props) => {
458
485
  }
459
486
  }, [canSearch]);
460
487
 
488
+ // Reset session when main categories change
489
+ useEffect(() => {
490
+ const prev = prevSelectedCategoriesRef.current;
491
+ const current = selectedCategories;
492
+
493
+ // Check if categories changed (different length or different items)
494
+ const categoriesChanged =
495
+ prev.length !== current.length ||
496
+ !current.every((cat) => prev.includes(cat));
497
+
498
+ if (categoriesChanged && prev.length > 0) {
499
+ // Reset session only if we had categories before (not on initial mount)
500
+ reset();
501
+ }
502
+
503
+ prevSelectedCategoriesRef.current = [...current];
504
+ }, [selectedCategories, reset]);
505
+
461
506
  const grouped = useMemo(() => groupChunksByDoc(result), [result]);
462
507
 
463
508
  const docNumberMap = useMemo(() => {
@@ -717,9 +762,9 @@ const DocSearchBase: FC<Props> = (props) => {
717
762
  style={
718
763
  recording
719
764
  ? {
720
- transform: `scale(${1 + audioLevel / 300})`,
721
- transition: "transform 0.1s ease-out",
722
- }
765
+ transform: `scale(${1 + audioLevel / 300})`,
766
+ transition: "transform 0.1s ease-out",
767
+ }
723
768
  : undefined
724
769
  }
725
770
  >
@@ -760,142 +805,141 @@ const DocSearchBase: FC<Props> = (props) => {
760
805
  ) : null}
761
806
 
762
807
  {/* User filter collapse */}
763
- {enableUserFilters && metadataOptions && (
764
- <Stack gap="xs">
765
- <Button
766
- variant="subtle"
767
- size="xs"
768
- onClick={() => setFiltersOpen(!filtersOpen)}
769
- leftSection={
770
- filtersOpen ? (
771
- <IconChevronDown size={14} />
772
- ) : (
773
- <IconChevronRight size={14} />
774
- )
775
- }
776
- style={{ alignSelf: "flex-start" }}
777
- >
778
- {I18n.get("Filters")}
779
- </Button>
808
+ {enableUserFilters &&
809
+ metadataOptions &&
810
+ hasValidFilterOptions && (
811
+ <Stack gap="xs">
812
+ <Button
813
+ variant="subtle"
814
+ size="xs"
815
+ onClick={() => setFiltersOpen(!filtersOpen)}
816
+ leftSection={
817
+ filtersOpen ? (
818
+ <IconChevronDown size={14} />
819
+ ) : (
820
+ <IconChevronRight size={14} />
821
+ )
822
+ }
823
+ style={{ alignSelf: "flex-start" }}
824
+ >
825
+ {I18n.get("Filters")}
826
+ </Button>
780
827
 
781
- <Collapse in={filtersOpen}>
782
- <Stack gap="md">
783
- {/* Main categories as checkboxes */}
784
- {Object.keys(metadataOptions.allowedCategories)
785
- .length > 0 && (
786
- <div>
787
- <Text size="sm" fw={500} mb="xs">
788
- {I18n.get("Categories")}
789
- </Text>
790
- <Group gap="md">
791
- {Object.keys(
792
- metadataOptions.allowedCategories,
793
- ).map((category) => (
794
- <Checkbox
795
- key={category}
796
- label={category}
797
- checked={selectedCategories.includes(
798
- category,
799
- )}
800
- onChange={(e) => {
801
- if (e.currentTarget.checked) {
802
- setSelectedCategories([
803
- ...selectedCategories,
828
+ <Collapse in={filtersOpen}>
829
+ <Stack gap="md">
830
+ {/* Main categories as checkboxes */}
831
+ {Object.keys(metadataOptions.allowedCategories)
832
+ .length > 0 && (
833
+ <div>
834
+ <Text size="sm" fw={500} mb="xs">
835
+ {I18n.get("Categories")}
836
+ </Text>
837
+ <Group gap="md">
838
+ {Object.keys(
839
+ metadataOptions.allowedCategories,
840
+ ).map((category) => (
841
+ <Checkbox
842
+ key={category}
843
+ label={I18n.get(category)}
844
+ checked={selectedCategories.includes(
804
845
  category,
805
- ]);
806
- } else {
807
- setSelectedCategories(
808
- selectedCategories.filter(
809
- (c) => c !== category,
810
- ),
811
- );
812
- // Remove subcategories of unchecked category
813
- const subcatsToRemove =
814
- metadataOptions.allowedCategories[
815
- category
816
- ] || [];
817
- setSelectedSubcategories(
818
- selectedSubcategories.filter(
819
- (sc) =>
820
- !subcatsToRemove.includes(sc),
821
- ),
822
- );
823
- }
824
- }}
825
- disabled={busy || loadingMetadata}
826
- />
827
- ))}
828
- </Group>
829
- </div>
830
- )}
831
-
832
- {/* Subcategories for selected categories */}
833
- {selectedCategories.length > 0 && (
834
- <div>
835
- <Text size="sm" fw={500} mb="xs">
836
- {I18n.get("Subcategories")}
837
- </Text>
838
- <Group gap="md">
839
- {selectedCategories
840
- .flatMap(
841
- (cat) =>
842
- metadataOptions.allowedCategories[
843
- cat
844
- ] || [],
845
- )
846
- .filter(
847
- (subcat, index, self) =>
848
- self.indexOf(subcat) === index,
849
- )
850
- .map((subcat) => (
851
- <Checkbox
852
- key={subcat}
853
- label={subcat}
854
- checked={selectedSubcategories.includes(
855
- subcat,
856
- )}
857
- onChange={(e) => {
858
- if (e.currentTarget.checked) {
859
- setSelectedSubcategories([
860
- ...selectedSubcategories,
861
- subcat,
862
- ]);
863
- } else {
864
- setSelectedSubcategories(
865
- selectedSubcategories.filter(
866
- (sc) => sc !== subcat,
867
- ),
868
- );
869
- }
870
- }}
871
- disabled={busy || loadingMetadata}
872
- />
873
- ))}
874
- </Group>
875
- </div>
876
- )}
877
-
878
- {/* Tags input */}
879
- {metadataOptions.allowedTags.length > 0 && (
880
- <MultiSelect
881
- label={I18n.get("Tags")}
882
- placeholder={I18n.get("Select or type tags...")}
883
- data={metadataOptions.allowedTags}
884
- value={selectedTags}
885
- onChange={setSelectedTags}
886
- searchValue={tagSearchValue}
887
- onSearchChange={setTagSearchValue}
888
- disabled={busy || loadingMetadata}
889
- searchable
890
- clearable
891
- maxDropdownHeight={200}
892
- limit={20}
893
- />
894
- )}
895
- </Stack>
896
- </Collapse>
897
- </Stack>
898
- )}
846
+ )}
847
+ onChange={(e) => {
848
+ if (e.currentTarget.checked) {
849
+ setSelectedCategories([
850
+ ...selectedCategories,
851
+ category,
852
+ ]);
853
+ } else {
854
+ setSelectedCategories(
855
+ selectedCategories.filter(
856
+ (c) => c !== category,
857
+ ),
858
+ );
859
+ // Remove subcategories of unchecked category
860
+ const subcatsToRemove =
861
+ metadataOptions.allowedCategories[
862
+ category
863
+ ] || [];
864
+ setSelectedSubcategories(
865
+ selectedSubcategories.filter(
866
+ (sc) =>
867
+ !subcatsToRemove.includes(sc),
868
+ ),
869
+ );
870
+ }
871
+ }}
872
+ disabled={busy || loadingMetadata}
873
+ />
874
+ ))}
875
+ </Group>
876
+ </div>
877
+ )}
878
+
879
+ {/* Subcategories for selected categories */}
880
+ {subcategories.length > 0 && (
881
+ <div>
882
+ <Text size="sm" fw={500} mb="xs">
883
+ {I18n.get("Subcategories")}
884
+ </Text>
885
+ <Group gap="md">
886
+ {subcategories
887
+ .map((subcat) => (
888
+ <Checkbox
889
+ key={subcat}
890
+ label={I18n.get(subcat)}
891
+ checked={selectedSubcategories.includes(
892
+ subcat,
893
+ )}
894
+ onChange={(e) => {
895
+ if (e.currentTarget.checked) {
896
+ setSelectedSubcategories([
897
+ ...selectedSubcategories,
898
+ subcat,
899
+ ]);
900
+ } else {
901
+ setSelectedSubcategories(
902
+ selectedSubcategories.filter(
903
+ (sc) => sc !== subcat,
904
+ ),
905
+ );
906
+ }
907
+ }}
908
+ disabled={busy || loadingMetadata}
909
+ />
910
+ ))}
911
+ </Group>
912
+ </div>
913
+ )}
914
+
915
+ {/* Tags input */}
916
+ {metadataOptions.allowedTags.length > 0 && (
917
+ <MultiSelect
918
+ label={I18n.get("Tags")}
919
+ placeholder={I18n.get(
920
+ "Select or type tags...",
921
+ )}
922
+ data={metadataOptions.allowedTags.map(
923
+ (tag) => ({
924
+ value: tag,
925
+ label: I18n.get(tag),
926
+ }),
927
+ )}
928
+ value={selectedTags}
929
+ onChange={setSelectedTags}
930
+ searchValue={tagSearchValue}
931
+ onSearchChange={setTagSearchValue}
932
+ disabled={busy || loadingMetadata}
933
+ searchable
934
+ clearable
935
+ maxDropdownHeight={200}
936
+ limit={20}
937
+ />
938
+ )}
939
+ </Stack>
940
+ </Collapse>
941
+ </Stack>
942
+ )}
899
943
 
900
944
  {
901
945
  /* Audio level indicator when recording */ USE_AUDIO && (
@@ -982,8 +1026,8 @@ const DocSearchBase: FC<Props> = (props) => {
982
1026
  ) : null}
983
1027
 
984
1028
  {showSources &&
985
- (result?.citations?.docs?.length ||
986
- result?.citations?.chunks?.length) ? (
1029
+ (result?.citations?.docs?.length ||
1030
+ result?.citations?.chunks?.length) ? (
987
1031
  <>
988
1032
  <Divider />
989
1033
  <Stack gap="sm" data-doc-search-sources>