diff --git a/application/api/answer/routes.py b/application/api/answer/routes.py
index 9559133c..7eed8434 100644
--- a/application/api/answer/routes.py
+++ b/application/api/answer/routes.py
@@ -189,13 +189,14 @@ def complete_stream(question, retriever, conversation_id, user_api_key):
llm = LLMCreator.create_llm(
settings.LLM_NAME, api_key=settings.API_KEY, user_api_key=user_api_key
)
- conversation_id = save_conversation(
- conversation_id, question, response_full, source_log_docs, llm
- )
-
- # send data.type = "end" to indicate that the stream has ended as json
- data = json.dumps({"type": "id", "id": str(conversation_id)})
- yield f"data: {data}\n\n"
+ if(user_api_key is None):
+ conversation_id = save_conversation(
+ conversation_id, question, response_full, source_log_docs, llm
+ )
+ # send data.type = "end" to indicate that the stream has ended as json
+ data = json.dumps({"type": "id", "id": str(conversation_id)})
+ yield f"data: {data}\n\n"
+
data = json.dumps({"type": "end"})
yield f"data: {data}\n\n"
except Exception as e:
diff --git a/frontend/src/components/SourceDropdown.tsx b/frontend/src/components/SourceDropdown.tsx
index 6983fe76..ce130b4d 100644
--- a/frontend/src/components/SourceDropdown.tsx
+++ b/frontend/src/components/SourceDropdown.tsx
@@ -77,7 +77,7 @@ function SourceDropdown({
/>
{isDocsListOpen && (
-
+
{options ? (
options.map((option: any, index: number) => {
if (option.model === embeddingsName) {
diff --git a/frontend/src/conversation/Conversation.tsx b/frontend/src/conversation/Conversation.tsx
index 1b53ab2f..dddd945e 100644
--- a/frontend/src/conversation/Conversation.tsx
+++ b/frontend/src/conversation/Conversation.tsx
@@ -193,7 +193,7 @@ export default function Conversation() {
const handlePaste = (e: React.ClipboardEvent) => {
e.preventDefault();
const text = e.clipboardData.getData('text/plain');
- document.execCommand('insertText', false, text);
+ inputRef.current && (inputRef.current.innerText = text);
};
return (
diff --git a/frontend/src/conversation/SharedConversation.tsx b/frontend/src/conversation/SharedConversation.tsx
index e365c6f0..9a2d8d16 100644
--- a/frontend/src/conversation/SharedConversation.tsx
+++ b/frontend/src/conversation/SharedConversation.tsx
@@ -1,9 +1,12 @@
-import { useState, useEffect } from 'react';
+import { useState, useEffect, useRef } from 'react';
import { useParams } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { Query } from './conversationModels';
import { useTranslation } from 'react-i18next';
import ConversationBubble from './ConversationBubble';
+import Send from '../assets/send.svg';
+import Spinner from '../assets/spinner.svg';
+
import { Fragment } from 'react';
const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';
const SharedConversation = () => {
@@ -13,6 +16,8 @@ const SharedConversation = () => {
const [queries, setQueries] = useState([]);
const [title, setTitle] = useState('');
const [date, setDate] = useState('');
+ const [apiKey, setAPIKey] = useState(null);
+ const inputRef = useRef(null);
const { t } = useTranslation();
function formatISODate(isoDateStr: string) {
const date = new Date(isoDateStr);
@@ -57,10 +62,15 @@ const SharedConversation = () => {
setQueries(data.queries);
setTitle(data.title);
setDate(formatISODate(data.timestamp));
+ data.api_key && setAPIKey(data.api_key);
}
});
};
-
+ const handlePaste = (e: React.ClipboardEvent) => {
+ e.preventDefault();
+ const text = e.clipboardData.getData('text/plain');
+ inputRef.current && (inputRef.current.innerText = text);
+ };
const prepResponseView = (query: Query, index: number) => {
let responseView;
if (query.response) {
@@ -126,17 +136,51 @@ const SharedConversation = () => {
-
-
-
- {t('sharedConv.meta')}
-
+
+ {apiKey ? (
+
+
{
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ //handleQuestionSubmission();
+ }
+ }}
+ >
+ {status === 'loading' ? (
+

+ ) : (
+
+

+
+ )}
+
+ ) : (
+
+ )}
+
+ {t('sharedConv.meta')}
+
);
};
diff --git a/frontend/src/conversation/conversationApi.ts b/frontend/src/conversation/conversationApi.ts
index e107abc8..3010a63d 100644
--- a/frontend/src/conversation/conversationApi.ts
+++ b/frontend/src/conversation/conversationApi.ts
@@ -233,3 +233,76 @@ export function sendFeedback(
}
});
}
+
+export function fetchSharedAnswerSteaming( //for shared conversations
+ question: string,
+ signal: AbortSignal,
+ apiKey: string,
+ history: Array = [],
+ onEvent: (event: MessageEvent) => void,
+): Promise {
+ history = history.map((item) => {
+ return { prompt: item.prompt, response: item.response };
+ });
+
+ return new Promise((resolve, reject) => {
+ const body = {
+ question: question,
+ history: JSON.stringify(history),
+ apiKey: apiKey,
+ };
+ fetch(apiHost + '/stream', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(body),
+ signal,
+ })
+ .then((response) => {
+ if (!response.body) throw Error('No response body');
+
+ const reader = response.body.getReader();
+ const decoder = new TextDecoder('utf-8');
+ let counterrr = 0;
+ const processStream = ({
+ done,
+ value,
+ }: ReadableStreamReadResult) => {
+ if (done) {
+ console.log(counterrr);
+ return;
+ }
+
+ counterrr += 1;
+
+ const chunk = decoder.decode(value);
+
+ const lines = chunk.split('\n');
+
+ for (let line of lines) {
+ if (line.trim() == '') {
+ continue;
+ }
+ if (line.startsWith('data:')) {
+ line = line.substring(5);
+ }
+
+ const messageEvent: MessageEvent = new MessageEvent('message', {
+ data: line,
+ });
+
+ onEvent(messageEvent); // handle each message
+ }
+
+ reader.read().then(processStream).catch(reject);
+ };
+
+ reader.read().then(processStream).catch(reject);
+ })
+ .catch((error) => {
+ console.error('Connection failed:', error);
+ reject(error);
+ });
+ });
+}
diff --git a/frontend/src/conversation/sharedConversationSlice.ts b/frontend/src/conversation/sharedConversationSlice.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/frontend/src/modals/ShareConversationModal.tsx b/frontend/src/modals/ShareConversationModal.tsx
index 37da934d..cffe477b 100644
--- a/frontend/src/modals/ShareConversationModal.tsx
+++ b/frontend/src/modals/ShareConversationModal.tsx
@@ -1,8 +1,22 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
+import { useSelector } from 'react-redux';
+import {
+ selectSourceDocs,
+ selectSelectedDocs,
+ selectChunks,
+ selectPrompt,
+} from '../preferences/preferenceSlice';
+import Dropdown from '../components/Dropdown';
+import { Doc } from '../models/misc';
import Spinner from '../assets/spinner.svg';
import Exit from '../assets/exit.svg';
const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';
+const embeddingsName =
+ import.meta.env.VITE_EMBEDDINGS_NAME ||
+ 'huggingface_sentence-transformers/all-mpnet-base-v2';
+
+type StatusType = 'loading' | 'idle' | 'fetched' | 'failed';
export const ShareConversationModal = ({
close,
@@ -11,26 +25,87 @@ export const ShareConversationModal = ({
close: () => void;
conversationId: string;
}) => {
+ const { t } = useTranslation();
+
+ const domain = window.location.origin;
+
const [identifier, setIdentifier] = useState(null);
const [isCopied, setIsCopied] = useState(false);
- type StatusType = 'loading' | 'idle' | 'fetched' | 'failed';
const [status, setStatus] = useState('idle');
- const { t } = useTranslation();
- const domain = window.location.origin;
+ const [allowPrompt, setAllowPrompt] = useState(false);
+
+ const sourceDocs = useSelector(selectSourceDocs);
+ const preSelectedDoc = useSelector(selectSelectedDocs);
+ const selectedPrompt = useSelector(selectPrompt);
+ const selectedChunk = useSelector(selectChunks);
+
+ const extractDocPaths = (docs: Doc[]) =>
+ docs
+ ? docs
+ .filter((doc) => doc.model === embeddingsName)
+ .map((doc: Doc) => {
+ let namePath = doc.name;
+ if (doc.language === namePath) {
+ namePath = '.project';
+ }
+ let docPath = 'default';
+ if (doc.location === 'local') {
+ docPath = 'local' + '/' + doc.name + '/';
+ } else if (doc.location === 'remote') {
+ docPath =
+ doc.language +
+ '/' +
+ namePath +
+ '/' +
+ doc.version +
+ '/' +
+ doc.model +
+ '/';
+ }
+ return {
+ label: doc.name,
+ value: docPath,
+ };
+ })
+ : [];
+
+ const [sourcePath, setSourcePath] = useState<{
+ label: string;
+ value: string;
+ } | null>(preSelectedDoc ? extractDocPaths([preSelectedDoc])[0] : null);
+
const handleCopyKey = (url: string) => {
navigator.clipboard.writeText(url);
setIsCopied(true);
};
+
+ const togglePromptPermission = () => {
+ setAllowPrompt(!allowPrompt);
+ setStatus('idle');
+ setIdentifier(null);
+ };
+
const shareCoversationPublicly: (isPromptable: boolean) => void = (
isPromptable = false,
) => {
setStatus('loading');
+ const payload: {
+ conversation_id: string;
+ chunks?: string;
+ prompt_id?: string;
+ source?: string;
+ } = { conversation_id: conversationId };
+ if (isPromptable) {
+ payload.chunks = selectedChunk;
+ payload.prompt_id = selectedPrompt.id;
+ sourcePath && (payload.source = sourcePath.value);
+ }
fetch(`${apiHost}/api/share?isPromptable=${isPromptable}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
- body: JSON.stringify({ conversation_id: conversationId }),
+ body: JSON.stringify(payload),
})
.then((res) => {
console.log(res.status);
@@ -44,6 +119,7 @@ export const ShareConversationModal = ({
})
.catch((err) => setStatus('failed'));
};
+
return (
@@ -53,13 +129,52 @@ export const ShareConversationModal = ({
{t('modals.shareConv.label')}
{t('modals.shareConv.note')}
+
+
Allow users to prompt further
+
+
+ {allowPrompt && (
+
+
+ setSourcePath(selection)
+ }
+ options={extractDocPaths(sourceDocs ?? [])}
+ size="w-full"
+ rounded="xl"
+ />
+
+ )}
- {`${domain}/share/${
- identifier ?? '....'
- }`}
+
+ {`${domain}/share/${identifier ?? '....'}`}
+
{status === 'fetched' ? (
) : (