Compare commits

...

15 Commits

Author SHA1 Message Date
Alex ab40d2c37a remove pip from final container 2 months ago
Alex 784206b39b chore: Update Dockerfile to use Ubuntu mantic as base image and upgrade gunicorn to version 22.0.0 2 months ago
Alex 7c8264e221
Merge pull request #929 from TomasMatarazzo/issue-button-to-clean-chat-history
Issue button to clean chat history
2 months ago
TomasMatarazzo db7195aa30 Update Navigation.tsx 2 months ago
TomasMatarazzo eb7bbc1612 TS2741 2 months ago
TomasMatarazzo ee3792181d probando 2 months ago
TomasMatarazzo 9804965a20 style in button and user in back route delete all conv 2 months ago
TomasMatarazzo b84842df3d Fixing types 2 months ago
TomasMatarazzo fc170d3033 Update package.json 2 months ago
TomasMatarazzo 8fa4ec7ad8 delete console.log 2 months ago
TomasMatarazzo 480825ddd7 now is working in settings 2 months ago
TomasMatarazzo 260e328cc1 first change 2 months ago
TomasMatarazzo 88d9d4f4a3 Update DeleteConvModal.tsx 2 months ago
TomasMatarazzo d4840f85c0 change text in modal 2 months ago
TomasMatarazzo 6f9ddeaed0 Button to clean chat history 2 months ago

@ -1,31 +1,70 @@
FROM python:3.11-slim-bullseye as builder # Builder Stage
FROM ubuntu:mantic as builder
# Tiktoken requires Rust toolchain, so build it in a separate stage
RUN apt-get update && apt-get install -y gcc curl # Install necessary packages
RUN apt-get install -y wget unzip RUN apt-get update && \
RUN wget https://d3dg1063dc54p9.cloudfront.net/models/embeddings/mpnet-base-v2.zip apt-get install -y --no-install-recommends gcc curl wget unzip libc6-dev python3.11 python3-pip python3-venv && \
RUN unzip mpnet-base-v2.zip -d model ln -s /usr/bin/python3.11 /usr/bin/python && \
RUN rm mpnet-base-v2.zip ln -sf /usr/bin/pip3 /usr/bin/pip
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y && apt-get install --reinstall libc6-dev -y
ENV PATH="/root/.cargo/bin:${PATH}" # Download and unzip the model
RUN pip install --upgrade pip && pip install tiktoken==0.5.2 RUN wget https://d3dg1063dc54p9.cloudfront.net/models/embeddings/mpnet-base-v2.zip && \
unzip mpnet-base-v2.zip -d model && \
rm mpnet-base-v2.zip
# Install Rust
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
# Clean up to reduce container size
RUN apt-get remove --purge -y wget unzip && apt-get autoremove -y && rm -rf /var/lib/apt/lists/*
# Copy requirements.txt
COPY requirements.txt . COPY requirements.txt .
RUN pip install -r requirements.txt
# Setup Python virtual environment
RUN python3 -m venv /venv
ENV PATH="/venv/bin:$PATH"
# Install Python packages
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir tiktoken && \
pip install --no-cache-dir -r requirements.txt
FROM python:3.11-slim-bullseye # Final Stage
FROM ubuntu:mantic as final
# Copy pre-built packages and binaries from builder stage # Install Python
COPY --from=builder /usr/local/ /usr/local/ RUN apt-get update && apt-get install -y --no-install-recommends python3.11 gunicorn && \
ln -s /usr/bin/python3.11 /usr/bin/python && \
rm -rf /var/lib/apt/lists/*
# Set working directory
WORKDIR /app WORKDIR /app
# Create a non-root user: `appuser` (Feel free to choose a name)
RUN groupadd -r appuser && \
useradd -r -g appuser -d /app -s /sbin/nologin -c "Docker image user" appuser
# Copy the virtual environment and model from the builder stage
COPY --from=builder /venv /venv
COPY --from=builder /model /app/model COPY --from=builder /model /app/model
# Copy your application code
COPY . /app/application COPY . /app/application
ENV FLASK_APP=app.py
ENV FLASK_DEBUG=true
# Change the ownership of the /app directory to the appuser
RUN chown -R appuser:appuser /app
# Set environment variables
ENV FLASK_APP=app.py \
FLASK_DEBUG=true \
PATH="/venv/bin:$PATH"
# Expose the port the app runs on
EXPOSE 7091 EXPOSE 7091
CMD ["gunicorn", "-w", "2", "--timeout", "120", "--bind", "0.0.0.0:7091", "application.wsgi:app"] # Switch to non-root user
USER appuser
# Start Gunicorn
CMD ["gunicorn", "-w", "2", "--timeout", "120", "--bind", "0.0.0.0:7091", "application.wsgi:app"]

@ -37,6 +37,12 @@ def delete_conversation():
return {"status": "ok"} return {"status": "ok"}
@user.route("/api/delete_all_conversations", methods=["POST"])
def delete_all_conversations():
user_id = "local"
conversations_collection.delete_many({"user":user_id})
return {"status": "ok"}
@user.route("/api/get_conversations", methods=["get"]) @user.route("/api/get_conversations", methods=["get"])
def get_conversations(): def get_conversations():
# provides a list of conversations # provides a list of conversations

@ -10,7 +10,7 @@ escodegen==1.0.11
esprima==4.0.1 esprima==4.0.1
faiss-cpu==1.7.4 faiss-cpu==1.7.4
Flask==3.0.1 Flask==3.0.1
gunicorn==21.2.0 gunicorn==22.0.0
html2text==2020.1.16 html2text==2020.1.16
javalang==0.13.0 javalang==0.13.0
langchain==0.1.4 langchain==0.1.4
@ -27,8 +27,8 @@ redis==5.0.1
Requests==2.31.0 Requests==2.31.0
retry==0.9.2 retry==0.9.2
sentence-transformers sentence-transformers
tiktoken==0.5.2 tiktoken
torch==2.1.2 torch
tqdm==4.66.1 tqdm==4.66.1
transformers==4.36.2 transformers==4.36.2
unstructured==0.12.2 unstructured==0.12.2

@ -58,4 +58,5 @@
"vite": "^5.0.13", "vite": "^5.0.13",
"vite-plugin-svgr": "^4.2.0" "vite-plugin-svgr": "^4.2.0"
} }
} }

@ -8,7 +8,9 @@ interface ModalProps {
modalState: string; modalState: string;
isError: boolean; isError: boolean;
errorMessage?: string; errorMessage?: string;
textDelete?: boolean;
} }
const Modal = (props: ModalProps) => { const Modal = (props: ModalProps) => {
return ( return (
<div <div
@ -23,7 +25,7 @@ const Modal = (props: ModalProps) => {
onClick={() => props.handleSubmit()} onClick={() => props.handleSubmit()}
className="ml-auto h-10 w-20 rounded-3xl bg-violet-800 text-white transition-all hover:bg-violet-700" className="ml-auto h-10 w-20 rounded-3xl bg-violet-800 text-white transition-all hover:bg-violet-700"
> >
Save {props.textDelete ? 'Delete' : 'Save'}
</button> </button>
{props.isCancellable && ( {props.isCancellable && (
<button <button

@ -20,6 +20,8 @@ import Add from './assets/add.svg';
import UploadIcon from './assets/upload.svg'; import UploadIcon from './assets/upload.svg';
import { ActiveState } from './models/misc'; import { ActiveState } from './models/misc';
import APIKeyModal from './preferences/APIKeyModal'; import APIKeyModal from './preferences/APIKeyModal';
import DeleteConvModal from './preferences/DeleteConvModal';
import { import {
selectApiKeyStatus, selectApiKeyStatus,
selectSelectedDocs, selectSelectedDocs,
@ -29,6 +31,8 @@ import {
selectConversations, selectConversations,
setConversations, setConversations,
selectConversationId, selectConversationId,
selectModalStateDeleteConv,
setModalStateDeleteConv,
} from './preferences/preferenceSlice'; } from './preferences/preferenceSlice';
import { import {
setConversation, setConversation,
@ -66,7 +70,9 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
const docs = useSelector(selectSourceDocs); const docs = useSelector(selectSourceDocs);
const selectedDocs = useSelector(selectSelectedDocs); const selectedDocs = useSelector(selectSelectedDocs);
const conversations = useSelector(selectConversations); const conversations = useSelector(selectConversations);
const modalStateDeleteConv = useSelector(selectModalStateDeleteConv);
const conversationId = useSelector(selectConversationId); const conversationId = useSelector(selectConversationId);
const { isMobile } = useMediaQuery(); const { isMobile } = useMediaQuery();
const [isDarkTheme] = useDarkTheme(); const [isDarkTheme] = useDarkTheme();
const [isDocsListOpen, setIsDocsListOpen] = useState(false); const [isDocsListOpen, setIsDocsListOpen] = useState(false);
@ -92,6 +98,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
fetchConversations(); fetchConversations();
} }
}, [conversations, dispatch]); }, [conversations, dispatch]);
async function fetchConversations() { async function fetchConversations() {
return await getConversations() return await getConversations()
.then((fetchedConversations) => { .then((fetchedConversations) => {
@ -102,6 +109,16 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
}); });
} }
const handleDeleteAllConversations = () => {
fetch(`${apiHost}/api/delete_all_conversations`, {
method: 'POST',
})
.then(() => {
fetchConversations();
})
.catch((error) => console.error(error));
};
const handleDeleteConversation = (id: string) => { const handleDeleteConversation = (id: string) => {
fetch(`${apiHost}/api/delete_conversation?id=${id}`, { fetch(`${apiHost}/api/delete_conversation?id=${id}`, {
method: 'POST', method: 'POST',
@ -260,7 +277,9 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
<div className="mb-auto h-[56vh] overflow-y-auto overflow-x-hidden dark:text-white"> <div className="mb-auto h-[56vh] overflow-y-auto overflow-x-hidden dark:text-white">
{conversations && ( {conversations && (
<div> <div>
<p className="ml-6 mt-3 text-sm font-semibold">Chats</p> <div className=" my-auto mx-4 mt-2 flex h-6 items-center justify-between gap-4 rounded-3xl">
<p className="my-auto ml-6 text-sm font-semibold">Chats</p>
</div>
<div className="conversations-container"> <div className="conversations-container">
{conversations?.map((conversation) => ( {conversations?.map((conversation) => (
<ConversationTile <ConversationTile
@ -312,7 +331,6 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
</p> </p>
</NavLink> </NavLink>
</div> </div>
<div className="flex flex-col gap-2 border-b-[1.5px] py-2 dark:border-b-purple-taupe"> <div className="flex flex-col gap-2 border-b-[1.5px] py-2 dark:border-b-purple-taupe">
<NavLink <NavLink
to="/about" to="/about"
@ -370,6 +388,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
/> />
</button> </button>
</div> </div>
<SelectDocsModal <SelectDocsModal
modalState={selectedDocsModalState} modalState={selectedDocsModalState}
setModalState={setSelectedDocsModalState} setModalState={setSelectedDocsModalState}
@ -380,6 +399,11 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
setModalState={setApiKeyModalState} setModalState={setApiKeyModalState}
isCancellable={isApiKeySet} isCancellable={isApiKeySet}
/> />
<DeleteConvModal
modalState={modalStateDeleteConv}
setModalState={setModalStateDeleteConv}
handleDeleteAllConv={handleDeleteAllConversations}
/>
<Upload <Upload
modalState={uploadModalState} modalState={uploadModalState}
setModalState={setUploadModalState} setModalState={setUploadModalState}

@ -0,0 +1,63 @@
import { useRef } from 'react';
import { ActiveState } from '../models/misc';
import { useMediaQuery, useOutsideAlerter } from './../hooks';
import Modal from '../Modal';
import { useDispatch } from 'react-redux';
import { Action } from '@reduxjs/toolkit';
export default function DeleteConvModal({
modalState,
setModalState,
handleDeleteAllConv,
}: {
modalState: ActiveState;
setModalState: (val: ActiveState) => Action;
handleDeleteAllConv: () => void;
}) {
const dispatch = useDispatch();
const modalRef = useRef(null);
const { isMobile } = useMediaQuery();
useOutsideAlerter(
modalRef,
() => {
if (isMobile && modalState === 'ACTIVE') {
dispatch(setModalState('INACTIVE'));
}
},
[modalState],
);
function handleSubmit() {
handleDeleteAllConv();
dispatch(setModalState('INACTIVE'));
}
function handleCancel() {
dispatch(setModalState('INACTIVE'));
}
return (
<Modal
handleCancel={handleCancel}
isError={false}
modalState={modalState}
isCancellable={true}
handleSubmit={handleSubmit}
textDelete={true}
render={() => {
return (
<article
ref={modalRef}
className="mx-auto mt-24 flex w-[90vw] max-w-lg flex-col gap-4 rounded-t-lg bg-white p-6 shadow-lg"
>
<p className="text-xl text-jet">
Are you sure you want to delete all the conversations?
</p>
<p className="text-md leading-6 text-gray-500"></p>
</article>
);
}}
/>
);
}

@ -1,10 +1,12 @@
import { import {
PayloadAction,
createListenerMiddleware, createListenerMiddleware,
createSlice, createSlice,
isAnyOf, isAnyOf,
} from '@reduxjs/toolkit'; } from '@reduxjs/toolkit';
import { Doc, setLocalApiKey, setLocalRecentDocs } from './preferenceApi'; import { Doc, setLocalApiKey, setLocalRecentDocs } from './preferenceApi';
import { RootState } from '../store'; import { RootState } from '../store';
import { ActiveState } from '../models/misc';
interface Preference { interface Preference {
apiKey: string; apiKey: string;
@ -13,6 +15,7 @@ interface Preference {
chunks: string; chunks: string;
sourceDocs: Doc[] | null; sourceDocs: Doc[] | null;
conversations: { name: string; id: string }[] | null; conversations: { name: string; id: string }[] | null;
modalState: ActiveState;
} }
const initialState: Preference = { const initialState: Preference = {
@ -32,6 +35,7 @@ const initialState: Preference = {
} as Doc, } as Doc,
sourceDocs: null, sourceDocs: null,
conversations: null, conversations: null,
modalState: 'INACTIVE',
}; };
export const prefSlice = createSlice({ export const prefSlice = createSlice({
@ -56,6 +60,9 @@ export const prefSlice = createSlice({
setChunks: (state, action) => { setChunks: (state, action) => {
state.chunks = action.payload; state.chunks = action.payload;
}, },
setModalStateDeleteConv: (state, action: PayloadAction<ActiveState>) => {
state.modalState = action.payload;
},
}, },
}); });
@ -66,6 +73,7 @@ export const {
setConversations, setConversations,
setPrompt, setPrompt,
setChunks, setChunks,
setModalStateDeleteConv,
} = prefSlice.actions; } = prefSlice.actions;
export default prefSlice.reducer; export default prefSlice.reducer;
@ -114,6 +122,8 @@ export const selectSelectedDocsStatus = (state: RootState) =>
!!state.preference.selectedDocs; !!state.preference.selectedDocs;
export const selectSourceDocs = (state: RootState) => export const selectSourceDocs = (state: RootState) =>
state.preference.sourceDocs; state.preference.sourceDocs;
export const selectModalStateDeleteConv = (state: RootState) =>
state.preference.modalState;
export const selectSelectedDocs = (state: RootState) => export const selectSelectedDocs = (state: RootState) =>
state.preference.selectedDocs; state.preference.selectedDocs;
export const selectConversations = (state: RootState) => export const selectConversations = (state: RootState) =>

@ -8,6 +8,7 @@ import {
setPrompt, setPrompt,
setChunks, setChunks,
selectChunks, selectChunks,
setModalStateDeleteConv,
} from '../preferences/preferenceSlice'; } from '../preferences/preferenceSlice';
const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com'; const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';
@ -43,6 +44,7 @@ const General: React.FC = () => {
}; };
fetchPrompts(); fetchPrompts();
}, []); }, []);
return ( return (
<div className="mt-[59px]"> <div className="mt-[59px]">
<div className="mb-4"> <div className="mb-4">
@ -93,6 +95,19 @@ const General: React.FC = () => {
apiHost={apiHost} apiHost={apiHost}
/> />
</div> </div>
<div className="w-55 w-56">
<p className="font-bold text-jet dark:text-bright-gray">
Delete all conversations
</p>
<button
className="mt-2 flex w-full cursor-pointer items-center justify-between rounded-3xl border-2 border-solid border-purple-30 bg-white px-5 py-3 text-purple-30 hover:bg-purple-30 hover:text-white dark:border-chinese-silver dark:bg-transparent"
onClick={() => dispatch(setModalStateDeleteConv('ACTIVE'))}
>
<span className="overflow-hidden text-ellipsis dark:text-bright-gray">
Delete
</span>
</button>
</div>
</div> </div>
); );
}; };

@ -34,6 +34,7 @@ const store = configureStore({
model: '1.0', model: '1.0',
}, },
], ],
modalState: 'INACTIVE',
}, },
}, },
reducer: { reducer: {

Loading…
Cancel
Save