mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-29 08:33:20 +00:00
Sources in responses
This commit is contained in:
@@ -157,6 +157,10 @@ def complete_stream(question, docsearch, chat_history, api_key):
|
||||
docs_together = "\n".join([doc.page_content for doc in docs])
|
||||
p_chat_combine = chat_combine_template.replace("{summaries}", docs_together)
|
||||
messages_combine = [{"role": "system", "content": p_chat_combine}]
|
||||
for doc in docs:
|
||||
data = json.dumps({"type": "source", "doc": doc.page_content})
|
||||
yield f"data:{data}\n\n"
|
||||
|
||||
if len(chat_history) > 1:
|
||||
tokens_current_history = 0
|
||||
# count tokens in history
|
||||
@@ -308,6 +312,9 @@ def api_answer():
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
sources = docsearch.similarity_search(question, k=2)
|
||||
result['sources'] = [{'title': i.page_content, 'text': i.page_content} for i in sources]
|
||||
|
||||
# mock result
|
||||
# result = {
|
||||
# "answer": "The answer is 42",
|
||||
|
||||
@@ -60,6 +60,7 @@ export default function Conversation() {
|
||||
key={`${index}ANSWER`}
|
||||
message={query.response}
|
||||
type={'ANSWER'}
|
||||
sources={query.sources}
|
||||
feedback={query.feedback}
|
||||
handleFeedback={(feedback: FEEDBACK) =>
|
||||
handleFeedback(query, feedback, index)
|
||||
@@ -83,6 +84,7 @@ export default function Conversation() {
|
||||
key={`${index}QUESTION`}
|
||||
message={query.prompt}
|
||||
type="QUESTION"
|
||||
sources={query.sources}
|
||||
></ConversationBubble>
|
||||
{prepResponseView(query, index)}
|
||||
</Fragment>
|
||||
|
||||
@@ -8,6 +8,8 @@ import ReactMarkdown from 'react-markdown';
|
||||
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
|
||||
import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism';
|
||||
|
||||
const DisableSourceFE = import.meta.env.VITE_DISABLE_SOURCE_FE || false;
|
||||
|
||||
const ConversationBubble = forwardRef<
|
||||
HTMLDivElement,
|
||||
{
|
||||
@@ -16,12 +18,14 @@ const ConversationBubble = forwardRef<
|
||||
className?: string;
|
||||
feedback?: FEEDBACK;
|
||||
handleFeedback?: (feedback: FEEDBACK) => void;
|
||||
sources?: { title: string; text: string }[];
|
||||
}
|
||||
>(function ConversationBubble(
|
||||
{ message, type, className, feedback, handleFeedback },
|
||||
{ message, type, className, feedback, handleFeedback, sources },
|
||||
ref,
|
||||
) {
|
||||
const [showFeedback, setShowFeedback] = useState(false);
|
||||
const [openSource, setOpenSource] = useState<number | null>(null);
|
||||
const List = ({
|
||||
ordered,
|
||||
children,
|
||||
@@ -37,7 +41,7 @@ const ConversationBubble = forwardRef<
|
||||
if (type === 'QUESTION') {
|
||||
bubble = (
|
||||
<div ref={ref} className={`flex flex-row-reverse self-end ${className}`}>
|
||||
<Avatar className="mt-4 text-2xl" avatar="🧑💻"></Avatar>
|
||||
<Avatar className="mt-2 text-2xl" avatar="🧑💻"></Avatar>
|
||||
<div className="mr-2 ml-10 flex items-center rounded-3xl bg-blue-1000 p-3.5 text-white">
|
||||
<ReactMarkdown className="whitespace-pre-wrap break-words">
|
||||
{message}
|
||||
@@ -49,85 +53,116 @@ const ConversationBubble = forwardRef<
|
||||
bubble = (
|
||||
<div
|
||||
ref={ref}
|
||||
className={`flex self-start ${className}`}
|
||||
className={`flex self-start ${className} flex-col`}
|
||||
onMouseEnter={() => setShowFeedback(true)}
|
||||
onMouseLeave={() => setShowFeedback(false)}
|
||||
>
|
||||
<Avatar className="mt-4 text-2xl" avatar="🦖"></Avatar>
|
||||
<div
|
||||
className={`ml-2 mr-5 flex items-center rounded-3xl bg-gray-1000 p-3.5 ${
|
||||
type === 'ERROR'
|
||||
? ' rounded-lg border border-red-2000 bg-red-1000 p-2 text-red-3000'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
{type === 'ERROR' && (
|
||||
<img src={Alert} alt="alert" className="mr-2 inline" />
|
||||
)}
|
||||
<ReactMarkdown
|
||||
className="whitespace-pre-wrap break-words"
|
||||
components={{
|
||||
code({ node, inline, className, children, ...props }) {
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
|
||||
return !inline && match ? (
|
||||
<SyntaxHighlighter
|
||||
PreTag="div"
|
||||
language={match[1]}
|
||||
{...props}
|
||||
style={vscDarkPlus}
|
||||
>
|
||||
{String(children).replace(/\n$/, '')}
|
||||
</SyntaxHighlighter>
|
||||
) : (
|
||||
<code className={className ? className : ''} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
},
|
||||
ul({ node, children }) {
|
||||
return <List>{children}</List>;
|
||||
},
|
||||
ol({ node, children }) {
|
||||
return <List ordered>{children}</List>;
|
||||
},
|
||||
}}
|
||||
<div className="flex self-start">
|
||||
<Avatar className="mt-2 text-2xl" avatar="🦖"></Avatar>
|
||||
<div
|
||||
className={`ml-2 mr-5 flex items-center rounded-3xl bg-gray-1000 p-3.5 ${
|
||||
type === 'ERROR'
|
||||
? ' rounded-lg border border-red-2000 bg-red-1000 p-2 text-red-3000'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
{message}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
<div
|
||||
className={`mr-2 flex items-center justify-center ${
|
||||
feedback === 'LIKE' || (type !== 'ERROR' && showFeedback)
|
||||
? ''
|
||||
: 'md:invisible'
|
||||
}`}
|
||||
>
|
||||
<Like
|
||||
className={`cursor-pointer ${
|
||||
feedback === 'LIKE'
|
||||
? 'fill-blue-1000 stroke-blue-1000'
|
||||
: 'fill-none stroke-gray-4000 hover:fill-gray-4000'
|
||||
{type === 'ERROR' && (
|
||||
<img src={Alert} alt="alert" className="mr-2 inline" />
|
||||
)}
|
||||
<ReactMarkdown
|
||||
className="whitespace-pre-wrap break-words"
|
||||
components={{
|
||||
code({ node, inline, className, children, ...props }) {
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
|
||||
return !inline && match ? (
|
||||
<SyntaxHighlighter
|
||||
PreTag="div"
|
||||
language={match[1]}
|
||||
{...props}
|
||||
style={vscDarkPlus}
|
||||
>
|
||||
{String(children).replace(/\n$/, '')}
|
||||
</SyntaxHighlighter>
|
||||
) : (
|
||||
<code className={className ? className : ''} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
},
|
||||
ul({ node, children }) {
|
||||
return <List>{children}</List>;
|
||||
},
|
||||
ol({ node, children }) {
|
||||
return <List ordered>{children}</List>;
|
||||
},
|
||||
}}
|
||||
>
|
||||
{message}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
<div
|
||||
className={`mr-2 flex items-center justify-center ${
|
||||
feedback === 'LIKE' || (type !== 'ERROR' && showFeedback)
|
||||
? ''
|
||||
: 'md:invisible'
|
||||
}`}
|
||||
onClick={() => handleFeedback?.('LIKE')}
|
||||
></Like>
|
||||
</div>
|
||||
<div
|
||||
className={`mr-10 flex items-center justify-center ${
|
||||
feedback === 'DISLIKE' || (type !== 'ERROR' && showFeedback)
|
||||
? ''
|
||||
: 'md:invisible'
|
||||
}`}
|
||||
>
|
||||
<Dislike
|
||||
className={`cursor-pointer ${
|
||||
feedback === 'DISLIKE'
|
||||
? 'fill-red-2000 stroke-red-2000'
|
||||
: 'fill-none stroke-gray-4000 hover:fill-gray-4000'
|
||||
>
|
||||
<Like
|
||||
className={`cursor-pointer ${
|
||||
feedback === 'LIKE'
|
||||
? 'fill-blue-1000 stroke-blue-1000'
|
||||
: 'fill-none stroke-gray-4000 hover:fill-gray-4000'
|
||||
}`}
|
||||
onClick={() => handleFeedback?.('LIKE')}
|
||||
></Like>
|
||||
</div>
|
||||
<div
|
||||
className={`mr-10 flex items-center justify-center ${
|
||||
feedback === 'DISLIKE' || (type !== 'ERROR' && showFeedback)
|
||||
? ''
|
||||
: 'md:invisible'
|
||||
}`}
|
||||
onClick={() => handleFeedback?.('DISLIKE')}
|
||||
></Dislike>
|
||||
>
|
||||
<Dislike
|
||||
className={`cursor-pointer ${
|
||||
feedback === 'DISLIKE'
|
||||
? 'fill-red-2000 stroke-red-2000'
|
||||
: 'fill-none stroke-gray-4000 hover:fill-gray-4000'
|
||||
}`}
|
||||
onClick={() => handleFeedback?.('DISLIKE')}
|
||||
></Dislike>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-8 mt-2 grid w-1/2 grid-cols-3 gap-2">
|
||||
{DisableSourceFE
|
||||
? null
|
||||
: sources?.map((source, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="w-26 cursor-pointer rounded-xl border border-gray-200 py-1 px-2 hover:bg-gray-100"
|
||||
onClick={() =>
|
||||
setOpenSource(openSource === index ? null : index)
|
||||
}
|
||||
>
|
||||
<p className="truncate text-xs text-gray-500">
|
||||
{index + 1}. {source.title}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{sources && openSource !== null && sources[openSource] && (
|
||||
<div className="ml-8 mt-2 w-3/4 rounded-xl bg-blue-200 p-2">
|
||||
<p className="w-3/4 truncate text-xs text-gray-500">Source:</p>
|
||||
|
||||
<div className="rounded-xl border-2 border-gray-200 bg-white p-2">
|
||||
<p className="text-xs text-gray-500 ">
|
||||
{sources[openSource].text}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export function fetchAnswerApi(
|
||||
})
|
||||
.then((data) => {
|
||||
const result = data.answer;
|
||||
return { answer: result, query: question, result };
|
||||
return { answer: result, query: question, result, sources: data.sources };
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface Answer {
|
||||
answer: string;
|
||||
query: string;
|
||||
result: string;
|
||||
sources: { title: string; text: string }[];
|
||||
}
|
||||
|
||||
export interface Query {
|
||||
@@ -23,4 +24,5 @@ export interface Query {
|
||||
response?: string;
|
||||
feedback?: FEEDBACK;
|
||||
error?: string;
|
||||
sources?: { title: string; text: string }[];
|
||||
}
|
||||
|
||||
@@ -28,6 +28,14 @@ export const fetchAnswer = createAsyncThunk<Answer, { question: string }>(
|
||||
if (data.type === 'end') {
|
||||
// set status to 'idle'
|
||||
dispatch(conversationSlice.actions.setStatus('idle'));
|
||||
} else if (data.type === 'source') {
|
||||
const result = data.doc;
|
||||
dispatch(
|
||||
updateStreamingSource({
|
||||
index: state.conversation.queries.length - 1,
|
||||
query: { sources: [{ title: result, text: result }] },
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
const result = data.answer;
|
||||
dispatch(
|
||||
@@ -50,7 +58,7 @@ export const fetchAnswer = createAsyncThunk<Answer, { question: string }>(
|
||||
dispatch(
|
||||
updateQuery({
|
||||
index: state.conversation.queries.length - 1,
|
||||
query: { response: answer.answer },
|
||||
query: { response: answer.answer, sources: answer.sources },
|
||||
}),
|
||||
);
|
||||
dispatch(conversationSlice.actions.setStatus('idle'));
|
||||
@@ -83,6 +91,17 @@ export const conversationSlice = createSlice({
|
||||
};
|
||||
}
|
||||
},
|
||||
updateStreamingSource(
|
||||
state,
|
||||
action: PayloadAction<{ index: number; query: Partial<Query> }>,
|
||||
) {
|
||||
const index = action.payload.index;
|
||||
if (!state.queries[index].sources) {
|
||||
state.queries[index].sources = [action.payload.query.sources![0]];
|
||||
} else {
|
||||
state.queries[index].sources!.push(action.payload.query.sources![0]);
|
||||
}
|
||||
},
|
||||
updateQuery(
|
||||
state,
|
||||
action: PayloadAction<{ index: number; query: Partial<Query> }>,
|
||||
@@ -116,6 +135,10 @@ export const selectQueries = (state: RootState) => state.conversation.queries;
|
||||
|
||||
export const selectStatus = (state: RootState) => state.conversation.status;
|
||||
|
||||
export const { addQuery, updateQuery, updateStreamingQuery } =
|
||||
conversationSlice.actions;
|
||||
export const {
|
||||
addQuery,
|
||||
updateQuery,
|
||||
updateStreamingQuery,
|
||||
updateStreamingSource,
|
||||
} = conversationSlice.actions;
|
||||
export default conversationSlice.reducer;
|
||||
|
||||
Reference in New Issue
Block a user