Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: ActiveSources + type Sources #82

Merged
merged 4 commits into from
Mar 5, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lexio/lib/components/LexioProvider/provider.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useMemo } from 'react';
import { Provider, createStore } from 'jotai';
import { configAtom, registeredActionHandlersAtom } from '../../state/rag-state';
import { ActionHandler, RAGConfig } from '../../types';
import { ActionHandler, ProviderConfig } from '../../types';


import { ThemeProvider } from '../../theme/ThemeContext';
Expand All @@ -12,7 +12,7 @@ interface LexioProviderProps {
children: React.ReactNode;
onAction?: ActionHandler['handler'];
theme?: Theme;
config?: RAGConfig;
config?: ProviderConfig;
}


Expand Down
119 changes: 84 additions & 35 deletions lexio/lib/components/SourcesDisplay/SourcesDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ResetWrapper } from "../../utils/ResetWrapper";
import { useSources} from "../../hooks";
import { TrashIcon } from '@heroicons/react/24/outline';
import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
import {addOpacity} from "../../utils/scaleFontSize.tsx";
import {addOpacity, scaleFontSize} from "../../utils/scaleFontSize.tsx";

export interface SourcesDisplayStyles extends React.CSSProperties {
backgroundColor?: string;
Expand Down Expand Up @@ -202,40 +202,69 @@ const SourcesDisplay: React.FC<SourcesDisplayProps> = ({
style={{
backgroundColor: source.id === selectedSourceId
? style.selectedSourceBackground
: activeSources.includes(source)
: activeSources && activeSources.includes(source)
? style.activeSourceBackground
: activeSources.length > 0
: activeSources && activeSources.length > 0
? style.inactiveSourceBackground
: style.inactiveSourceBackground,
borderColor: source.id === selectedSourceId
? style.selectedSourceBorderColor
: activeSources.includes(source)
: activeSources && activeSources.includes(source)
? style.selectedSourceBorderColor
: style.inactiveSourceBorderColor,
opacity: activeSources.length > 0 && !activeSources.includes(source) ? 0.6 : 1,
opacity: activeSources && activeSources.length > 0 && !activeSources.includes(source) ? 0.6 : 1,
borderRadius: style.borderRadius,
fontSize: style.fontSize,
}}
onClick={() => setSelectedSource(source.id)}
>
<div className="flex items-start justify-between">
<div className="overflow-hidden">
<p className="font-medium truncate" style={{ color: style.color }}>
{source.title}
</p>
{source.type && (
<span className="inline-block px-2 py-1 font-medium rounded-full mt-1" style={{
backgroundColor: style.sourceTypeBackground,
color: style.sourceTypeColor,
fontSize: `calc(${style.fontSize} * 0.75)` // todo: replace with utils func
<div className="overflow-hidden flex-1 mr-2">
<div className="flex items-center gap-2">
<p className="font-medium truncate" style={{
color: style.color,
fontSize: scaleFontSize(style.fontSize || '16px', 1.0)
}}>
{source.type}
</span>
{source.title}
</p>
{source.href && (
<a
href={source.href}
target="_blank"
rel="noopener noreferrer"
className="flex-shrink-0 p-1 rounded hover:bg-gray-100 transition-colors"
onClick={(e) => e.stopPropagation()}
title="Open source link"
style={{
color: style.buttonBackground,
}}
>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className="w-4 h-4">
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
</svg>
</a>
)}
</div>
{source.description && (
<p className="text-gray-500 line-clamp-2" style={{
color: addOpacity(style.color || colors.text, 0.6),
fontSize: scaleFontSize(style.fontSize || '14px', 0.95)
}}>
{source.description}
</p>
)}
{showRelevanceScore && source.relevance !== undefined && (
<div className="mt-2 flex items-center">
<span style={{ color: addOpacity(style.color || colors.text, 0.6), fontSize: `calc(${style.fontSize} * 0.9)` }}>Relevance:</span>
<div className="ml-2 h-2 w-24 rounded-full" style={{ backgroundColor: style.metadataTagBackground }}>
<div className="mt-2 flex items-center group relative">
<span style={{
color: addOpacity(style.color || colors.text, 0.6),
fontSize: scaleFontSize(style.fontSize || '12px', 0.85),
}}>Relevance:</span>
<div
className="ml-2 h-2 w-24 rounded-full"
style={{
backgroundColor: style.metadataTagBackground,
}}
>
<div
className="h-2 rounded-full"
style={{
Expand All @@ -244,29 +273,49 @@ const SourcesDisplay: React.FC<SourcesDisplayProps> = ({
}}
/>
</div>
<div className="absolute bottom-full left-1/3 transform -translate-x-1 mb-1 px-2 py-1 rounded text-xs whitespace-nowrap opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-opacity duration-200"
style={{
backgroundColor: style.backgroundColor,
color: style.color,
border: `1px solid ${style.metadataTagBackground}`,
fontSize: scaleFontSize(style.fontSize || '12px', 0.75),
}}>
{Math.round(source.relevance * 100)}%
</div>
</div>
)}
</div>
{source.type && (
<span className="inline-block px-2 py-1 font-medium rounded-full flex-shrink-0" style={{
backgroundColor: style.sourceTypeBackground,
color: style.sourceTypeColor,
fontSize: scaleFontSize(style.fontSize || '12px', 0.8)
}}>
{source.type}
</span>
)}
</div>
{showMetadata && source.metadata && Object.keys(source.metadata).length > 0 && (
<div className="mt-2 pt-2 border-t" style={{ borderColor: style.inactiveSourceBorderColor }}>
<div className="pt-2 border-t" style={{ borderColor: style.inactiveSourceBorderColor }}>
<div className="flex flex-wrap gap-2">
{Object.entries(source.metadata).map(([key, value]) => (
<span
key={key}
className="inline-flex items-center px-2 py-1 rounded-md"
style={{
backgroundColor: style.metadataTagBackground,
color: style.metadataTagColor,
fontSize: `calc(${style.fontSize} * 0.75)`,
lineHeight: '1.2',
}}
>
{key}: {value}
</span>
))}
{Object.entries(source.metadata)
.filter(([key]) => typeof key === "string" && !key.startsWith("_"))
.map(([key, value]) => (
<span
key={key}
className="inline-flex items-center px-2 py-1 rounded-md"
style={{
backgroundColor: style.metadataTagBackground,
color: style.metadataTagColor,
fontSize: scaleFontSize(style.fontSize || '12px', 0.85),
lineHeight: '1.2',
}}
>
{key}: {value}
</span>
))}
</div>
</div>
</div>
)}
</li>
))}
Expand Down
2 changes: 1 addition & 1 deletion lexio/lib/hooks/hooks.ts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

allow setting null from hook?

Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const useSources = (component: Component) => {
};

const setActiveSources = (sourceIds: string[] | UUID[]) => {
dispatch({type: 'SET_ACTIVE_SOURCES', sourceIds, source: component}, false);
dispatch({type: 'SET_ACTIVE_SOURCES', sourceIds: sourceIds, source: component}, false);
};

const setSelectedSource = (sourceId: string | UUID) => {
Expand Down
30 changes: 19 additions & 11 deletions lexio/lib/state/rag-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,20 @@ export const activeMessageAtom = atom(

// sources, active sources, selected source
export const retrievedSourcesAtom = atom<Source[]>([]);
export const activeSourcesIdsAtom = atom<string[]>([]);
export const activeSourcesIdsAtom = atom<string[] | null>(null);
export const selectedSourceIdAtom = atom<string | null>(null);

export const activeSourcesAtom = atom(
(get) => {
const retrievedSources = get(retrievedSourcesAtom);
const activeIds = get(activeSourcesIdsAtom);
// Simply filter sources to only include those with IDs in the activeIds array

// If activeIds is null, return null
if (activeIds === null) {
return null;
}

// Otherwise filter sources to only include those with IDs in the activeIds array
return retrievedSources.filter(source => activeIds.includes(source.id));
}
);
Expand Down Expand Up @@ -169,7 +175,7 @@ export const addUserMessageAtom = atom(

// Update sources-related state.
set(retrievedSourcesAtom, sourcesDataWithIds);
set(activeSourcesIdsAtom, []);
set(activeSourcesIdsAtom, null);
set(selectedSourceIdAtom, null);
return sourcesDataWithIds;
};
Expand Down Expand Up @@ -324,7 +330,7 @@ const searchSourcesAtom = atom(
set(retrievedSourcesAtom, sourcesDataWithIds);

// Optionally reset related state associated with sources.
set(activeSourcesIdsAtom, []);
set(activeSourcesIdsAtom, null);
set(selectedSourceIdAtom, null);

return sourcesDataWithIds;
Expand Down Expand Up @@ -357,7 +363,7 @@ const clearSourcesAtom = atom(null, (_get, set, { action, response }: {
}) => {
console.log('clearSourcesAtom', action, response);
set(retrievedSourcesAtom, []);
set(activeSourcesIdsAtom, []);
set(activeSourcesIdsAtom, null);
set(selectedSourceIdAtom, null);
});

Expand All @@ -367,10 +373,12 @@ const setActiveSourcesAtom = atom(null, (_get, set, { action, response }: {
response: SetActiveSourcesActionResponse
}) => {
console.log('setActiveSourcesAtom', action, response);
// Use response.activeSourceIds if provided, otherwise use action.sourceIds
// Ensure we never set to null - empty array is used for reset
const activeSourceIds = response.activeSourceIds ?? action.sourceIds;
if (activeSourceIds) {
set(activeSourcesIdsAtom, activeSourceIds);
}

// Convert null to empty array to ensure we never set null directly
set(activeSourcesIdsAtom, activeSourceIds === null ? [] : activeSourceIds);
});

// set selected source
Expand Down Expand Up @@ -497,10 +505,10 @@ export const dispatchAtom = atom(

const activeSourcesIds = get(activeSourcesIdsAtom);

// If activeSourcesIds is empty, use all retrievedSources
// If activeSourcesIds is null, pass null as activeSources
// Otherwise use the filtered sources from activeSourcesAtom
const activeSources = activeSourcesIds.length === 0
? retrievedSources
const activeSources = activeSourcesIds === null
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does the atom not return null either way? handling at two places?

? null
: get(activeSourcesAtom);

// ---- Call the handler
Expand Down
Loading
Loading