@e-llm-studio/citation 0.0.214 → 0.0.216

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.
Files changed (55) hide show
  1. package/README.md +254 -38
  2. package/dist/cjs/features/CognitiveCompare/CognitiveCompare.js +1 -1
  3. package/dist/cjs/features/GptWebCitation/GptWebCitation.js +1 -1
  4. package/dist/cjs/features/GptWebCitation/GptWebCitation.module.css.js +1 -1
  5. package/dist/cjs/features/GptWebCitation/GptWebCitationImageCitation.js +1 -1
  6. package/dist/cjs/features/GptWebCitation/GptWebCitationImageFullScreenView.js +1 -1
  7. package/dist/cjs/features/GptWebCitation/GptWebCitationSkeleton.js +1 -1
  8. package/dist/cjs/features/GptWebCitation/utils/citationData.utils.js +1 -1
  9. package/dist/cjs/features/GptWebCitation/utils/fixedHeight.utils.js +2 -0
  10. package/dist/cjs/features/GptWebCitation/utils/fixedHeight.utils.js.map +1 -0
  11. package/dist/cjs/features/GptWebCitation/utils/styleOverrides.utils.js +2 -0
  12. package/dist/cjs/features/GptWebCitation/utils/styleOverrides.utils.js.map +1 -0
  13. package/dist/cjs/index.js +1 -1
  14. package/dist/features/CognitiveCompare/CognitiveCompare.js +1 -1
  15. package/dist/features/GptWebCitation/GptWebCitation.js +1 -1
  16. package/dist/features/GptWebCitation/GptWebCitation.module.css.js +1 -1
  17. package/dist/features/GptWebCitation/GptWebCitationImageCitation.js +1 -1
  18. package/dist/features/GptWebCitation/GptWebCitationImageFullScreenView.js +1 -1
  19. package/dist/features/GptWebCitation/GptWebCitationSkeleton.js +1 -1
  20. package/dist/features/GptWebCitation/utils/citationData.utils.js +1 -1
  21. package/dist/features/GptWebCitation/utils/fixedHeight.utils.js +2 -0
  22. package/dist/features/GptWebCitation/utils/fixedHeight.utils.js.map +1 -0
  23. package/dist/features/GptWebCitation/utils/styleOverrides.utils.js +2 -0
  24. package/dist/features/GptWebCitation/utils/styleOverrides.utils.js.map +1 -0
  25. package/dist/index.js +1 -1
  26. package/dist/types/src/CognitiveCompareTest.d.ts.map +1 -1
  27. package/dist/types/src/features/CognitiveCompare/CognitiveCompare.d.ts.map +1 -1
  28. package/dist/types/src/features/CognitiveCompare/CognitiveCompareTypes.d.ts +1 -0
  29. package/dist/types/src/features/CognitiveCompare/CognitiveCompareTypes.d.ts.map +1 -1
  30. package/dist/types/src/features/GptWebCitation/GptWebCitation.d.ts +10 -0
  31. package/dist/types/src/features/GptWebCitation/GptWebCitation.d.ts.map +1 -1
  32. package/dist/types/src/features/GptWebCitation/GptWebCitationImageCitation.d.ts +9 -1
  33. package/dist/types/src/features/GptWebCitation/GptWebCitationImageCitation.d.ts.map +1 -1
  34. package/dist/types/src/features/GptWebCitation/GptWebCitationImageFullScreenView.d.ts +3 -1
  35. package/dist/types/src/features/GptWebCitation/GptWebCitationImageFullScreenView.d.ts.map +1 -1
  36. package/dist/types/src/features/GptWebCitation/GptWebCitationSkeleton.d.ts +12 -1
  37. package/dist/types/src/features/GptWebCitation/GptWebCitationSkeleton.d.ts.map +1 -1
  38. package/dist/types/src/features/GptWebCitation/GptWebCitationTest.d.ts +1 -1
  39. package/dist/types/src/features/GptWebCitation/GptWebCitationTest.d.ts.map +1 -1
  40. package/dist/types/src/features/GptWebCitation/WebCitationSampleResponse.d.ts.map +1 -1
  41. package/dist/types/src/features/GptWebCitation/types.d.ts +289 -18
  42. package/dist/types/src/features/GptWebCitation/types.d.ts.map +1 -1
  43. package/dist/types/src/features/GptWebCitation/utils/citationData.utils.d.ts +2 -0
  44. package/dist/types/src/features/GptWebCitation/utils/citationData.utils.d.ts.map +1 -1
  45. package/dist/types/src/features/GptWebCitation/utils/fixedHeight.utils.d.ts +10 -0
  46. package/dist/types/src/features/GptWebCitation/utils/fixedHeight.utils.d.ts.map +1 -0
  47. package/dist/types/src/features/GptWebCitation/utils/styleOverrides.utils.d.ts +6 -0
  48. package/dist/types/src/features/GptWebCitation/utils/styleOverrides.utils.d.ts.map +1 -0
  49. package/dist/types/src/index.d.ts +2 -2
  50. package/dist/types/src/index.d.ts.map +1 -1
  51. package/package.json +1 -1
  52. package/dist/cjs/features/GptWebCitation/GptWebCitationMarkdown.js +0 -2
  53. package/dist/cjs/features/GptWebCitation/GptWebCitationMarkdown.js.map +0 -1
  54. package/dist/features/GptWebCitation/GptWebCitationMarkdown.js +0 -2
  55. package/dist/features/GptWebCitation/GptWebCitationMarkdown.js.map +0 -1
package/README.md CHANGED
@@ -681,31 +681,51 @@ const reasoningData = {
681
681
 
682
682
  `GptWebCitation` is a composite React component that wraps the GPT reasoning citation (`CognitiveInternalgptCoreComponent`) and adds an optional **Web Citation** view. Users can toggle between GPT and web-based citations without modifying the underlying GPT citation code.
683
683
 
684
- In **GPT view**, the component renders the standard GPT citation panel with a **View Web Citation** button on the left and the document source link on the right (inside the GPT citation footer). In **Web Citation view**, it shows a web snapshot (image or markdown content) with highlight navigation, relevance score, and a **View GPT Citation** toggle to switch back.
684
+ In **GPT view**, the component renders the standard GPT citation panel with a **View Web Citation** button on the left and the document source link on the right (inside the GPT citation footer). In **Web Citation view**, it shows a screenshot snapshot of the source page (when available), a relevance score badge, visit links, and a **View GPT Citation** toggle to switch back.
685
+
686
+ The component is **presentational** — it does not fetch web citations. The parent owns `webCitationData`, `webCitationStatus`, and view toggling.
685
687
 
686
688
  ### Features
687
689
 
688
690
  - **Dual citation views** — Toggle between GPT reasoning and web-based citation
689
691
  - **Non-invasive GPT integration** — Wraps `CognitiveInternalgptCoreComponent` via props; no changes to GPT citation code required
690
- - **Image & markdown web citations** — Supports screenshot snapshots (`imageMetadata.signed_url`) or HTML/markdown content
691
- - **Highlight pagination** — Navigate between highlighted anchors in web citation markdown
692
+ - **Screenshot web citations** — Renders `imageMetadata.signed_url` when the API returns a snapshot
693
+ - **Status-driven skeleton UI** — `"pending"` loading skeleton and `"error"` fallback with **Visit Link**
694
+ - **`defaultCitationUrl`** — Immediate visit link while fetch is in progress (before `highlightedUrl` arrives)
695
+ - **Visit link priority** — `highlightedUrl` from API response wins over `defaultCitationUrl` in pending/error states
692
696
  - **Relevance score badge** — Auto-extracted from web citation content (e.g. "decision strength is 85%")
693
- - **Loading skeleton** — Built-in loading state while web citation is being generated
694
697
  - **Expand screenshot button** — Optional full-screen image view (`showExpandImageButton`)
698
+ - **Custom icons** — Override expand and external-link icons via `iconsConfig`
699
+ - **Fixed height mode** — Optional `isFixedHeight` keeps GPT and web panels the same size with contained screenshots
695
700
  - **Style overrides** — Customize layout via the `styles` prop without editing component internals
696
701
 
697
702
  ### Installation / Import
698
703
 
699
704
  ```tsx
700
705
  import GptWebCitation from '@e-llm-studio/citation/GptWebCitation';
706
+
707
+ // Optional utilities and types
708
+ import {
709
+ mapCitationDataToDisplay,
710
+ getWebCitationImageUrl,
711
+ resolveSkeletonVisitUrl,
712
+ type GptWebCitationStatus,
713
+ type IWebCitationApiResponse,
714
+ type IGptWebCitationStyleOverrides,
715
+ type IGptWebCitationImageStyles,
716
+ type IGptWebCitationSkeletonStyles,
717
+ type IGptWebCitationVisitLinkStyles,
718
+ type IGptWebCitationFullScreenStyles,
719
+ } from '@e-llm-studio/citation';
701
720
  ```
702
721
 
703
722
  ### Usage 1: Basic GPT + Web Citation Toggle
704
723
 
705
724
  ```tsx
706
725
  import { useState } from 'react';
707
- import { ChevronDown, ChevronUp, Maximize2, X } from 'lucide-react';
726
+ import { ChevronDown, ChevronUp, ExternalLink, Maximize2, X } from 'lucide-react';
708
727
  import GptWebCitation from '@e-llm-studio/citation/GptWebCitation';
728
+ import type { GptWebCitationStatus } from '@e-llm-studio/citation';
709
729
 
710
730
  const gptCitation = {
711
731
  item: {
@@ -727,31 +747,42 @@ const gptCitation = {
727
747
  previewCallback: () => window.open('/preview/document'),
728
748
  };
729
749
 
750
+ const webCitationIcons = {
751
+ MaximizeIcon: Maximize2,
752
+ ExternalLinkIcon: ExternalLink,
753
+ };
754
+
730
755
  function CitationPanel() {
731
756
  const [showWebCitation, setShowWebCitation] = useState(false);
732
757
  const [webCitationData, setWebCitationData] = useState(undefined);
733
- const [isLoading, setIsLoading] = useState(false);
758
+ const [webCitationStatus, setWebCitationStatus] =
759
+ useState<GptWebCitationStatus>('pending');
734
760
 
735
761
  const handleGenerateWebCitation = async () => {
736
762
  setShowWebCitation(true);
737
- setIsLoading(true);
738
-
739
- const response = await fetch('/api/web-citation/generate');
740
- const data = await response.json();
741
-
742
- setWebCitationData(data);
743
- setIsLoading(false);
763
+ setWebCitationStatus('pending');
764
+
765
+ try {
766
+ const response = await fetch('/api/web-citation/generate');
767
+ const data = await response.json();
768
+ setWebCitationData(data);
769
+ setWebCitationStatus('success');
770
+ } catch {
771
+ setWebCitationStatus('error');
772
+ }
744
773
  };
745
774
 
746
775
  return (
747
776
  <GptWebCitation
748
777
  gptCitation={gptCitation}
778
+ defaultCitationUrl="https://example.com/article"
749
779
  isWebCitation={true}
750
780
  webCitationData={webCitationData}
781
+ webCitationStatus={webCitationStatus}
751
782
  showWebCitation={showWebCitation}
752
- isWebCitationLoading={isLoading}
753
783
  onGenerateWebCitation={handleGenerateWebCitation}
754
784
  onToggleCitationView={() => setShowWebCitation((prev) => !prev)}
785
+ iconsConfig={webCitationIcons}
755
786
  />
756
787
  );
757
788
  }
@@ -768,6 +799,8 @@ const webCitationData = {
768
799
  'The decision strength is 85% because:<ol><li>The exact <a href="https://example.com/article">valuation of SpaceX</a> fluctuates between sources.</li></ol>',
769
800
  citationType: 'web',
770
801
  citationUrl: 'https://example.com/article',
802
+ highlightedUrl: 'https://example.com/article#highlight',
803
+ message: 'Citation processed successfully',
771
804
  imageMetadata: {
772
805
  signed_url: 'https://storage.example.com/image_citations/screenshot.png',
773
806
  blob_path: 'image_citations/screenshot.png',
@@ -777,21 +810,33 @@ const webCitationData = {
777
810
 
778
811
  <GptWebCitation
779
812
  gptCitation={gptCitation}
813
+ defaultCitationUrl="https://example.com/article"
780
814
  isWebCitation={true}
781
815
  webCitationData={webCitationData}
816
+ webCitationStatus="success"
782
817
  showWebCitation={true}
783
818
  />
784
819
  ```
785
820
 
786
- When `imageMetadata.signed_url` is present, the component renders a screenshot snapshot. Otherwise it falls back to rendering the `content` field as markdown/HTML.
821
+ When `imageMetadata.signed_url` is present and `webCitationStatus` is `"success"`, the component renders a screenshot snapshot. If the image is unavailable or the fetch fails, an error skeleton is shown with a **Visit Link** (using `highlightedUrl` or `defaultCitationUrl`).
822
+
823
+ #### Visit link resolution
824
+
825
+ | UI state | Visit link source |
826
+ | --- | --- |
827
+ | Pending / error skeleton | `highlightedUrl` from `webCitationData` → else `defaultCitationUrl` prop |
828
+ | Screenshot footer (success) | `citationUrl` from `webCitationData` |
829
+
830
+ Use `resolveSkeletonVisitUrl(highlightedUrl, defaultCitationUrl)` from `@e-llm-studio/citation` if you need the same logic outside the component.
787
831
 
788
832
  ### Usage 3: GPT-Only Mode (No Web Citation)
789
833
 
790
- Set `isWebCitation={false}` to render only the GPT citation with no toggle button:
834
+ Set `isWebCitation={false}` to render only the GPT citation with no toggle button. `defaultCitationUrl` is still required by the prop contract but is unused in GPT-only mode:
791
835
 
792
836
  ```tsx
793
837
  <GptWebCitation
794
838
  gptCitation={gptCitation}
839
+ defaultCitationUrl="https://example.com/article"
795
840
  isWebCitation={false}
796
841
  />
797
842
  ```
@@ -803,8 +848,10 @@ Hide the web citation screenshot expand button:
803
848
  ```tsx
804
849
  <GptWebCitation
805
850
  gptCitation={gptCitation}
851
+ defaultCitationUrl="https://example.com/article"
806
852
  isWebCitation={true}
807
853
  webCitationData={webCitationData}
854
+ webCitationStatus="success"
808
855
  showWebCitation={true}
809
856
  showExpandImageButton={false}
810
857
  />
@@ -815,6 +862,7 @@ Hide the GPT citation header maximize/expand icon:
815
862
  ```tsx
816
863
  <GptWebCitation
817
864
  gptCitation={gptCitation}
865
+ defaultCitationUrl="https://example.com/article"
818
866
  isWebCitation={true}
819
867
  showGptMaximizeButton={false}
820
868
  />
@@ -824,7 +872,7 @@ You can also use `gptCitation.disableMaximize: true` directly on the GPT config.
824
872
 
825
873
  ### Usage 5: Custom Styling via Props (matched heights)
826
874
 
827
- Layout, borders, and heights for **both GPT and web views** can be controlled from the parent without modifying component internals:
875
+ Layout, borders, and heights for **both GPT and web views** can be controlled from the parent without modifying component internals. Prefer **`isFixedHeight`** (Usage 6) when you only need equal panel size + contained screenshots; use `styles` when you need granular control over skeleton, image, visit-link, or full-screen elements:
828
876
 
829
877
  ```tsx
830
878
  const sharedPanelHeight = '520px';
@@ -835,9 +883,15 @@ const sharedContentHeight = '384px';
835
883
  ...gptCitation,
836
884
  bodyHeight: sharedContentHeight,
837
885
  }}
886
+ defaultCitationUrl="https://example.com/article"
838
887
  isWebCitation={true}
839
888
  webCitationData={webCitationData}
889
+ webCitationStatus="success"
840
890
  showGptMaximizeButton={true}
891
+ iconsConfig={{
892
+ MaximizeIcon: Maximize2,
893
+ ExternalLinkIcon: ExternalLink,
894
+ }}
841
895
  styles={{
842
896
  container: { maxWidth: 900 },
843
897
  gptCitation: {
@@ -878,33 +932,143 @@ const sharedContentHeight = '384px';
878
932
  | `gptCitation.footerAction` | GPT view | View Web Citation button row in footer |
879
933
  | `gptCitation.toggleButton` | GPT view | View Web Citation button styles |
880
934
  | `webCitation.panel` | Web view | Outer web citation card |
881
- | `webCitation.header` | Web view | Source label + pagination + score badge row |
882
- | `webCitation.content` | Web view | Main content area (markdown, image, skeleton) |
935
+ | `webCitation.header` | Web view | Source label + relevance score badge row |
936
+ | `webCitation.content` | Web view | Screenshot or loading/error skeleton |
883
937
  | `webCitation.footer` | Web view | View GPT Citation + Learned From row |
884
938
  | `webCitation.toggleButton` | Web view | View GPT Citation button styles |
885
939
  | `webCitation.learnedFrom` | Web view | "Learned From …" text styles |
940
+ | `webCitation.visitLink.row` | Image + skeleton | Shared visit link row (merged with scoped overrides below) |
941
+ | `webCitation.visitLink.hint` | Image + skeleton | Shared visit link hint text |
942
+ | `webCitation.visitLink.link` | Image + skeleton | Shared visit link anchor styles |
943
+ | `webCitation.image.wrapper` | Screenshot view | Outer image citation wrapper |
944
+ | `webCitation.image.center` | Screenshot view | Center column (viewport or inline skeleton) |
945
+ | `webCitation.image.viewport` | Screenshot view | Viewport around scroll container |
946
+ | `webCitation.image.scrollContainer` | Screenshot view | Scrollable screenshot container |
947
+ | `webCitation.image.image` | Screenshot view | Screenshot `<img>` element |
948
+ | `webCitation.image.expandButton` | Screenshot view | Full-screen expand button |
949
+ | `webCitation.image.visitLink.*` | Screenshot footer | Image-specific visit link overrides (merged on top of `webCitation.visitLink`) |
950
+ | `webCitation.image.fullScreen.overlay` | Full-screen viewer | Portal overlay backdrop |
951
+ | `webCitation.image.fullScreen.content` | Full-screen viewer | Modal content card |
952
+ | `webCitation.image.fullScreen.closeButton` | Full-screen viewer | Close button |
953
+ | `webCitation.image.fullScreen.imageWrapper` | Full-screen viewer | Scrollable image wrapper |
954
+ | `webCitation.image.fullScreen.image` | Full-screen viewer | Full-screen `<img>` |
955
+ | `webCitation.skeleton.container` | Pending/error | Outer skeleton column |
956
+ | `webCitation.skeleton.frame` | Pending/error | Gray skeleton frame |
957
+ | `webCitation.skeleton.shimmer` | Pending | Shimmer animation overlay |
958
+ | `webCitation.skeleton.pending.content` | Pending | Centered pending content block |
959
+ | `webCitation.skeleton.pending.text` | Pending | Loading message text |
960
+ | `webCitation.skeleton.pending.actionRow` | Pending | Visit link row |
961
+ | `webCitation.skeleton.pending.actionHint` | Pending | "to view details" hint |
962
+ | `webCitation.skeleton.error.content` | Error | Error content block |
963
+ | `webCitation.skeleton.error.title` | Error | "No preview available" title |
964
+ | `webCitation.skeleton.error.message` | Error | Restriction message |
965
+ | `webCitation.skeleton.error.actionGroup` | Error | Action row wrapper |
966
+ | `webCitation.skeleton.error.detailsToggle` | Error | Error details expand button |
967
+ | `webCitation.skeleton.error.detailsMessage` | Error | Expanded error details text |
968
+ | `webCitation.skeleton.error.visitLinkGlow` | Error | Glowing visit link (merged on top of visit link styles) |
969
+ | `webCitation.skeleton.visitLink.*` | Pending/error | Skeleton-specific visit link overrides (merged on top of `webCitation.visitLink`) |
886
970
 
887
971
  Legacy flat keys (`gptCitationWrapper`, `footerAction`, `webCitationButton`) still work for backward compatibility.
888
972
 
973
+ #### Example: customize screenshot and pending skeleton
974
+
975
+ ```tsx
976
+ <GptWebCitation
977
+ gptCitation={gptCitation}
978
+ defaultCitationUrl="https://example.com/article"
979
+ isWebCitation
980
+ webCitationData={data}
981
+ webCitationStatus={status}
982
+ styles={{
983
+ webCitation: {
984
+ visitLink: {
985
+ link: { backgroundColor: 'rgba(37, 99, 235, 0.08)', borderRadius: 8 },
986
+ },
987
+ image: {
988
+ scrollContainer: { borderRadius: 16, borderColor: '#d1d5db' },
989
+ image: { objectFit: 'contain', maxHeight: 360 },
990
+ expandButton: { backgroundColor: '#111827' },
991
+ },
992
+ skeleton: {
993
+ frame: { minHeight: 320, backgroundColor: '#f8fafc' },
994
+ pending: {
995
+ text: { fontSize: 14, color: '#374151' },
996
+ },
997
+ error: {
998
+ title: { color: '#dc2626' },
999
+ visitLinkGlow: { borderColor: '#dc2626' },
1000
+ },
1001
+ },
1002
+ },
1003
+ }}
1004
+ />
1005
+ ```
1006
+
1007
+ ### Usage 6: Fixed height mode (matched GPT + web panels)
1008
+
1009
+ Set `isFixedHeight={true}` when you want GPT and web citation views to share the same panel dimensions. Screenshots use `object-fit: contain` so tall pages stay inside the box without layout shift when toggling views.
1010
+
1011
+ ```tsx
1012
+ <GptWebCitation
1013
+ gptCitation={{
1014
+ ...gptCitation,
1015
+ bodyHeight: '384px', // optional — customizes inner content height
1016
+ }}
1017
+ defaultCitationUrl="https://example.com/article"
1018
+ isWebCitation
1019
+ isFixedHeight
1020
+ webCitationData={webCitationData}
1021
+ webCitationStatus="success"
1022
+ showWebCitation={showWebCitation}
1023
+ onToggleCitationView={() => setShowWeb((v) => !v)}
1024
+ />
1025
+ ```
1026
+
1027
+ | Scenario | Panel height |
1028
+ | --- | --- |
1029
+ | `isFixedHeight` + `isWebCitation` (toggle footer present) | `520px` default, or `calc(bodyHeight + 3.25rem)` when `gptCitation.bodyHeight` is set |
1030
+ | `isFixedHeight` + `isWebCitation={false}` (GPT only) | `520px` default, or `gptCitation.bodyHeight` when set |
1031
+
1032
+ `isFixedHeight` applies flex layout and contained images automatically. For manual per-element styling without fixed dimensions, use `styles` (Usage 5) instead.
1033
+
889
1034
  ### Props
890
1035
 
891
1036
  | Prop | Type | Default | Description |
892
1037
  |------|------|---------|-------------|
893
- | `gptCitation` | `IGptCitationConfig` | **required** | GPT citation config passed to `CognitiveInternalgptCoreComponent` (`item`, `headerTitle`, `iconsConfig`, `DocumentTitle`, `previewCallback`, etc.) |
894
- | `isWebCitation` | `boolean` | `false` | When `true`, enables web citation toggle and web view. When `false`, only GPT citation is shown. |
895
- | `webCitationData` | `IWebCitationApiResponse` | | Web citation API response (content, image snapshot, URL) |
896
- | `showWebCitation` | `boolean` | — | Controls which view is active. Omit for uncontrolled internal state. |
897
- | `onGenerateWebCitation` | `() => void` | | Called when user clicks **View Web Citation** and no cached web data exists yet |
898
- | `isWebCitationLoading` | `boolean` | `false` | Parent-controlled loading state while web citation is being fetched |
1038
+ | `gptCitation` | `IGptCitationConfig` | **required** | GPT citation config passed to `CognitiveInternalgptCoreComponent` |
1039
+ | `defaultCitationUrl` | `string` | **required** | Fallback source URL for **Visit Link** in pending/error skeletons. Used when `webCitationData.highlightedUrl` is not yet available |
1040
+ | `isWebCitation` | `boolean` | `false` | When `true`, enables web citation toggle and web view |
1041
+ | `webCitationData` | `IWebCitationApiResponse` | — | Web citation API response (screenshot metadata, URLs, optional message) |
1042
+ | `webCitationStatus` | `"pending"` \| `"success"` \| `"error"` | inferred | Lifecycle status from parent API call. Inferred from `webCitationData` when omitted |
1043
+ | `showWebCitation` | `boolean` | `true` (uncontrolled) | `false` = GPT view, `true` = web view. Omit for uncontrolled internal state |
1044
+ | `onGenerateWebCitation` | `() => void` | — | Called when web view opens and no cached screenshot exists. Parent should fetch and update status/data. Also invoked once on mount when `isWebCitation` + `showWebCitation` are true and no image is cached |
899
1045
  | `onToggleCitationView` | `() => void` | — | Called when user toggles between GPT and web views |
900
- | `showExpandImageButton` | `boolean` | `true` | Show/hide the full-screen expand button on web citation image snapshots |
901
- | `showGptMaximizeButton` | `boolean` | `true` | Show/hide the GPT citation header maximize/expand icon (`gptCitation.disableMaximize: true` when `false`) |
902
- | `topic` | `string` | auto | Override topic label in web citation header (auto-extracted from content if omitted) |
1046
+ | `showExpandImageButton` | `boolean` | `true` | Show/hide full-screen expand on screenshot |
1047
+ | `showGptMaximizeButton` | `boolean` | `true` | Show/hide GPT header maximize icon (`gptCitation.disableMaximize: true` when `false`) |
1048
+ | `iconsConfig` | `IGptWebCitationImageIconsConfig` | | `MaximizeIcon` and `ExternalLinkIcon` overrides for web view |
1049
+ | `topic` | `string` | auto | Override topic label in web header |
903
1050
  | `sourceLabel` | `string` | auto | Override header source label in web view |
904
- | `relevanceScore` | `number` | auto | Override relevance/decision strength score (auto-extracted from content if omitted) |
905
- | `learnedFrom` | `string` | auto | Site name shown in web citation footer (auto-extracted from `citationUrl` if omitted) |
1051
+ | `relevanceScore` | `number` | auto | Override relevance/decision strength badge (%) |
1052
+ | `learnedFrom` | `string` | auto | Site name in web footer (auto-derived from `citationUrl`) |
1053
+ | `sourceModel` | `string` | — | **Deprecated.** Use `sourceLabel` instead |
1054
+ | `isFixedHeight` | `boolean` | `false` | When `true`, GPT and web panels share the same fixed height/width; screenshot uses `object-fit: contain` |
906
1055
  | `styles` | `IGptWebCitationStyleOverrides` | — | Style overrides for GPT and web citation views (see Usage 5) |
907
1056
 
1057
+ When `isFixedHeight` is `true`, both views use the same panel height (`520px` by default, or `gptCitation.bodyHeight` + toggle footer). Pair with `gptCitation.bodyHeight` to customize the content area (e.g. `"384px"`).
1058
+
1059
+ #### `webCitationStatus` inference
1060
+
1061
+ When `webCitationStatus` is omitted, the component derives it in this order:
1062
+
1063
+ | Priority | Condition | Status |
1064
+ | --- | --- | --- |
1065
+ | 1 | Valid screenshot URL in `webCitationData` (`signed_url` or `gs_uri`) | `"success"` (always — image wins over explicit status) |
1066
+ | 2 | Explicit `webCitationStatus` prop | uses prop value |
1067
+ | 3 | `webCitationData` present but no image URL | `"error"` |
1068
+ | 4 | No `webCitationData` | `"pending"` |
1069
+
1070
+ Pass `webCitationStatus="pending"` explicitly while your fetch is in flight (before the image URL is available).
1071
+
908
1072
  ### `IGptCitationConfig` (gptCitation prop)
909
1073
 
910
1074
  | Field | Type | Description |
@@ -913,35 +1077,87 @@ Legacy flat keys (`gptCitationWrapper`, `footerAction`, `webCitationButton`) sti
913
1077
  | `headerTitle` | `string` | Panel header title |
914
1078
  | `index` | `number` | Citation index |
915
1079
  | `iconsConfig` | `object` | Icon components (`ChevronDownIcon`, `ChevronUpIcon`, `MaximizeIcon`, `CloseIcon`) |
916
- | `DocumentTitle` | `string` | Source document label shown in the GPT footer (right side) |
1080
+ | `DocumentTitle` | `string` | Source document label shown in the GPT footer (right side). Hidden when `isWebCitation` is true |
917
1081
  | `previewCallback` | `() => void` | Called when the source link is clicked |
918
- | `bodyHeight` | `string` | Optional fixed GPT content body height (use with `styles.webCitation.content` to match web view height) |
1082
+ | `bodyHeight` | `string` | Optional fixed GPT content body height (pair with `styles.webCitation.content`) |
919
1083
  | `disableMaximize` | `boolean` | Disable GPT header fullscreen maximize button (or use `showGptMaximizeButton={false}`) |
920
1084
 
1085
+ ### `IGptWebCitationImageIconsConfig` (iconsConfig prop)
1086
+
1087
+ | Field | Type | Fallback |
1088
+ |-------|------|----------|
1089
+ | `MaximizeIcon` | `ElementType` | `gptCitation.iconsConfig.MaximizeIcon`, then `Maximize2` |
1090
+ | `ExternalLinkIcon` | `ElementType` | `ExternalLink` |
1091
+
1092
+ ### Style override types (`styles` prop)
1093
+
1094
+ Nested under `styles.webCitation`:
1095
+
1096
+ | Type | Keys | Purpose |
1097
+ | --- | --- | --- |
1098
+ | `IGptWebCitationVisitLinkStyles` | `row`, `hint`, `link` | Visit link row shared by image footer and skeletons |
1099
+ | `IGptWebCitationImageStyles` | `wrapper`, `center`, `viewport`, `scrollContainer`, `image`, `expandButton`, `visitLink`, `fullScreen` | Screenshot view + inline loading skeleton while image decodes |
1100
+ | `IGptWebCitationSkeletonStyles` | `container`, `frame`, `shimmer`, `pending.*`, `error.*`, `visitLink` | Pending/error skeleton UI |
1101
+ | `IGptWebCitationFullScreenStyles` | `overlay`, `content`, `closeButton`, `imageWrapper`, `image` | Full-screen screenshot modal |
1102
+
1103
+ `webCitation.visitLink` merges with scoped `image.visitLink` or `skeleton.visitLink` overrides (scoped wins on conflict).
1104
+
921
1105
  ### `IWebCitationApiResponse` (webCitationData prop)
922
1106
 
923
1107
  | Field | Type | Description |
924
1108
  |-------|------|-------------|
925
1109
  | `citationId` | `string` | Unique citation identifier |
926
- | `content` | `string` | HTML/markdown web citation content |
1110
+ | `content` | `string` | HTML/markdown explanation (used for topic/score extraction) |
927
1111
  | `citationType` | `string` | Citation type (e.g. `"web"`) |
928
- | `citationUrl` | `string` | Original source URL |
1112
+ | `citationUrl` | `string` | Original source URL (screenshot footer **Visit Link**) |
1113
+ | `highlightedUrl` | `string` | Highlighted URL from API (preferred for skeleton **Visit Link**) |
1114
+ | `message` | `string` | API status or error message |
929
1115
  | `imageMetadata` | `object` | Image snapshot metadata |
930
- | `imageMetadata.signed_url` | `string` | Signed URL for screenshot image (preferred) |
1116
+ | `imageMetadata.signed_url` | `string` | Signed URL for screenshot (preferred) |
931
1117
  | `imageMetadata.gs_uri` | `string` | GCS URI fallback for image |
932
1118
  | `db` | `object` | Database metadata (`citation_id`, `blob_path`) |
933
1119
 
1120
+ ### Utility helpers
1121
+
1122
+ Exported from `@e-llm-studio/citation`:
1123
+
1124
+ | Function | Purpose |
1125
+ | --- | --- |
1126
+ | `mapCitationDataToDisplay(data)` | Normalizes API response → display fields (`topic`, `highlightedUrl`, `relevanceScore`, etc.) |
1127
+ | `getWebCitationImageUrl(data)` | Resolves screenshot URL from `imageMetadata` |
1128
+ | `resolveSkeletonVisitUrl(highlightedUrl, defaultCitationUrl)` | Visit link for pending/error skeletons |
1129
+ | `extractTopicFromContent(content)` | First anchor text from content |
1130
+ | `extractRelevanceScoreFromContent(content)` | Parses decision strength / confidence % |
1131
+ | `extractLearnedFromUrl(citationUrl)` | Hostname → site name for footer |
1132
+ | `sanitizeExternalUrl(url)` | Safe http(s) URL for links |
1133
+
934
1134
  ### View Behavior
935
1135
 
936
1136
  | State | What renders |
937
1137
  |-------|--------------|
938
1138
  | `isWebCitation={false}` | GPT citation only |
939
- | `isWebCitation={true}`, `showWebCitation={false}` | GPT citation + **View Web Citation** button (left footer) + source link (right footer) |
940
- | `isWebCitation={true}`, `showWebCitation={true}` | Web citation panel with snapshot/markdown, highlight nav, and **View GPT Citation** button |
1139
+ | `isWebCitation={true}`, `showWebCitation={false}` | GPT citation + **View Web Citation** button |
1140
+ | `isWebCitation={true}`, `showWebCitation={true}`, `webCitationStatus="pending"` | Shimmer skeleton + optional **Visit Link** (`highlightedUrl` `defaultCitationUrl`) |
1141
+ | `isWebCitation={true}`, `showWebCitation={true}`, `webCitationStatus="success"` | Screenshot (pending skeleton while image loads) + footer **Visit Link** |
1142
+ | `isWebCitation={true}`, `showWebCitation={true}`, `webCitationStatus="error"` | Error skeleton ("No preview available") + inline **Visit Link** |
1143
+ | `isFixedHeight={true}` | GPT and web outer panels share height; screenshot `object-fit: contain`; skeleton fills content area |
1144
+
1145
+ #### Auto-fetch
1146
+
1147
+ When `isWebCitation` is true and the web view is open with no cached screenshot, `onGenerateWebCitation` is called:
1148
+
1149
+ 1. Once on mount (per `webCitationData.citationId`)
1150
+ 2. Again when the user clicks **View Web Citation** and no image is cached
1151
+
1152
+ The parent should set `webCitationStatus="pending"`, fetch, then update `webCitationData` and status.
1153
+
1154
+ #### Error skeleton
1155
+
1156
+ The error state shows a fixed title ("No preview available"), a restriction message ("This website restricts external previews"), and an inline **Visit Link** when a source URL is available.
941
1157
 
942
1158
  ### Local Test Component
943
1159
 
944
- A test harness is available at `src/features/GptWebCitation/GptWebCitationTest.tsx`. Run the citation dev server with `GptWebCitationTest` mounted in `src/index.tsx` to preview the component interactively.
1160
+ A test harness is available at `src/features/GptWebCitation/GptWebCitationTest.tsx`. Run the citation dev server with `GptWebCitationTest` mounted in `src/index.tsx` to preview the component interactively. The harness uses `sampleWebCitationApiResponse` from `WebCitationSampleResponse.ts`, simulates a 2s fetch delay when **View Web Citation** is clicked, and accepts `isFixedHeight`, `webCitationStatus`, `iconsConfig`, and `styles` props for local experimentation.
945
1161
 
946
1162
  ---
947
1163