Merge pull request #1317 from utin-francis-peter/feat/sources-in-react-widget

Feat/sources in react widget
This commit is contained in:
Manish Madan
2025-02-17 16:26:43 +05:30
committed by GitHub
5 changed files with 146 additions and 5 deletions

View File

@@ -41,6 +41,7 @@
},
"dependencies": {
"@babel/plugin-transform-flow-strip-types": "^7.23.3",
"@bpmn-io/snarkdown": "^2.2.0",
"@parcel/resolver-glob": "^2.12.0",
"@parcel/transformer-svg-react": "^2.12.0",
"@parcel/transformer-typescript-tsc": "^2.12.0",

View File

@@ -0,0 +1,128 @@
import {Query} from "../types/index"
import styled from 'styled-components';
import { ExternalLinkIcon } from '@radix-ui/react-icons'
import { useEffect, useMemo, useState } from "react";
const SourcesWrapper = styled.div`
margin: 4px;
display: flex;
flex-direction: column;
overflow: hidden;
`
const SourcesGrid = styled.div`
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.5rem;
max-width: 90vw;
overflow-x: scroll;
@media(min-width: 768px){
max-width: 70vw;
}
`
const SourceItem = styled.div`
cursor: pointer;
display: flex;
flex-direction: column;
justify-content: space-between;
border-radius: 6px;
background-color: ${props =>props.theme.secondary.bg};
padding-left: 12px;
padding-right: 12px;
color:${props => props.theme.text};
transform: background-color .2s, color .2s;
&:hover{
background-color: ${props => props.theme.primary.bg};
color: ${props => props.theme.primary.text};
}
`
const SourceLink = styled.div<{$hasExternalSource: boolean}>`
display: flex;
flex-direction: row;
align-items: center;
gap: 0.375rem;
text-decoration: ${({$hasExternalSource}) => ($hasExternalSource? "underline": "none")};
text-underline-offset: 2px;
`
const SourceLinkText = styled.p`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 0.75rem;
line-height: 1rem;
`
const OtherSources = styled.button`
cursor: pointer;
background: transparent;
color: #8860DB;
border: none;
outline: none;
margin-top: 0.5rem;
align-self: flex-start;
`
type TQuerySources = {
sources: Pick<Query, "sources">["sources"],
}
const QuerySources = ({sources}:TQuerySources) => {
const [showAllSources, setShowAllSources] = useState(false)
const visibleSources = useMemo(() => {
if(!sources) return [];
return showAllSources? sources : sources.slice(0, 3)
}, [sources, showAllSources])
const handleToggleShowAll = () => {
setShowAllSources(prev => !prev)
}
if(!sources || sources.length === 0){
return null;
}
return (
<SourcesWrapper>
<SourcesGrid>
{visibleSources?.map((source, index) => (
<SourceItem
key={index}
>
<SourceLink
$hasExternalSource={!!source.source && source.source !== "local"}
onClick={() =>
source.source && source.source !== 'local'
? window.open(source.source, '_blank', 'noopener,noreferrer')
: null
}
>
<ExternalLinkIcon />
<SourceLinkText title={source.source && source.source !== 'local' ? source.source : source.title}>
{source.source && source.source !== 'local' ? source.source : source.title}
</SourceLinkText>
</SourceLink>
</SourceItem>
))}
</SourcesGrid>
{
sources.length > 3 && (
<OtherSources onClick={handleToggleShowAll}>{
showAllSources ? `Show less` : `+ ${sources.length - 3} more`
}</OtherSources>
)
}
</SourcesWrapper>
)
}
export default QuerySources

View File

@@ -1,7 +1,8 @@
//development
import { createRoot } from "react-dom/client";
import { App } from "./App";
import React from "react";
const container = document.getElementById("app") as HTMLElement;
const root = createRoot(container)
root.render(<App />);
root.render(<App />);

View File

@@ -1,8 +1,10 @@
import { FEEDBACK } from "@/types";
interface HistoryItem {
prompt: string;
response?: string;
}
interface FetchAnswerStreamingProps {
question?: string;
apiKey?: string;
@@ -12,12 +14,14 @@ interface FetchAnswerStreamingProps {
apiHost?: string;
onEvent?: (event: MessageEvent) => void;
}
interface FeedbackPayload {
question: string;
answer: string;
apikey: string;
feedback: FEEDBACK;
}
export function fetchAnswerStreaming({
question = '',
apiKey = '',
@@ -46,7 +50,7 @@ export function fetchAnswerStreaming({
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let counterrr = 0;
let counter = 0;
const processStream = ({
done,
value,
@@ -56,7 +60,7 @@ export function fetchAnswerStreaming({
return;
}
counterrr += 1;
counter += 1;
const chunk = decoder.decode(value);

View File

@@ -1,16 +1,21 @@
export type MESSAGE_TYPE = 'QUESTION' | 'ANSWER' | 'ERROR';
export type Status = 'idle' | 'loading' | 'failed';
export type FEEDBACK = 'LIKE' | 'DISLIKE';
export type THEME = 'light' | 'dark';
export interface Query {
prompt: string;
response?: string;
feedback?: FEEDBACK;
error?: string;
sources?: { title: string; text: string }[];
sources?: { title: string; text: string, source:string }[];
conversationId?: string | null;
title?: string | null;
}
export interface WidgetProps {
apiHost?: string;
apiKey?: string;
@@ -32,6 +37,8 @@ export interface WidgetProps {
buttonText?:string;
buttonBg?:string;
collectFeedback?:boolean;
deafultOpen?: boolean;
showSources?: boolean
defaultOpen?: boolean;
}
export interface WidgetCoreProps extends WidgetProps {
@@ -54,4 +61,4 @@ export interface Result {
text:string;
title:string;
source:string;
}
}