diff --git a/extensions/react-widget/package.json b/extensions/react-widget/package.json index 3e4f15da7..75239685e 100644 --- a/extensions/react-widget/package.json +++ b/extensions/react-widget/package.json @@ -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", diff --git a/extensions/react-widget/src/components/QuerySources.tsx b/extensions/react-widget/src/components/QuerySources.tsx new file mode 100644 index 000000000..a619a822d --- /dev/null +++ b/extensions/react-widget/src/components/QuerySources.tsx @@ -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["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 ( + + + {visibleSources?.map((source, index) => ( + + + source.source && source.source !== 'local' + ? window.open(source.source, '_blank', 'noopener,noreferrer') + : null + } + > + + + {source.source && source.source !== 'local' ? source.source : source.title} + + + + ))} + + + { + sources.length > 3 && ( + { + showAllSources ? `Show less` : `+ ${sources.length - 3} more` + } + + ) +} + + ) +} + +export default QuerySources \ No newline at end of file diff --git a/extensions/react-widget/src/main.tsx b/extensions/react-widget/src/main.tsx index a1a470652..f238c51ed 100644 --- a/extensions/react-widget/src/main.tsx +++ b/extensions/react-widget/src/main.tsx @@ -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(); \ No newline at end of file +root.render(); diff --git a/extensions/react-widget/src/requests/streamingApi.ts b/extensions/react-widget/src/requests/streamingApi.ts index 9cb9fddc1..d5f79fe13 100644 --- a/extensions/react-widget/src/requests/streamingApi.ts +++ b/extensions/react-widget/src/requests/streamingApi.ts @@ -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); diff --git a/extensions/react-widget/src/types/index.ts b/extensions/react-widget/src/types/index.ts index 5438cec73..34ab66a21 100644 --- a/extensions/react-widget/src/types/index.ts +++ b/extensions/react-widget/src/types/index.ts @@ -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; -} +} \ No newline at end of file