Compare commits
5 Commits
4e772ab8ea
...
bbd5cb3783
Author | SHA1 | Date | |
---|---|---|---|
bbd5cb3783 | |||
5dc5e8415c | |||
9f7ddca90a | |||
e044f9c639 | |||
a1798b58ac |
253
Dockerfile
253
Dockerfile
@ -1,74 +1,28 @@
|
||||
#
|
||||
# Build Pyton 3.11 for use in later stages
|
||||
#
|
||||
FROM ubuntu:oracular AS python-build
|
||||
FROM ubuntu:oracular AS python
|
||||
|
||||
SHELL [ "/bin/bash", "-c" ]
|
||||
|
||||
# Instructions Dockerfied from:
|
||||
#
|
||||
# https://github.com/pytorch/pytorch
|
||||
#
|
||||
# and
|
||||
#
|
||||
# https://pytorch.org/docs/stable/notes/get_start_xpu.html
|
||||
# https://www.intel.com/content/www/us/en/developer/articles/tool/pytorch-prerequisites-for-intel-gpu/2-6.html
|
||||
#
|
||||
#
|
||||
# Install some utilities frequently used
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
gpg \
|
||||
wget \
|
||||
nano \
|
||||
rsync \
|
||||
jq \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/{apt,dpkg,cache,log}
|
||||
|
||||
|
||||
# ipex only supports python 3.11, so use 3.11 instead of latest oracular (3.12)
|
||||
|
||||
# Install latest Python3
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
ccache \
|
||||
cmake \
|
||||
curl \
|
||||
git \
|
||||
gpg-agent \
|
||||
less \
|
||||
libbz2-dev \
|
||||
libffi-dev \
|
||||
libjpeg-dev \
|
||||
libpng-dev \
|
||||
libreadline-dev \
|
||||
libssl-dev \
|
||||
libsqlite3-dev \
|
||||
llvm \
|
||||
nano \
|
||||
wget \
|
||||
zlib1g-dev \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/{apt,dpkg,cache,log}
|
||||
|
||||
# python3 \
|
||||
# python3-pip \
|
||||
# python3-venv \
|
||||
# python3-dev \
|
||||
|
||||
RUN /usr/sbin/update-ccache-symlinks
|
||||
RUN mkdir /opt/ccache && ccache --set-config=cache_dir=/opt/ccache
|
||||
|
||||
# Build Python in /opt/..., install it locally, then remove the build environment
|
||||
# collapsed to a single docker layer.
|
||||
WORKDIR /opt
|
||||
ENV PYTHON_VERSION=3.11.9
|
||||
|
||||
RUN wget -q -O - https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz | tar -xz \
|
||||
&& cd Python-${PYTHON_VERSION} \
|
||||
&& ./configure --prefix=/opt/python --enable-optimizations \
|
||||
&& make -j$(nproc) \
|
||||
&& make install \
|
||||
&& cd /opt \
|
||||
&& rm -rf Python-${PYTHON_VERSION}
|
||||
python3 \
|
||||
python3-pip \
|
||||
python3-venv \
|
||||
python3-dev
|
||||
|
||||
FROM ubuntu:oracular AS ze-monitor
|
||||
# From https://github.com/jketreno/ze-monitor
|
||||
@ -101,67 +55,13 @@ RUN cmake .. \
|
||||
&& make \
|
||||
&& cpack
|
||||
|
||||
#
|
||||
# Build the ipex-llm wheel for use in later stages
|
||||
#
|
||||
FROM python-build AS ipex-llm-src
|
||||
|
||||
RUN update-alternatives --install /usr/bin/python3 python3 /opt/python/bin/python3.11 2
|
||||
|
||||
RUN git clone --branch main --depth 1 https://github.com/intel/ipex-llm.git /opt/ipex-llm \
|
||||
&& cd /opt/ipex-llm \
|
||||
&& git fetch --depth 1 origin cb3c4b26ad058c156591816aa37eec4acfcbf765 \
|
||||
&& git checkout cb3c4b26ad058c156591816aa37eec4acfcbf765
|
||||
|
||||
WORKDIR /opt/ipex-llm
|
||||
|
||||
RUN python3 -m venv --system-site-packages /opt/ipex-llm/venv
|
||||
RUN { \
|
||||
echo '#!/bin/bash' ; \
|
||||
echo 'update-alternatives --set python3 /opt/python/bin/python3.11' ; \
|
||||
echo 'source /opt/ipex-llm/venv/bin/activate' ; \
|
||||
echo 'if [[ "${1}" != "" ]]; then bash -c "${@}"; else bash; fi' ; \
|
||||
} > /opt/ipex-llm/shell ; \
|
||||
chmod +x /opt/ipex-llm/shell
|
||||
|
||||
SHELL [ "/opt/ipex-llm/shell" ]
|
||||
|
||||
RUN pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/xpu
|
||||
|
||||
WORKDIR /opt/ipex-llm/python/llm
|
||||
RUN pip install requests wheel
|
||||
RUN python setup.py clean --all bdist_wheel --linux
|
||||
|
||||
#
|
||||
# The main backstory image:
|
||||
# * python 3.11
|
||||
# * pytorch xpu w/ ipex-llm
|
||||
# * ollama-ipex-llm
|
||||
# * src/server.py - model server supporting RAG and fine-tuned models
|
||||
#
|
||||
FROM ubuntu:oracular AS llm-base
|
||||
|
||||
COPY --from=python-build /opt/python /opt/python
|
||||
|
||||
# Get a couple prerequisites
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
gpg \
|
||||
# python3 \
|
||||
# python3-pip \
|
||||
# python3-venv \
|
||||
wget \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/{apt,dpkg,cache,log}
|
||||
|
||||
# The client frontend is built using React Expo to allow
|
||||
# easy creation of an Android app as well as web app
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
nodejs \
|
||||
npm \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/{apt,dpkg,cache,log}
|
||||
FROM python AS llm-base
|
||||
|
||||
# Install Intel graphics runtimes
|
||||
RUN apt-get update \
|
||||
@ -177,13 +77,10 @@ RUN apt-get update \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/{apt,dpkg,cache,log}
|
||||
|
||||
RUN update-alternatives --install /usr/bin/python3 python3 /opt/python/bin/python3.11 2
|
||||
|
||||
# Prerequisite for ze-monitor
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
libncurses6 \
|
||||
rsync \
|
||||
jq \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/{apt,dpkg,cache,log}
|
||||
|
||||
@ -199,7 +96,6 @@ RUN python3 -m venv --system-site-packages /opt/backstory/venv
|
||||
# Setup the docker pip shell
|
||||
RUN { \
|
||||
echo '#!/bin/bash' ; \
|
||||
echo 'update-alternatives --set python3 /opt/python/bin/python3.11' ; \
|
||||
echo 'if [[ -e /opt/intel/oneapi/setvars.sh ]]; then source /opt/intel/oneapi/setvars.sh; fi' ; \
|
||||
echo 'source /opt/backstory/venv/bin/activate' ; \
|
||||
echo 'if [[ "${1}" != "" ]]; then bash -c "${@}"; else bash; fi' ; \
|
||||
@ -211,12 +107,12 @@ SHELL [ "/opt/backstory/shell" ]
|
||||
|
||||
# From https://pytorch-extension.intel.com/installation?platform=gpu&version=v2.6.10%2Bxpu&os=linux%2Fwsl2&package=pip
|
||||
RUN pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/xpu
|
||||
RUN pip install intel-extension-for-pytorch==2.6.10+xpu oneccl_bind_pt==2.6.0+xpu --extra-index-url https://pytorch-extension.intel.com/release-whl/stable/xpu/us/
|
||||
#RUN pip install intel-extension-for-pytorch==2.6.10+xpu oneccl_bind_pt==2.6.0+xpu --extra-index-url https://pytorch-extension.intel.com/release-whl/stable/xpu/us/
|
||||
|
||||
# From https://huggingface.co/docs/bitsandbytes/main/en/installation?backend=Intel+CPU+%2B+GPU#multi-backend
|
||||
RUN pip install "transformers>=4.45.1"
|
||||
RUN pip install 'https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_multi-backend-refactor/bitsandbytes-0.44.1.dev0-py3-none-manylinux_2_24_x86_64.whl'
|
||||
|
||||
#RUN pip install 'https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_multi-backend-refactor/bitsandbytes-0.44.1.dev0-py3-none-manylinux_2_24_x86_64.whl'
|
||||
RUN pip install 'https://github.com/bitsandbytes-foundation/bitsandbytes/releases/download/continuous-release_multi-backend-refactor/bitsandbytes-0.45.3.dev272-py3-none-manylinux_2_24_x86_64.whl'
|
||||
# Install ollama python module
|
||||
RUN pip install ollama langchain-ollama
|
||||
|
||||
@ -227,8 +123,8 @@ RUN pip install tiktoken
|
||||
RUN pip install flask flask_cors flask_sock
|
||||
RUN pip install peft datasets
|
||||
|
||||
COPY --from=ipex-llm-src /opt/ipex-llm/python/llm/dist/*.whl /opt/wheels/
|
||||
RUN for pkg in /opt/wheels/ipex_llm*.whl; do pip install $pkg; done
|
||||
#COPY --from=ipex-llm-src /opt/ipex-llm/python/llm/dist/*.whl /opt/wheels/
|
||||
#RUN for pkg in /opt/wheels/ipex_llm*.whl; do pip install $pkg; done
|
||||
|
||||
# mistral fails with cache_position errors with transformers>4.40 (or at least it fails with the latest)
|
||||
# as well as MistralSpda* and QwenSpda* things missing (needed when loading models with )
|
||||
@ -245,18 +141,22 @@ RUN pip install "sentence_transformers<3.4.1"
|
||||
RUN pip3 install 'bigdl-core-xe-all>=2.6.0b'
|
||||
|
||||
# NOTE: IPEX includes the oneAPI components... not sure if they still need to be installed separately with a oneAPI env
|
||||
RUN pip install einops diffusers # Required for IPEX optimize(), which is required to convert from Params4bit
|
||||
# Required for IPEX optimize(), which is required to convert from Params4bit
|
||||
RUN pip install einops diffusers
|
||||
|
||||
# Needed by src/utils/chroma.py
|
||||
# Needed by src/utils/rag.py
|
||||
RUN pip install watchdog
|
||||
|
||||
# Install packages needed for stock.py
|
||||
RUN pip install yfinance pyzt geopy PyHyphen nltk
|
||||
# Install packages needed for utils/tools/*
|
||||
RUN pip install yfinance pyzt geopy
|
||||
|
||||
# Install packages needed for vector operations
|
||||
RUN pip install umap-learn
|
||||
|
||||
FROM llm-base AS backstory
|
||||
|
||||
COPY /src/requirements.txt /opt/backstory/src/requirements.txt
|
||||
RUN pip install -r /opt/backstory/src/requirements.txt
|
||||
#COPY /src/requirements.txt /opt/backstory/src/requirements.txt
|
||||
#RUN pip install -r /opt/backstory/src/requirements.txt
|
||||
RUN pip install 'markitdown[all]' pydantic
|
||||
|
||||
# Prometheus
|
||||
@ -269,7 +169,6 @@ RUN { \
|
||||
echo 'echo "Container: backstory"'; \
|
||||
echo 'set -e'; \
|
||||
echo 'echo "Setting pip environment to /opt/backstory"'; \
|
||||
echo 'update-alternatives --set python3 /opt/python/bin/python3.11' ; \
|
||||
echo 'if [[ -e /opt/intel/oneapi/setvars.sh ]]; then source /opt/intel/oneapi/setvars.sh; fi' ; \
|
||||
echo 'source /opt/backstory/venv/bin/activate'; \
|
||||
echo ''; \
|
||||
@ -283,14 +182,6 @@ RUN { \
|
||||
echo ' exec /bin/bash'; \
|
||||
echo ' fi' ; \
|
||||
echo 'else'; \
|
||||
echo ' if [[ "${PRODUCTION}" -eq 0 ]]; then'; \
|
||||
echo ' while true; do'; \
|
||||
echo ' cd /opt/backstory/frontend'; \
|
||||
echo ' echo "Launching Backstory React Frontend..."'; \
|
||||
echo ' npm start "${@}" || echo "Backstory frontend died. Restarting in 3 seconds."'; \
|
||||
echo ' sleep 3'; \
|
||||
echo ' done &' ; \
|
||||
echo ' fi' ; \
|
||||
echo ' if [[ ! -e src/cert.pem ]]; then' ; \
|
||||
echo ' echo "Generating self-signed certificate for HTTPS"'; \
|
||||
echo ' openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout src/key.pem -out src/cert.pem -subj "/C=US/ST=OR/L=Portland/O=Development/CN=localhost"'; \
|
||||
@ -320,11 +211,6 @@ ENV SYCL_CACHE_PERSISTENT=1
|
||||
ENV PATH=/opt/backstory:$PATH
|
||||
|
||||
COPY /src/ /opt/backstory/src/
|
||||
COPY /frontend/ /opt/backstory/frontend/
|
||||
|
||||
WORKDIR /opt/backstory/frontend
|
||||
RUN npm install --force
|
||||
WORKDIR /opt/backstory
|
||||
|
||||
ENTRYPOINT [ "/entrypoint.sh" ]
|
||||
|
||||
@ -381,7 +267,6 @@ RUN python3 -m venv --system-site-packages /opt/ollama/venv
|
||||
# Setup the docker pip shell
|
||||
RUN { \
|
||||
echo '#!/bin/bash' ; \
|
||||
update-alternatives --set python3 /opt/python/bin/python3.11 ; \
|
||||
echo 'source /opt/ollama/venv/bin/activate' ; \
|
||||
echo 'if [[ "${1}" != "" ]]; then bash -c ${*}; else bash; fi' ; \
|
||||
} > /opt/ollama/shell ; \
|
||||
@ -454,6 +339,14 @@ ENTRYPOINT [ "/entrypoint.sh" ]
|
||||
|
||||
FROM llm-base AS jupyter
|
||||
|
||||
# npm and Node.JS are required for jupyterlab
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
nodejs \
|
||||
npm \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/{apt,dpkg,cache,log}
|
||||
|
||||
SHELL [ "/opt/backstory/shell" ]
|
||||
|
||||
# BEGIN setup Jupyter
|
||||
@ -463,9 +356,8 @@ RUN pip install \
|
||||
&& jupyter lab build --dev-build=False --minimize=False
|
||||
# END setup Jupyter
|
||||
|
||||
COPY /src/requirements.txt /opt/backstory/src/requirements.txt
|
||||
|
||||
RUN pip install -r /opt/backstory/src/requirements.txt
|
||||
#COPY /src/requirements.txt /opt/backstory/src/requirements.txt
|
||||
#RUN pip install -r /opt/backstory/src/requirements.txt
|
||||
|
||||
RUN pip install timm xformers
|
||||
|
||||
@ -483,7 +375,6 @@ RUN { \
|
||||
echo ' echo "${HF_ACCESS_TOKEN}" > /root/.cache/hub/token' ; \
|
||||
echo ' fi' ; \
|
||||
echo 'fi' ; \
|
||||
echo 'update-alternatives --set python3 /opt/python/bin/python3.11' ; \
|
||||
echo 'if [[ -e /opt/intel/oneapi/setvars.sh ]]; then source /opt/intel/oneapi/setvars.sh; fi' ; \
|
||||
echo 'source /opt/backstory/venv/bin/activate' ; \
|
||||
echo 'if [[ "${1}" == "shell" ]]; then echo "Dropping to shell"; /bin/bash; exit $?; fi' ; \
|
||||
@ -510,9 +401,7 @@ RUN { \
|
||||
|
||||
ENTRYPOINT [ "/entrypoint-jupyter.sh" ]
|
||||
|
||||
FROM ubuntu:oracular AS miniircd
|
||||
|
||||
COPY --from=python-build /opt/python /opt/python
|
||||
FROM python AS miniircd
|
||||
|
||||
# Get a couple prerequisites
|
||||
RUN apt-get update \
|
||||
@ -526,15 +415,12 @@ RUN apt-get update \
|
||||
|
||||
WORKDIR /opt/miniircd
|
||||
|
||||
RUN update-alternatives --install /usr/bin/python3 python3 /opt/python/bin/python3.11 2
|
||||
|
||||
# Setup the ollama python virtual environment
|
||||
RUN python3 -m venv --system-site-packages /opt/miniircd/venv
|
||||
|
||||
# Setup the docker pip shell
|
||||
RUN { \
|
||||
echo '#!/bin/bash' ; \
|
||||
echo 'update-alternatives --set python3 /opt/python/bin/python3.11' ; \
|
||||
echo 'source /opt/miniircd/venv/bin/activate' ; \
|
||||
echo 'if [[ "${1}" != "" ]]; then bash -c "${@}"; else bash; fi' ; \
|
||||
} > /opt/miniircd/shell ; \
|
||||
@ -552,7 +438,6 @@ RUN { \
|
||||
echo 'echo "Container: miniircd"'; \
|
||||
echo 'set -e'; \
|
||||
echo 'echo "Setting pip environment to /opt/miniircd"'; \
|
||||
echo 'update-alternatives --set python3 /opt/python/bin/python3.11' ; \
|
||||
echo 'source /opt/miniircd/venv/bin/activate'; \
|
||||
echo ''; \
|
||||
echo 'if [[ "${1}" == "/bin/bash" ]] || [[ "${1}" =~ ^(/opt/miniircd/)?shell$ ]]; then'; \
|
||||
@ -572,3 +457,65 @@ RUN { \
|
||||
&& chmod +x /entrypoint.sh
|
||||
|
||||
ENTRYPOINT [ "/entrypoint.sh" ]
|
||||
|
||||
FROM ubuntu:oracular AS frontend
|
||||
|
||||
# The client frontend is built using React Expo to allow
|
||||
# easy creation of an Android app as well as web app
|
||||
RUN apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
nodejs \
|
||||
npm \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/{apt,dpkg,cache,log}
|
||||
|
||||
SHELL [ "/bin/bash", "-c" ]
|
||||
|
||||
RUN { \
|
||||
echo '#!/bin/bash'; \
|
||||
echo 'echo "Container: frontend"'; \
|
||||
echo 'set -e'; \
|
||||
echo ''; \
|
||||
echo 'if [[ "${1}" == "/bin/bash" ]] || [[ "${1}" =~ ^(/opt/backstory/)?shell$ ]]; then'; \
|
||||
echo ' echo "Dropping to shell"'; \
|
||||
echo ' shift' ; \
|
||||
echo ' echo "Running: ${@}"' ; \
|
||||
echo ' if [[ "${1}" != "" ]]; then' ; \
|
||||
echo ' exec ${@}'; \
|
||||
echo ' else' ; \
|
||||
echo ' exec /bin/bash'; \
|
||||
echo ' fi' ; \
|
||||
echo 'fi' ; \
|
||||
echo 'cd /opt/backstory/frontend'; \
|
||||
echo 'if [[ "${1}" == "install" ]] || [[ ! -d node_modules ]]; then'; \
|
||||
echo ' echo "Installing node modules"'; \
|
||||
echo ' if [[ -d node_modules ]]; then'; \
|
||||
echo ' echo "Deleting current node_modules"'; \
|
||||
echo ' rm -rf node_modules'; \
|
||||
echo ' fi'; \
|
||||
echo ' npm install --force'; \
|
||||
echo 'fi'; \
|
||||
echo 'if [[ "${1}" == "build" ]]; then'; \
|
||||
echo ' echo "Building production static build"'; \
|
||||
echo ' ./build.sh'; \
|
||||
echo 'fi'; \
|
||||
echo 'while true; do'; \
|
||||
echo ' echo "Launching Backstory React Frontend..."'; \
|
||||
echo ' npm start "${@}" || echo "Backstory frontend died. Restarting in 3 seconds."'; \
|
||||
echo ' sleep 3'; \
|
||||
echo 'done' ; \
|
||||
} > /entrypoint.sh \
|
||||
&& chmod +x /entrypoint.sh
|
||||
|
||||
WORKDIR /opt/backstory/frontend
|
||||
|
||||
RUN { \
|
||||
echo '#!/bin/bash' ; \
|
||||
echo 'if [[ "${1}" != "" ]]; then bash -c "${@}"; else bash; fi' ; \
|
||||
} > /opt/backstory/shell ; \
|
||||
chmod +x /opt/backstory/shell
|
||||
|
||||
COPY /frontend/ /opt/backstory/frontend/
|
||||
ENV PATH=/opt/backstory:$PATH
|
||||
|
||||
ENTRYPOINT [ "/entrypoint.sh" ]
|
||||
|
@ -91,7 +91,9 @@ This project provides the following containers:
|
||||
|
||||
| Container | Purpose |
|
||||
|:----------|:---------------------------------------------------------------|
|
||||
| backstory | Base container with GPU packages installed and configured. Main server entry point. Also used for frontend development. |
|
||||
| backstory | Base container with GPU packages installed and configured. Main server entry point. Exposes an HTTPS entrypoint for use by frontend development |
|
||||
| backstory-prod | Base container with GPU packages installed and configured. Main server entry point. Exposes an HTTP entrypoint for exposing via nginx or other reverse proxy server. Serves static files generated by frontend. |
|
||||
| frontend | Frontend development and building static file for backstory-prod. |
|
||||
| jupyter | backstory + Jupyter notebook for running Jupyter sessions |
|
||||
| miniircd | Tiny deployment of an IRC server for testing IRC agents |
|
||||
| ollama | Installation of Intel's pre-built Ollama.cpp |
|
||||
|
@ -6,7 +6,7 @@ services:
|
||||
target: backstory
|
||||
container_name: backstory
|
||||
image: backstory
|
||||
restart: "no"
|
||||
restart: "always"
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
@ -19,8 +19,7 @@ services:
|
||||
networks:
|
||||
- internal
|
||||
ports:
|
||||
- 8912:8911 # Flask React server
|
||||
- 3000:3000 # REACT expo while developing frontend
|
||||
- 8912:8911 # FastAPI React server
|
||||
volumes:
|
||||
- ./cache:/root/.cache # Persist all models and GPU kernel cache
|
||||
- ./sessions:/opt/backstory/sessions:rw # Persist sessions
|
||||
@ -28,7 +27,6 @@ services:
|
||||
- ./dev-keys:/opt/backstory/keys:ro # Developer keys
|
||||
- ./docs:/opt/backstory/docs:rw # Live mount of RAG content
|
||||
- ./src:/opt/backstory/src:rw # Live mount server src
|
||||
- ./frontend:/opt/backstory/frontend:rw # Live mount frontend src
|
||||
cap_add: # used for running ze-monitor within container
|
||||
- CAP_DAC_READ_SEARCH # Bypass all filesystem read access checks
|
||||
- CAP_PERFMON # Access to perf_events (vs. overloaded CAP_SYS_ADMIN)
|
||||
@ -54,18 +52,33 @@ services:
|
||||
networks:
|
||||
- internal
|
||||
ports:
|
||||
- 8911:8911 # Flask React server
|
||||
- 8911:8911 # FastAPI React server
|
||||
volumes:
|
||||
- ./cache:/root/.cache # Persist all models and GPU kernel cache
|
||||
- ./chromadb-prod:/opt/backstory/chromadb:rw # Persist ChromaDB
|
||||
- ./sessions-prod:/opt/backstory/sessions:rw # Persist sessions
|
||||
- ./docs-prod:/opt/backstory/docs:rw # Live mount of RAG content
|
||||
- ./frontend:/opt/backstory/frontend:rw # Live mount frontend src
|
||||
- ./frontend/deployed:/opt/backstory/deployed:ro # Live mount built frontend
|
||||
cap_add: # used for running ze-monitor within container
|
||||
- CAP_DAC_READ_SEARCH # Bypass all filesystem read access checks
|
||||
- CAP_PERFMON # Access to perf_events (vs. overloaded CAP_SYS_ADMIN)
|
||||
- CAP_SYS_PTRACE # PTRACE_MODE_READ_REALCREDS ptrace access mode check
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: frontend
|
||||
container_name: frontend
|
||||
image: frontend
|
||||
restart: "always"
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- 3000:3000 # REACT expo while developing frontend
|
||||
volumes:
|
||||
- ./frontend:/opt/backstory/frontend:rw # Live mount frontend src
|
||||
|
||||
ollama:
|
||||
build:
|
||||
context: .
|
||||
@ -116,19 +129,6 @@ services:
|
||||
volumes:
|
||||
- ./jupyter:/opt/jupyter:rw
|
||||
- ./cache:/root/.cache
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: "0" # No memory limit (Docker treats 0 as unlimited)
|
||||
reservations:
|
||||
memory: "0" # No reserved memory (optional)
|
||||
ulimits:
|
||||
memlock: -1 # Prevents memory from being locked
|
||||
#oom_kill_disable: true # Prevents OOM killer from killing the container
|
||||
cap_add: # used for running ze-monitor within container
|
||||
- CAP_DAC_READ_SEARCH # Bypass all filesystem read access checks
|
||||
- CAP_PERFMON # Access to perf_events (vs. overloaded CAP_SYS_ADMIN)
|
||||
- CAP_SYS_PTRACE # PTRACE_MODE_READ_REALCREDS ptrace access mode check
|
||||
|
||||
miniircd:
|
||||
build:
|
||||
@ -140,8 +140,6 @@ services:
|
||||
restart: "no"
|
||||
env_file:
|
||||
- .env
|
||||
devices:
|
||||
- /dev/dri:/dev/dri
|
||||
ports:
|
||||
- 6667:6667 # IRC
|
||||
networks:
|
||||
@ -153,10 +151,6 @@ services:
|
||||
image: prom/prometheus
|
||||
container_name: prometheus
|
||||
restart: "always"
|
||||
# env_file:
|
||||
# - .env
|
||||
# devices:
|
||||
# - /dev/dri:/dev/dri
|
||||
ports:
|
||||
- 9090:9090 # Prometheus
|
||||
networks:
|
||||
|
2
frontend/deployed/static/css/main.3c3ddc39.css
Normal file
2
frontend/deployed/static/css/main.3c3ddc39.css
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/css/main.3c3ddc39.css.map
Normal file
1
frontend/deployed/static/css/main.3c3ddc39.css.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/109.fea37f16.chunk.js
Normal file
2
frontend/deployed/static/js/109.fea37f16.chunk.js
Normal file
@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunkbackstory=self.webpackChunkbackstory||[]).push([[109],{3490:(e,t,a)=>{a.d(t,{diagram:()=>b});var i=a(7799),n=a(634),l=a(3957),r=a(3759),s=a(5502),o=a(700),c=r.UI.pie,p={sections:new Map,showData:!1,config:c},d=p.sections,g=p.showData,u=structuredClone(c),h=(0,r.K2)((()=>structuredClone(u)),"getConfig"),m=(0,r.K2)((()=>{d=new Map,g=p.showData,(0,r.IU)()}),"clear"),f=(0,r.K2)((e=>{let{label:t,value:a}=e;d.has(t)||(d.set(t,a),r.Rm.debug(`added new section: ${t}, with value: ${a}`))}),"addSection"),S=(0,r.K2)((()=>d),"getSections"),x=(0,r.K2)((e=>{g=e}),"setShowData"),w=(0,r.K2)((()=>g),"getShowData"),D={getConfig:h,clear:m,setDiagramTitle:r.ke,getDiagramTitle:r.ab,setAccTitle:r.SV,getAccTitle:r.iN,setAccDescription:r.EI,getAccDescription:r.m7,addSection:f,getSections:S,setShowData:x,getShowData:w},y=(0,r.K2)(((e,t)=>{(0,i.S)(e,t),t.setShowData(e.showData),e.sections.map(t.addSection)}),"populateDb"),T={parse:(0,r.K2)((async e=>{const t=await(0,s.qg)("pie",e);r.Rm.debug(t),y(t,D)}),"parse")},$=(0,r.K2)((e=>`\n .pieCircle{\n stroke: ${e.pieStrokeColor};\n stroke-width : ${e.pieStrokeWidth};\n opacity : ${e.pieOpacity};\n }\n .pieOuterCircle{\n stroke: ${e.pieOuterStrokeColor};\n stroke-width: ${e.pieOuterStrokeWidth};\n fill: none;\n }\n .pieTitleText {\n text-anchor: middle;\n font-size: ${e.pieTitleTextSize};\n fill: ${e.pieTitleTextColor};\n font-family: ${e.fontFamily};\n }\n .slice {\n font-family: ${e.fontFamily};\n fill: ${e.pieSectionTextColor};\n font-size:${e.pieSectionTextSize};\n // fill: white;\n }\n .legend text {\n fill: ${e.pieLegendTextColor};\n font-family: ${e.fontFamily};\n font-size: ${e.pieLegendTextSize};\n }\n`),"getStyles"),C=(0,r.K2)((e=>{const t=[...e.entries()].map((e=>({label:e[0],value:e[1]}))).sort(((e,t)=>t.value-e.value));return(0,o.rLf)().value((e=>e.value))(t)}),"createPieArcs"),b={parser:T,db:D,renderer:{draw:(0,r.K2)(((e,t,a,i)=>{r.Rm.debug("rendering pie chart\n"+e);const s=i.db,c=(0,r.D7)(),p=(0,n.$t)(s.getConfig(),c.pie),d=18,g=450,u=g,h=(0,l.D)(t),m=h.append("g");m.attr("transform","translate(225,225)");const{themeVariables:f}=c;let[S]=(0,n.I5)(f.pieOuterStrokeWidth);S??=2;const x=p.textPosition,w=Math.min(u,g)/2-40,D=(0,o.JLW)().innerRadius(0).outerRadius(w),y=(0,o.JLW)().innerRadius(w*x).outerRadius(w*x);m.append("circle").attr("cx",0).attr("cy",0).attr("r",w+S/2).attr("class","pieOuterCircle");const T=s.getSections(),$=C(T),b=[f.pie1,f.pie2,f.pie3,f.pie4,f.pie5,f.pie6,f.pie7,f.pie8,f.pie9,f.pie10,f.pie11,f.pie12],k=(0,o.UMr)(b);m.selectAll("mySlices").data($).enter().append("path").attr("d",D).attr("fill",(e=>k(e.data.label))).attr("class","pieCircle");let K=0;T.forEach((e=>{K+=e})),m.selectAll("mySlices").data($).enter().append("text").text((e=>(e.data.value/K*100).toFixed(0)+"%")).attr("transform",(e=>"translate("+y.centroid(e)+")")).style("text-anchor","middle").attr("class","slice"),m.append("text").text(s.getDiagramTitle()).attr("x",0).attr("y",-200).attr("class","pieTitleText");const v=m.selectAll(".legend").data(k.domain()).enter().append("g").attr("class","legend").attr("transform",((e,t)=>"translate(216,"+(22*t-22*k.domain().length/2)+")"));v.append("rect").attr("width",d).attr("height",d).style("fill",k).style("stroke",k),v.data($).append("text").attr("x",22).attr("y",14).text((e=>{const{label:t,value:a}=e.data;return s.getShowData()?`${t} [${a}]`:t}));const A=512+Math.max(...v.selectAll("text").nodes().map((e=>e?.getBoundingClientRect().width??0)));h.attr("viewBox",`0 0 ${A} 450`),(0,r.a$)(h,g,A,p.useMaxWidth)}),"draw")},styles:$}},7799:(e,t,a)=>{function i(e,t){e.accDescr&&t.setAccDescription?.(e.accDescr),e.accTitle&&t.setAccTitle?.(e.accTitle),e.title&&t.setDiagramTitle?.(e.title)}a.d(t,{S:()=>i}),(0,a(3759).K2)(i,"populateCommonDb")}}]);
|
||||
//# sourceMappingURL=109.fea37f16.chunk.js.map
|
1
frontend/deployed/static/js/109.fea37f16.chunk.js.map
Normal file
1
frontend/deployed/static/js/109.fea37f16.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/126.9837c9af.chunk.js
Normal file
2
frontend/deployed/static/js/126.9837c9af.chunk.js
Normal file
@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunkbackstory=self.webpackChunkbackstory||[]).push([[126],{2126:(r,s,e)=>{e.d(s,{diagram:()=>c});var a=e(8035),t=(e(6327),e(53),e(1580),e(2598),e(958),e(8434),e(9711),e(2596),e(634),e(3759)),c={parser:a._$,get db(){return new a.NM},renderer:a.Lh,styles:a.tM,init:(0,t.K2)((r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute}),"init")}}}]);
|
||||
//# sourceMappingURL=126.9837c9af.chunk.js.map
|
1
frontend/deployed/static/js/126.9837c9af.chunk.js.map
Normal file
1
frontend/deployed/static/js/126.9837c9af.chunk.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"static/js/126.9837c9af.chunk.js","mappings":"0NAoBIA,EAAU,CACZC,OAAQC,EAAAA,GACR,MAAIC,GACF,OAAO,IAAIC,EAAAA,EACb,EACAC,SAAUC,EAAAA,GACVC,OAAQC,EAAAA,GACRC,MAAsBC,EAAAA,EAAAA,KAAQC,IACvBA,EAAIC,QACPD,EAAIC,MAAQ,CAAC,GAEfD,EAAIC,MAAMC,oBAAsBF,EAAIE,mBAAmB,GACtD,Q","sources":["../node_modules/mermaid/dist/chunks/mermaid.core/classDiagram-v2-COTLJTTW.mjs"],"sourcesContent":["import {\n ClassDB,\n classDiagram_default,\n classRenderer_v3_unified_default,\n styles_default\n} from \"./chunk-A2AXSNBT.mjs\";\nimport \"./chunk-RZ5BOZE2.mjs\";\nimport \"./chunk-TYCBKAJE.mjs\";\nimport \"./chunk-IIMUDSI4.mjs\";\nimport \"./chunk-VV3M67IP.mjs\";\nimport \"./chunk-HRU6DDCH.mjs\";\nimport \"./chunk-K557N5IZ.mjs\";\nimport \"./chunk-H2D2JQ3I.mjs\";\nimport \"./chunk-C3MQ5ANM.mjs\";\nimport \"./chunk-O4NI6UNU.mjs\";\nimport {\n __name\n} from \"./chunk-YTJNT7DU.mjs\";\n\n// src/diagrams/class/classDiagram-v2.ts\nvar diagram = {\n parser: classDiagram_default,\n get db() {\n return new ClassDB();\n },\n renderer: classRenderer_v3_unified_default,\n styles: styles_default,\n init: /* @__PURE__ */ __name((cnf) => {\n if (!cnf.class) {\n cnf.class = {};\n }\n cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;\n }, \"init\")\n};\nexport {\n diagram\n};\n"],"names":["diagram","parser","classDiagram_default","db","ClassDB","renderer","classRenderer_v3_unified_default","styles","styles_default","init","__name","cnf","class","arrowMarkerAbsolute"],"sourceRoot":""}
|
2
frontend/deployed/static/js/147.660f25b1.chunk.js
Normal file
2
frontend/deployed/static/js/147.660f25b1.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/147.660f25b1.chunk.js.map
Normal file
1
frontend/deployed/static/js/147.660f25b1.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/203.e56567f8.chunk.js
Normal file
2
frontend/deployed/static/js/203.e56567f8.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/203.e56567f8.chunk.js.map
Normal file
1
frontend/deployed/static/js/203.e56567f8.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/255.28a7c83e.chunk.js
Normal file
2
frontend/deployed/static/js/255.28a7c83e.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/255.28a7c83e.chunk.js.map
Normal file
1
frontend/deployed/static/js/255.28a7c83e.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/282.876bf6b3.chunk.js
Normal file
2
frontend/deployed/static/js/282.876bf6b3.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/282.876bf6b3.chunk.js.map
Normal file
1
frontend/deployed/static/js/282.876bf6b3.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/314.5b42713c.chunk.js
Normal file
2
frontend/deployed/static/js/314.5b42713c.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/314.5b42713c.chunk.js.map
Normal file
1
frontend/deployed/static/js/314.5b42713c.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/318.239ef60c.chunk.js
Normal file
1
frontend/deployed/static/js/318.239ef60c.chunk.js
Normal file
@ -0,0 +1 @@
|
||||
"use strict";(self.webpackChunkbackstory=self.webpackChunkbackstory||[]).push([[318],{318:(e,s,a)=>{a.d(s,{createGitGraphServices:()=>c.b});var c=a(3670);a(1584)}}]);
|
2
frontend/deployed/static/js/320.f30ff8c0.chunk.js
Normal file
2
frontend/deployed/static/js/320.f30ff8c0.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/320.f30ff8c0.chunk.js.map
Normal file
1
frontend/deployed/static/js/320.f30ff8c0.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/349.dfb2510e.chunk.js
Normal file
2
frontend/deployed/static/js/349.dfb2510e.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/349.dfb2510e.chunk.js.map
Normal file
1
frontend/deployed/static/js/349.dfb2510e.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/35.b34bde96.chunk.js
Normal file
2
frontend/deployed/static/js/35.b34bde96.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/35.b34bde96.chunk.js.map
Normal file
1
frontend/deployed/static/js/35.b34bde96.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/355.143eaed8.chunk.js
Normal file
2
frontend/deployed/static/js/355.143eaed8.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/355.143eaed8.chunk.js.map
Normal file
1
frontend/deployed/static/js/355.143eaed8.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/358.7641aa7d.chunk.js
Normal file
2
frontend/deployed/static/js/358.7641aa7d.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/358.7641aa7d.chunk.js.map
Normal file
1
frontend/deployed/static/js/358.7641aa7d.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/370.721ed12b.chunk.js
Normal file
2
frontend/deployed/static/js/370.721ed12b.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/370.721ed12b.chunk.js.map
Normal file
1
frontend/deployed/static/js/370.721ed12b.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/372.1ba03aa4.chunk.js
Normal file
1
frontend/deployed/static/js/372.1ba03aa4.chunk.js
Normal file
@ -0,0 +1 @@
|
||||
"use strict";(self.webpackChunkbackstory=self.webpackChunkbackstory||[]).push([[372],{7372:(e,s,c)=>{c.d(s,{createInfoServices:()=>a.v});var a=c(9456);c(1584)}}]);
|
2
frontend/deployed/static/js/415.d031fb39.chunk.js
Normal file
2
frontend/deployed/static/js/415.d031fb39.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/415.d031fb39.chunk.js.map
Normal file
1
frontend/deployed/static/js/415.d031fb39.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/448.0ebff170.chunk.js
Normal file
2
frontend/deployed/static/js/448.0ebff170.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/448.0ebff170.chunk.js.map
Normal file
1
frontend/deployed/static/js/448.0ebff170.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/453.6fae039d.chunk.js
Normal file
2
frontend/deployed/static/js/453.6fae039d.chunk.js
Normal file
@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunkbackstory=self.webpackChunkbackstory||[]).push([[453],{8834:(e,t,n)=>{n.r(t),n.d(t,{getCLS:()=>y,getFCP:()=>g,getFID:()=>F,getLCP:()=>P,getTTFB:()=>D});var i,r,a,o,u=function(e,t){return{name:e,value:void 0===t?-1:t,delta:0,entries:[],id:"v2-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)}},c=function(e,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){if("first-input"===e&&!("PerformanceEventTiming"in self))return;var n=new PerformanceObserver((function(e){return e.getEntries().map(t)}));return n.observe({type:e,buffered:!0}),n}}catch(e){}},s=function(e,t){var n=function n(i){"pagehide"!==i.type&&"hidden"!==document.visibilityState||(e(i),t&&(removeEventListener("visibilitychange",n,!0),removeEventListener("pagehide",n,!0)))};addEventListener("visibilitychange",n,!0),addEventListener("pagehide",n,!0)},f=function(e){addEventListener("pageshow",(function(t){t.persisted&&e(t)}),!0)},m=function(e,t,n){var i;return function(r){t.value>=0&&(r||n)&&(t.delta=t.value-(i||0),(t.delta||void 0===i)&&(i=t.value,e(t)))}},v=-1,p=function(){return"hidden"===document.visibilityState?0:1/0},d=function(){s((function(e){var t=e.timeStamp;v=t}),!0)},l=function(){return v<0&&(v=p(),d(),f((function(){setTimeout((function(){v=p(),d()}),0)}))),{get firstHiddenTime(){return v}}},g=function(e,t){var n,i=l(),r=u("FCP"),a=function(e){"first-contentful-paint"===e.name&&(s&&s.disconnect(),e.startTime<i.firstHiddenTime&&(r.value=e.startTime,r.entries.push(e),n(!0)))},o=window.performance&&performance.getEntriesByName&&performance.getEntriesByName("first-contentful-paint")[0],s=o?null:c("paint",a);(o||s)&&(n=m(e,r,t),o&&a(o),f((function(i){r=u("FCP"),n=m(e,r,t),requestAnimationFrame((function(){requestAnimationFrame((function(){r.value=performance.now()-i.timeStamp,n(!0)}))}))})))},h=!1,T=-1,y=function(e,t){h||(g((function(e){T=e.value})),h=!0);var n,i=function(t){T>-1&&e(t)},r=u("CLS",0),a=0,o=[],v=function(e){if(!e.hadRecentInput){var t=o[0],i=o[o.length-1];a&&e.startTime-i.startTime<1e3&&e.startTime-t.startTime<5e3?(a+=e.value,o.push(e)):(a=e.value,o=[e]),a>r.value&&(r.value=a,r.entries=o,n())}},p=c("layout-shift",v);p&&(n=m(i,r,t),s((function(){p.takeRecords().map(v),n(!0)})),f((function(){a=0,T=-1,r=u("CLS",0),n=m(i,r,t)})))},E={passive:!0,capture:!0},w=new Date,L=function(e,t){i||(i=t,r=e,a=new Date,k(removeEventListener),S())},S=function(){if(r>=0&&r<a-w){var e={entryType:"first-input",name:i.type,target:i.target,cancelable:i.cancelable,startTime:i.timeStamp,processingStart:i.timeStamp+r};o.forEach((function(t){t(e)})),o=[]}},b=function(e){if(e.cancelable){var t=(e.timeStamp>1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,t){var n=function(){L(e,t),r()},i=function(){r()},r=function(){removeEventListener("pointerup",n,E),removeEventListener("pointercancel",i,E)};addEventListener("pointerup",n,E),addEventListener("pointercancel",i,E)}(t,e):L(t,e)}},k=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(t){return e(t,b,E)}))},F=function(e,t){var n,a=l(),v=u("FID"),p=function(e){e.startTime<a.firstHiddenTime&&(v.value=e.processingStart-e.startTime,v.entries.push(e),n(!0))},d=c("first-input",p);n=m(e,v,t),d&&s((function(){d.takeRecords().map(p),d.disconnect()}),!0),d&&f((function(){var a;v=u("FID"),n=m(e,v,t),o=[],r=-1,i=null,k(addEventListener),a=p,o.push(a),S()}))},C={},P=function(e,t){var n,i=l(),r=u("LCP"),a=function(e){var t=e.startTime;t<i.firstHiddenTime&&(r.value=t,r.entries.push(e),n())},o=c("largest-contentful-paint",a);if(o){n=m(e,r,t);var v=function(){C[r.id]||(o.takeRecords().map(a),o.disconnect(),C[r.id]=!0,n(!0))};["keydown","click"].forEach((function(e){addEventListener(e,v,{once:!0,capture:!0})})),s(v,!0),f((function(i){r=u("LCP"),n=m(e,r,t),requestAnimationFrame((function(){requestAnimationFrame((function(){r.value=performance.now()-i.timeStamp,C[r.id]=!0,n(!0)}))}))}))}},D=function(e){var t,n=u("TTFB");t=function(){try{var t=performance.getEntriesByType("navigation")[0]||function(){var e=performance.timing,t={entryType:"navigation",startTime:0};for(var n in e)"navigationStart"!==n&&"toJSON"!==n&&(t[n]=Math.max(e[n]-e.navigationStart,0));return t}();if(n.value=n.delta=t.responseStart,n.value<0||n.value>performance.now())return;n.entries=[t],e(n)}catch(e){}},"complete"===document.readyState?setTimeout(t,0):addEventListener("load",(function(){return setTimeout(t,0)}))}}}]);
|
||||
//# sourceMappingURL=453.6fae039d.chunk.js.map
|
1
frontend/deployed/static/js/453.6fae039d.chunk.js.map
Normal file
1
frontend/deployed/static/js/453.6fae039d.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/461.dcde3ae6.chunk.js
Normal file
2
frontend/deployed/static/js/461.dcde3ae6.chunk.js
Normal file
@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunkbackstory=self.webpackChunkbackstory||[]).push([[461],{5461:(e,r,a)=>{a.d(r,{diagram:()=>c});var t=a(9945),s=a(3957),n=a(3759),i=a(5502),d={parse:(0,n.K2)((async e=>{const r=await(0,i.qg)("info",e);n.Rm.debug(r)}),"parse")},o={version:t.n.version},c={parser:d,db:{getVersion:(0,n.K2)((()=>o.version),"getVersion")},renderer:{draw:(0,n.K2)(((e,r,a)=>{n.Rm.debug("rendering info diagram\n"+e);const t=(0,s.D)(r);(0,n.a$)(t,100,400,!0);t.append("g").append("text").attr("x",100).attr("y",40).attr("class","version").attr("font-size",32).style("text-anchor","middle").text(`v${a}`)}),"draw")}}}}]);
|
||||
//# sourceMappingURL=461.dcde3ae6.chunk.js.map
|
1
frontend/deployed/static/js/461.dcde3ae6.chunk.js.map
Normal file
1
frontend/deployed/static/js/461.dcde3ae6.chunk.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"static/js/461.dcde3ae6.chunk.js","mappings":"wKAcIA,EAAS,CACXC,OAAuBC,EAAAA,EAAAA,KAAOC,UAC5B,MAAMC,QAAYH,EAAAA,EAAAA,IAAM,OAAQI,GAChCC,EAAAA,GAAIC,MAAMH,EAAI,GACb,UAIDI,EAAkB,CAAEC,QAASC,EAAAA,EAAgBD,SAiB7CE,EAAU,CACZX,SACAY,GAjBO,CACPC,YAF+BX,EAAAA,EAAAA,KAAO,IAAMM,EAAgBC,SAAS,eAmBrEK,SANa,CAAEC,MAPUb,EAAAA,EAAAA,KAAO,CAACc,EAAMC,EAAIR,KAC3CH,EAAAA,GAAIC,MAAM,2BAA6BS,GACvC,MAAME,GAAMC,EAAAA,EAAAA,GAAiBF,IAC7BG,EAAAA,EAAAA,IAAiBF,EAAK,IAAK,KAAK,GAClBA,EAAIG,OAAO,KACnBA,OAAO,QAAQC,KAAK,IAAK,KAAKA,KAAK,IAAK,IAAIA,KAAK,QAAS,WAAWA,KAAK,YAAa,IAAIC,MAAM,cAAe,UAAUP,KAAK,IAAIP,IAAU,GAClJ,S","sources":["../node_modules/mermaid/dist/chunks/mermaid.core/infoDiagram-PH2N3AL5.mjs"],"sourcesContent":["import {\n package_default\n} from \"./chunk-5NNNAHNI.mjs\";\nimport {\n selectSvgElement\n} from \"./chunk-7B677QYD.mjs\";\nimport {\n __name,\n configureSvgSize,\n log\n} from \"./chunk-YTJNT7DU.mjs\";\n\n// src/diagrams/info/infoParser.ts\nimport { parse } from \"@mermaid-js/parser\";\nvar parser = {\n parse: /* @__PURE__ */ __name(async (input) => {\n const ast = await parse(\"info\", input);\n log.debug(ast);\n }, \"parse\")\n};\n\n// src/diagrams/info/infoDb.ts\nvar DEFAULT_INFO_DB = { version: package_default.version };\nvar getVersion = /* @__PURE__ */ __name(() => DEFAULT_INFO_DB.version, \"getVersion\");\nvar db = {\n getVersion\n};\n\n// src/diagrams/info/infoRenderer.ts\nvar draw = /* @__PURE__ */ __name((text, id, version) => {\n log.debug(\"rendering info diagram\\n\" + text);\n const svg = selectSvgElement(id);\n configureSvgSize(svg, 100, 400, true);\n const group = svg.append(\"g\");\n group.append(\"text\").attr(\"x\", 100).attr(\"y\", 40).attr(\"class\", \"version\").attr(\"font-size\", 32).style(\"text-anchor\", \"middle\").text(`v${version}`);\n}, \"draw\");\nvar renderer = { draw };\n\n// src/diagrams/info/infoDiagram.ts\nvar diagram = {\n parser,\n db,\n renderer\n};\nexport {\n diagram\n};\n"],"names":["parser","parse","__name","async","ast","input","log","debug","DEFAULT_INFO_DB","version","package_default","diagram","db","getVersion","renderer","draw","text","id","svg","selectSvgElement","configureSvgSize","append","attr","style"],"sourceRoot":""}
|
2
frontend/deployed/static/js/467.ed8fe19b.chunk.js
Normal file
2
frontend/deployed/static/js/467.ed8fe19b.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/467.ed8fe19b.chunk.js.map
Normal file
1
frontend/deployed/static/js/467.ed8fe19b.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/502.89ac9055.chunk.js
Normal file
2
frontend/deployed/static/js/502.89ac9055.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/502.89ac9055.chunk.js.map
Normal file
1
frontend/deployed/static/js/502.89ac9055.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/62.785a92ce.chunk.js
Normal file
2
frontend/deployed/static/js/62.785a92ce.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/62.785a92ce.chunk.js.map
Normal file
1
frontend/deployed/static/js/62.785a92ce.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/621.a72de2cb.chunk.js
Normal file
2
frontend/deployed/static/js/621.a72de2cb.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/621.a72de2cb.chunk.js.map
Normal file
1
frontend/deployed/static/js/621.a72de2cb.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/622.95b51007.chunk.js
Normal file
2
frontend/deployed/static/js/622.95b51007.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/622.95b51007.chunk.js.map
Normal file
1
frontend/deployed/static/js/622.95b51007.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/626.6df36496.chunk.js
Normal file
2
frontend/deployed/static/js/626.6df36496.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/626.6df36496.chunk.js.map
Normal file
1
frontend/deployed/static/js/626.6df36496.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
3
frontend/deployed/static/js/646.3df37337.chunk.js
Normal file
3
frontend/deployed/static/js/646.3df37337.chunk.js
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1,9 @@
|
||||
/*!
|
||||
Embeddable Minimum Strictly-Compliant Promises/A+ 1.1.1 Thenable
|
||||
Copyright (c) 2013-2014 Ralf S. Engelschall (http://engelschall.com)
|
||||
Licensed under The MIT License (http://opensource.org/licenses/MIT)
|
||||
*/
|
||||
|
||||
/*! Bezier curve function generator. Copyright Gaetan Renaudeau. MIT License: http://en.wikipedia.org/wiki/MIT_License */
|
||||
|
||||
/*! Runge-Kutta spring physics function generator. Adapted from Framer.js, copyright Koen Bok. MIT License: http://en.wikipedia.org/wiki/MIT_License */
|
1
frontend/deployed/static/js/646.3df37337.chunk.js.map
Normal file
1
frontend/deployed/static/js/646.3df37337.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/674.cb2cf2f8.chunk.js
Normal file
2
frontend/deployed/static/js/674.cb2cf2f8.chunk.js
Normal file
@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunkbackstory=self.webpackChunkbackstory||[]).push([[674],{4674:(t,e,a)=>{a.d(e,{diagram:()=>x});var r=a(7799),n=a(634),o=a(3957),l=a(3759),i=a(5502),c={packet:[]},s=structuredClone(c),d=l.UI.packet,k=(0,l.K2)((()=>{const t=(0,n.$t)({...d,...(0,l.zj)().packet});return t.showBits&&(t.paddingY+=10),t}),"getConfig"),p=(0,l.K2)((()=>s.packet),"getPacket"),b={pushWord:(0,l.K2)((t=>{t.length>0&&s.packet.push(t)}),"pushWord"),getPacket:p,getConfig:k,clear:(0,l.K2)((()=>{(0,l.IU)(),s=structuredClone(c)}),"clear"),setAccTitle:l.SV,getAccTitle:l.iN,setDiagramTitle:l.ke,getDiagramTitle:l.ab,getAccDescription:l.m7,setAccDescription:l.EI},g=(0,l.K2)((t=>{(0,r.S)(t,b);let e=-1,a=[],n=1;const{bitsPerRow:o}=b.getConfig();for(let{start:r,end:i,label:c}of t.blocks){if(i&&i<r)throw new Error(`Packet block ${r} - ${i} is invalid. End must be greater than start.`);if(r!==e+1)throw new Error(`Packet block ${r} - ${i??r} is not contiguous. It should start from ${e+1}.`);for(e=i??r,l.Rm.debug(`Packet block ${r} - ${e} with label ${c}`);a.length<=o+1&&b.getPacket().length<1e4;){const[t,e]=h({start:r,end:i,label:c},n,o);if(a.push(t),t.end+1===n*o&&(b.pushWord(a),a=[],n++),!e)break;({start:r,end:i,label:c}=e)}}b.pushWord(a)}),"populate"),h=(0,l.K2)(((t,e,a)=>{if(void 0===t.end&&(t.end=t.start),t.start>t.end)throw new Error(`Block start ${t.start} is greater than block end ${t.end}.`);return t.end+1<=e*a?[t,void 0]:[{start:t.start,end:e*a-1,label:t.label},{start:e*a,end:t.end,label:t.label}]}),"getNextFittingBlock"),f={parse:(0,l.K2)((async t=>{const e=await(0,i.qg)("packet",t);l.Rm.debug(e),g(e)}),"parse")},u=(0,l.K2)(((t,e,a,r)=>{const n=r.db,i=n.getConfig(),{rowHeight:c,paddingY:s,bitWidth:d,bitsPerRow:k}=i,p=n.getPacket(),b=n.getDiagramTitle(),g=c+s,h=g*(p.length+1)-(b?0:c),f=d*k+2,u=(0,o.D)(e);u.attr("viewbox",`0 0 ${f} ${h}`),(0,l.a$)(u,h,f,i.useMaxWidth);for(const[o,l]of p.entries())$(u,l,o,i);u.append("text").text(b).attr("x",f/2).attr("y",h-g/2).attr("dominant-baseline","middle").attr("text-anchor","middle").attr("class","packetTitle")}),"draw"),$=(0,l.K2)(((t,e,a,r)=>{let{rowHeight:n,paddingX:o,paddingY:l,bitWidth:i,bitsPerRow:c,showBits:s}=r;const d=t.append("g"),k=a*(n+l)+l;for(const p of e){const t=p.start%c*i+1,e=(p.end-p.start+1)*i-o;if(d.append("rect").attr("x",t).attr("y",k).attr("width",e).attr("height",n).attr("class","packetBlock"),d.append("text").attr("x",t+e/2).attr("y",k+n/2).attr("class","packetLabel").attr("dominant-baseline","middle").attr("text-anchor","middle").text(p.label),!s)continue;const a=p.end===p.start,r=k-2;d.append("text").attr("x",t+(a?e/2:0)).attr("y",r).attr("class","packetByte start").attr("dominant-baseline","auto").attr("text-anchor",a?"middle":"start").text(p.start),a||d.append("text").attr("x",t+e).attr("y",r).attr("class","packetByte end").attr("dominant-baseline","auto").attr("text-anchor","end").text(p.end)}}),"drawWord"),w={byteFontSize:"10px",startByteColor:"black",endByteColor:"black",labelColor:"black",labelFontSize:"12px",titleColor:"black",titleFontSize:"14px",blockStrokeColor:"black",blockStrokeWidth:"1",blockFillColor:"#efefef"},x={parser:f,db:b,renderer:{draw:u},styles:(0,l.K2)((function(){let{packet:t}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};const e=(0,n.$t)(w,t);return`\n\t.packetByte {\n\t\tfont-size: ${e.byteFontSize};\n\t}\n\t.packetByte.start {\n\t\tfill: ${e.startByteColor};\n\t}\n\t.packetByte.end {\n\t\tfill: ${e.endByteColor};\n\t}\n\t.packetLabel {\n\t\tfill: ${e.labelColor};\n\t\tfont-size: ${e.labelFontSize};\n\t}\n\t.packetTitle {\n\t\tfill: ${e.titleColor};\n\t\tfont-size: ${e.titleFontSize};\n\t}\n\t.packetBlock {\n\t\tstroke: ${e.blockStrokeColor};\n\t\tstroke-width: ${e.blockStrokeWidth};\n\t\tfill: ${e.blockFillColor};\n\t}\n\t`}),"styles")}},7799:(t,e,a)=>{function r(t,e){t.accDescr&&e.setAccDescription?.(t.accDescr),t.accTitle&&e.setAccTitle?.(t.accTitle),t.title&&e.setDiagramTitle?.(t.title)}a.d(e,{S:()=>r}),(0,a(3759).K2)(r,"populateCommonDb")}}]);
|
||||
//# sourceMappingURL=674.cb2cf2f8.chunk.js.map
|
1
frontend/deployed/static/js/674.cb2cf2f8.chunk.js.map
Normal file
1
frontend/deployed/static/js/674.cb2cf2f8.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/732.b7e64c48.chunk.js
Normal file
2
frontend/deployed/static/js/732.b7e64c48.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/732.b7e64c48.chunk.js.map
Normal file
1
frontend/deployed/static/js/732.b7e64c48.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/761.64498d2a.chunk.js
Normal file
2
frontend/deployed/static/js/761.64498d2a.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/761.64498d2a.chunk.js.map
Normal file
1
frontend/deployed/static/js/761.64498d2a.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/807.a9b3c2ae.chunk.js
Normal file
2
frontend/deployed/static/js/807.a9b3c2ae.chunk.js
Normal file
@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunkbackstory=self.webpackChunkbackstory||[]).push([[807],{2807:(e,r,t)=>{t.d(r,{diagram:()=>k});var a=t(3355),s=(t(6327),t(53),t(1580),t(2598),t(958),t(8434),t(9711),t(2596),t(634),t(3759)),k={parser:a.Zk,get db(){return new a.u4(2)},renderer:a.q7,styles:a.tM,init:(0,s.K2)((e=>{e.state||(e.state={}),e.state.arrowMarkerAbsolute=e.arrowMarkerAbsolute}),"init")}}}]);
|
||||
//# sourceMappingURL=807.a9b3c2ae.chunk.js.map
|
1
frontend/deployed/static/js/807.a9b3c2ae.chunk.js.map
Normal file
1
frontend/deployed/static/js/807.a9b3c2ae.chunk.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"static/js/807.a9b3c2ae.chunk.js","mappings":"0NAoBIA,EAAU,CACZC,OAAQC,EAAAA,GACR,MAAIC,GACF,OAAO,IAAIC,EAAAA,GAAQ,EACrB,EACAC,SAAUC,EAAAA,GACVC,OAAQC,EAAAA,GACRC,MAAsBC,EAAAA,EAAAA,KAAQC,IACvBA,EAAIC,QACPD,EAAIC,MAAQ,CAAC,GAEfD,EAAIC,MAAMC,oBAAsBF,EAAIE,mBAAmB,GACtD,Q","sources":["../node_modules/mermaid/dist/chunks/mermaid.core/stateDiagram-v2-YXO3MK2T.mjs"],"sourcesContent":["import {\n StateDB,\n stateDiagram_default,\n stateRenderer_v3_unified_default,\n styles_default\n} from \"./chunk-AEK57VVT.mjs\";\nimport \"./chunk-RZ5BOZE2.mjs\";\nimport \"./chunk-TYCBKAJE.mjs\";\nimport \"./chunk-IIMUDSI4.mjs\";\nimport \"./chunk-VV3M67IP.mjs\";\nimport \"./chunk-HRU6DDCH.mjs\";\nimport \"./chunk-K557N5IZ.mjs\";\nimport \"./chunk-H2D2JQ3I.mjs\";\nimport \"./chunk-C3MQ5ANM.mjs\";\nimport \"./chunk-O4NI6UNU.mjs\";\nimport {\n __name\n} from \"./chunk-YTJNT7DU.mjs\";\n\n// src/diagrams/state/stateDiagram-v2.ts\nvar diagram = {\n parser: stateDiagram_default,\n get db() {\n return new StateDB(2);\n },\n renderer: stateRenderer_v3_unified_default,\n styles: styles_default,\n init: /* @__PURE__ */ __name((cnf) => {\n if (!cnf.state) {\n cnf.state = {};\n }\n cnf.state.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;\n }, \"init\")\n};\nexport {\n diagram\n};\n"],"names":["diagram","parser","stateDiagram_default","db","StateDB","renderer","stateRenderer_v3_unified_default","styles","styles_default","init","__name","cnf","state","arrowMarkerAbsolute"],"sourceRoot":""}
|
2
frontend/deployed/static/js/824.71eb7c8f.chunk.js
Normal file
2
frontend/deployed/static/js/824.71eb7c8f.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/824.71eb7c8f.chunk.js.map
Normal file
1
frontend/deployed/static/js/824.71eb7c8f.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/854.2162dcf9.chunk.js
Normal file
2
frontend/deployed/static/js/854.2162dcf9.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/854.2162dcf9.chunk.js.map
Normal file
1
frontend/deployed/static/js/854.2162dcf9.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
frontend/deployed/static/js/859.88148fa8.chunk.js
Normal file
2
frontend/deployed/static/js/859.88148fa8.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/859.88148fa8.chunk.js.map
Normal file
1
frontend/deployed/static/js/859.88148fa8.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/914.0546aa7a.chunk.js
Normal file
1
frontend/deployed/static/js/914.0546aa7a.chunk.js
Normal file
@ -0,0 +1 @@
|
||||
"use strict";(self.webpackChunkbackstory=self.webpackChunkbackstory||[]).push([[914],{914:(e,s,c)=>{c.d(s,{createPieServices:()=>a.f});var a=c(7789);c(1584)}}]);
|
2
frontend/deployed/static/js/922.10b19c61.chunk.js
Normal file
2
frontend/deployed/static/js/922.10b19c61.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/922.10b19c61.chunk.js.map
Normal file
1
frontend/deployed/static/js/922.10b19c61.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
1
frontend/deployed/static/js/929.7d5d6402.chunk.js
Normal file
1
frontend/deployed/static/js/929.7d5d6402.chunk.js
Normal file
@ -0,0 +1 @@
|
||||
"use strict";(self.webpackChunkbackstory=self.webpackChunkbackstory||[]).push([[929],{4929:(e,a,s)=>{s.d(a,{createRadarServices:()=>c.f});var c=s(2502);s(1584)}}]);
|
2
frontend/deployed/static/js/972.19d4b287.chunk.js
Normal file
2
frontend/deployed/static/js/972.19d4b287.chunk.js
Normal file
@ -0,0 +1,2 @@
|
||||
"use strict";(self.webpackChunkbackstory=self.webpackChunkbackstory||[]).push([[972],{5972:(r,s,e)=>{e.d(s,{diagram:()=>c});var a=e(8035),t=(e(6327),e(53),e(1580),e(2598),e(958),e(8434),e(9711),e(2596),e(634),e(3759)),c={parser:a._$,get db(){return new a.NM},renderer:a.Lh,styles:a.tM,init:(0,t.K2)((r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute}),"init")}}}]);
|
||||
//# sourceMappingURL=972.19d4b287.chunk.js.map
|
1
frontend/deployed/static/js/972.19d4b287.chunk.js.map
Normal file
1
frontend/deployed/static/js/972.19d4b287.chunk.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"file":"static/js/972.19d4b287.chunk.js","mappings":"0NAoBIA,EAAU,CACZC,OAAQC,EAAAA,GACR,MAAIC,GACF,OAAO,IAAIC,EAAAA,EACb,EACAC,SAAUC,EAAAA,GACVC,OAAQC,EAAAA,GACRC,MAAsBC,EAAAA,EAAAA,KAAQC,IACvBA,EAAIC,QACPD,EAAIC,MAAQ,CAAC,GAEfD,EAAIC,MAAMC,oBAAsBF,EAAIE,mBAAmB,GACtD,Q","sources":["../node_modules/mermaid/dist/chunks/mermaid.core/classDiagram-GIVACNV2.mjs"],"sourcesContent":["import {\n ClassDB,\n classDiagram_default,\n classRenderer_v3_unified_default,\n styles_default\n} from \"./chunk-A2AXSNBT.mjs\";\nimport \"./chunk-RZ5BOZE2.mjs\";\nimport \"./chunk-TYCBKAJE.mjs\";\nimport \"./chunk-IIMUDSI4.mjs\";\nimport \"./chunk-VV3M67IP.mjs\";\nimport \"./chunk-HRU6DDCH.mjs\";\nimport \"./chunk-K557N5IZ.mjs\";\nimport \"./chunk-H2D2JQ3I.mjs\";\nimport \"./chunk-C3MQ5ANM.mjs\";\nimport \"./chunk-O4NI6UNU.mjs\";\nimport {\n __name\n} from \"./chunk-YTJNT7DU.mjs\";\n\n// src/diagrams/class/classDiagram.ts\nvar diagram = {\n parser: classDiagram_default,\n get db() {\n return new ClassDB();\n },\n renderer: classRenderer_v3_unified_default,\n styles: styles_default,\n init: /* @__PURE__ */ __name((cnf) => {\n if (!cnf.class) {\n cnf.class = {};\n }\n cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;\n }, \"init\")\n};\nexport {\n diagram\n};\n"],"names":["diagram","parser","classDiagram_default","db","ClassDB","renderer","classRenderer_v3_unified_default","styles","styles_default","init","__name","cnf","class","arrowMarkerAbsolute"],"sourceRoot":""}
|
1
frontend/deployed/static/js/974.879246cb.chunk.js
Normal file
1
frontend/deployed/static/js/974.879246cb.chunk.js
Normal file
@ -0,0 +1 @@
|
||||
"use strict";(self.webpackChunkbackstory=self.webpackChunkbackstory||[]).push([[974],{6974:(e,c,s)=>{s.d(c,{createPacketServices:()=>a.$});var a=s(8062);s(1584)}}]);
|
1
frontend/deployed/static/js/987.378e3c51.chunk.js
Normal file
1
frontend/deployed/static/js/987.378e3c51.chunk.js
Normal file
@ -0,0 +1 @@
|
||||
"use strict";(self.webpackChunkbackstory=self.webpackChunkbackstory||[]).push([[987],{987:(e,c,r)=>{r.d(c,{createArchitectureServices:()=>s.S});var s=r(1438);r(1584)}}]);
|
103
frontend/deployed/static/js/main.b1c48cdf.js
Normal file
103
frontend/deployed/static/js/main.b1c48cdf.js
Normal file
File diff suppressed because one or more lines are too long
157
frontend/deployed/static/js/main.b1c48cdf.js.LICENSE.txt
Normal file
157
frontend/deployed/static/js/main.b1c48cdf.js.LICENSE.txt
Normal file
@ -0,0 +1,157 @@
|
||||
/*!
|
||||
* Determine if an object is a Buffer
|
||||
*
|
||||
* @author Feross Aboukhadijeh <https://feross.org>
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/*!
|
||||
* The buffer module from node.js, for the browser.
|
||||
*
|
||||
* @author Feross Aboukhadijeh <https://feross.org>
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/*!
|
||||
* pad-left <https://github.com/jonschlinkert/pad-left>
|
||||
*
|
||||
* Copyright (c) 2014-2015, Jon Schlinkert.
|
||||
* Licensed under the MIT license.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* repeat-string <https://github.com/jonschlinkert/repeat-string>
|
||||
*
|
||||
* Copyright (c) 2014-2015, Jon Schlinkert.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
|
||||
/*! @license DOMPurify 3.2.5 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.5/LICENSE */
|
||||
|
||||
/*! Bundled license information:
|
||||
|
||||
js-yaml/dist/js-yaml.mjs:
|
||||
(*! js-yaml 4.1.0 https://github.com/nodeca/js-yaml @license MIT *)
|
||||
*/
|
||||
|
||||
/*! Bundled license information:
|
||||
|
||||
native-promise-only/lib/npo.src.js:
|
||||
(*! Native Promise Only
|
||||
v0.8.1 (c) Kyle Simpson
|
||||
MIT License: http://getify.mit-license.org
|
||||
*)
|
||||
|
||||
polybooljs/index.js:
|
||||
(*
|
||||
* @copyright 2016 Sean Connelly (@voidqk), http://syntheti.cc
|
||||
* @license MIT
|
||||
* @preserve Project Home: https://github.com/voidqk/polybooljs
|
||||
*)
|
||||
|
||||
ieee754/index.js:
|
||||
(*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> *)
|
||||
|
||||
buffer/index.js:
|
||||
(*!
|
||||
* The buffer module from node.js, for the browser.
|
||||
*
|
||||
* @author Feross Aboukhadijeh <https://feross.org>
|
||||
* @license MIT
|
||||
*)
|
||||
|
||||
safe-buffer/index.js:
|
||||
(*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> *)
|
||||
|
||||
assert/build/internal/util/comparisons.js:
|
||||
(*!
|
||||
* The buffer module from node.js, for the browser.
|
||||
*
|
||||
* @author Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
|
||||
* @license MIT
|
||||
*)
|
||||
|
||||
object-assign/index.js:
|
||||
(*
|
||||
object-assign
|
||||
(c) Sindre Sorhus
|
||||
@license MIT
|
||||
*)
|
||||
|
||||
maplibre-gl/dist/maplibre-gl.js:
|
||||
(**
|
||||
* MapLibre GL JS
|
||||
* @license 3-Clause BSD. Full text of license: https://github.com/maplibre/maplibre-gl-js/blob/v4.7.1/LICENSE.txt
|
||||
*)
|
||||
*/
|
||||
|
||||
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-dom-client.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-is.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.js
|
||||
*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/** @license React v16.13.1
|
||||
* react-is.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
1
frontend/deployed/static/js/main.b1c48cdf.js.map
Normal file
1
frontend/deployed/static/js/main.b1c48cdf.js.map
Normal file
File diff suppressed because one or more lines are too long
202
frontend/package-lock.json
generated
202
frontend/package-lock.json
generated
@ -25,13 +25,16 @@
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@uiw/react-json-view": "^2.0.0-alpha.31",
|
||||
"jsonrepair": "^3.12.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"mermaid": "^11.6.0",
|
||||
"mui-markdown": "^2.0.1",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-markdown-it": "^1.0.2",
|
||||
"react-plotly.js": "^2.6.0",
|
||||
"react-router-dom": "^7.6.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-spinners": "^0.15.0",
|
||||
"rehype-katex": "^7.0.1",
|
||||
@ -42,6 +45,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@craco/craco": "^7.1.0",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@types/plotly.js": "^2.35.5"
|
||||
}
|
||||
},
|
||||
@ -5090,6 +5094,12 @@
|
||||
"integrity": "sha512-Gjm4+H9noDJgu5EdT3rUw5MhPBag46fiOy27BefvWkNL8mlZnKnCaVVVTLKj6RYXed9b62CPKnPav9govyQDzA==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/linkify-it": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/long": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
|
||||
@ -5112,6 +5122,16 @@
|
||||
"@types/pbf": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/markdown-it": {
|
||||
"version": "14.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
|
||||
"integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/linkify-it": "^5",
|
||||
"@types/mdurl": "^2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mdast": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
|
||||
@ -5120,6 +5140,12 @@
|
||||
"@types/unist": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/mdurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
|
||||
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/mime": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
|
||||
@ -11221,6 +11247,14 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stdin": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
|
||||
"integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
@ -14334,6 +14368,14 @@
|
||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
||||
},
|
||||
"node_modules/linkify-it": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||
"integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
|
||||
"dependencies": {
|
||||
"uc.micro": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/loader-runner": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
|
||||
@ -14706,6 +14748,38 @@
|
||||
"node": "^16.13.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it": {
|
||||
"version": "14.1.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
|
||||
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1",
|
||||
"entities": "^4.4.0",
|
||||
"linkify-it": "^5.0.0",
|
||||
"mdurl": "^2.0.0",
|
||||
"punycode.js": "^2.3.1",
|
||||
"uc.micro": "^2.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"markdown-it": "bin/markdown-it.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-it/node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||
},
|
||||
"node_modules/markdown-it/node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-table": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
|
||||
@ -15044,6 +15118,11 @@
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz",
|
||||
"integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA=="
|
||||
},
|
||||
"node_modules/mdurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
@ -18316,6 +18395,14 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode.js": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
|
||||
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/q": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
|
||||
@ -18609,6 +18696,67 @@
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-markdown-it": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-markdown-it/-/react-markdown-it-1.0.2.tgz",
|
||||
"integrity": "sha512-Bzo/9UCCxlL2D7rYiVlxEqiOU66mqmLTzjxN0JLlioEhZhp7amzSq1YNS0+Jf0YKQmpBb5rfI9nh5s3wBsKnww==",
|
||||
"dependencies": {
|
||||
"markdown-it": "^4.4.0",
|
||||
"strip-indent": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-markdown-it/node_modules/entities": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
|
||||
"integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="
|
||||
},
|
||||
"node_modules/react-markdown-it/node_modules/linkify-it": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-1.2.4.tgz",
|
||||
"integrity": "sha512-eGHwtlABkp1NOJSiKUNqBf3SYAS5jPHtvRXPAgNaQwTqmkTahjtiLH9NtxdR5IOPhNvwNMN/diswSfZKzUkhGg==",
|
||||
"dependencies": {
|
||||
"uc.micro": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-markdown-it/node_modules/markdown-it": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-4.4.0.tgz",
|
||||
"integrity": "sha512-Rl8dHHeLuAh3E72OPY0tY7CLvlxgHiLhlshIYswAAabAg4YDBLa6e/LTgNkkxBO2K61ESzoquPQFMw/iMrT1PA==",
|
||||
"dependencies": {
|
||||
"argparse": "~1.0.2",
|
||||
"entities": "~1.1.1",
|
||||
"linkify-it": "~1.2.0",
|
||||
"mdurl": "~1.0.0",
|
||||
"uc.micro": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"markdown-it": "bin/markdown-it.js"
|
||||
}
|
||||
},
|
||||
"node_modules/react-markdown-it/node_modules/mdurl": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz",
|
||||
"integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g=="
|
||||
},
|
||||
"node_modules/react-markdown-it/node_modules/strip-indent": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
|
||||
"integrity": "sha512-I5iQq6aFMM62fBEAIB/hXzwJD6EEZ0xEGCX2t7oXqaKPIRgt4WruAQ285BISgdkP+HLGWyeGmNJcpIwFeRYRUA==",
|
||||
"dependencies": {
|
||||
"get-stdin": "^4.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"strip-indent": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-markdown-it/node_modules/uc.micro": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
|
||||
"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
|
||||
},
|
||||
"node_modules/react-plotly.js": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/react-plotly.js/-/react-plotly.js-2.6.0.tgz",
|
||||
@ -18629,6 +18777,50 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.0.tgz",
|
||||
"integrity": "sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ==",
|
||||
"dependencies": {
|
||||
"cookie": "^1.0.1",
|
||||
"set-cookie-parser": "^2.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.0.tgz",
|
||||
"integrity": "sha512-DYgm6RDEuKdopSyGOWZGtDfSm7Aofb8CCzgkliTjtu/eDuB0gcsv6qdFhhi8HdtmA+KHkt5MfZ5K2PdzjugYsA==",
|
||||
"dependencies": {
|
||||
"react-router": "7.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router/node_modules/cookie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
|
||||
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-scripts": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
|
||||
@ -19781,6 +19973,11 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
@ -21572,6 +21769,11 @@
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/uc.micro": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
|
||||
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="
|
||||
},
|
||||
"node_modules/ufo": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz",
|
||||
|
@ -20,13 +20,16 @@
|
||||
"@types/react-dom": "^19.0.4",
|
||||
"@uiw/react-json-view": "^2.0.0-alpha.31",
|
||||
"jsonrepair": "^3.12.0",
|
||||
"markdown-it": "^14.1.0",
|
||||
"mermaid": "^11.6.0",
|
||||
"mui-markdown": "^2.0.1",
|
||||
"prism-react-renderer": "^2.4.1",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-markdown-it": "^1.0.2",
|
||||
"react-plotly.js": "^2.6.0",
|
||||
"react-router-dom": "^7.6.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"react-spinners": "^0.15.0",
|
||||
"rehype-katex": "^7.0.1",
|
||||
@ -60,6 +63,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@craco/craco": "^7.1.0",
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@types/plotly.js": "^2.35.5"
|
||||
}
|
||||
}
|
||||
|
@ -19,10 +19,16 @@
|
||||
|
||||
## Some questions I've been asked
|
||||
|
||||
Q. <ChatQuery prompt="Why aren't you providing this as a Platform As a Service (PaaS) application?" tunables={{ "enable_tools": false }} />
|
||||
Q. <ChatQuery query={
|
||||
prompt: "Why aren't you providing this as a Platform As a Service (PaaS) application?",
|
||||
tunables: { "enable_tools": false }
|
||||
} />
|
||||
|
||||
A. I could; but I don't want to store your data. I also don't want to have to be on the hook for support of this service. I like it, it's fun, but it's not what I want as my day-gig, you know? If it was, I wouldn't be looking for a job...
|
||||
|
||||
Q. <ChatQuery prompt="Why can't I just ask Backstory these questions?" tunables={{ "enable_tools": false }} />
|
||||
Q. <ChatQuery query={
|
||||
prompt: "Why can't I just ask Backstory these questions?",
|
||||
tunables: { "enable_tools": false }
|
||||
} />
|
||||
|
||||
A. Try it. See what you find out :)
|
||||
|
@ -4,13 +4,12 @@ The system follows a carefully designed pipeline with isolated stages to prevent
|
||||
|
||||
The system uses a pipeline of isolated analysis and generation steps:
|
||||
|
||||
1. **Stage 1: Isolated Analysis** (three sub-stages)
|
||||
1. **Stage 1: Isolated Analysis**
|
||||
- **1A: Job Analysis** - Extracts requirements from job description only
|
||||
- **1B: Candidate Analysis** - Catalogs qualifications from resume/context only
|
||||
- **1C: Mapping Analysis** - Identifies legitimate matches between requirements and qualifications
|
||||
- **1B: Skill-Based Assessment** - For each required skill, determine a Individisual Skill Assessment, adding it to a Skill Assessments Collection.
|
||||
|
||||
2. **Stage 2: Resume Generation**
|
||||
- Uses mapping output to create a tailored resume with evidence-based content
|
||||
- Uses Skills Asessments Collection to generate a tailored resume.
|
||||
|
||||
3. **Stage 3: Verification**
|
||||
- Performs fact-checking to catch any remaining fabrications
|
||||
@ -23,63 +22,62 @@ flowchart TD
|
||||
A2 --> A3[Job Requirements JSON]
|
||||
end
|
||||
|
||||
subgraph "Stage 1B: Candidate Analysis"
|
||||
B1[Resume Input] --> B5[Candidate Analysis LLM]
|
||||
B5 --> B4[Candidate Qualifications JSON]
|
||||
B2[Candidate Info] --> B3[RAG]
|
||||
B3[RAG] --> B2[Candidate Info]
|
||||
A3[Job Requirements JSON] --> B3[RAG]
|
||||
B3[RAG] --> B5
|
||||
end
|
||||
|
||||
subgraph "Stage 1C: Mapping Analysis"
|
||||
C1[Job Requirements JSON] --> C3[Mapping Analysis LLM]
|
||||
C2[Candidate Qualifications JSON] --> C3
|
||||
C3 --> C4[Skills Mapping JSON]
|
||||
subgraph "Stage 1B: Skill-Based Assessment"
|
||||
B1[Resume Input] --> B2[Candidate Info]
|
||||
B2 --> B3[RAG System]
|
||||
A3 --> B4[Skill Assessment Generator]
|
||||
B3 --> B4
|
||||
B4 --> B5{For Each Required Skill}
|
||||
B5 --> B6[Skill-Focused LLM Query]
|
||||
B6 --> B7[Individual Skill Assessment]
|
||||
B7 --> B8[Skill Assessments Collection]
|
||||
end
|
||||
end
|
||||
|
||||
subgraph "Stage 2: Resume Generation"
|
||||
D1[Skills Mapping JSON] --> D3[Resume Generation LLM]
|
||||
D2[Original Resume Reference] --> D3
|
||||
D3 --> D4[Tailored Resume Draft]
|
||||
C1[Skill Assessments Collection] --> C2[Resume Generator]
|
||||
C3[Original Resume Reference] --> C2
|
||||
C4[Candidate Information] --> C2
|
||||
C2 --> C5[Resume Generation Prompt]
|
||||
C5 --> C6[Resume Generation LLM]
|
||||
C6 --> C7[Tailored Resume Draft]
|
||||
end
|
||||
|
||||
subgraph "Stage 3: Verification"
|
||||
E1[Skills Mapping JSON] --> E2[Original Materials]
|
||||
E2 --> E3[Tailored Resume Draft]
|
||||
E3 --> E4[Verification LLM]
|
||||
E4 --> E5{Verification Check}
|
||||
E5 -->|PASS| E6[Approved Resume]
|
||||
E5 -->|FAIL| E7[Correction Instructions]
|
||||
E7 --> D3
|
||||
subgraph "Stage 3: Statistics & Verification"
|
||||
D1[Job Requirements JSON] --> D2[Match Statistics Calculator]
|
||||
D3[Skill Assessments Collection] --> D2
|
||||
D2 --> D4[Match Statistics]
|
||||
D4 --> D5[Verification LLM]
|
||||
C7 --> D5
|
||||
D5 --> D6{Verification Check}
|
||||
D6 -->|PASS| D7[Approved Resume]
|
||||
D6 -->|FAIL| D8[Correction Instructions]
|
||||
D8 --> C2
|
||||
end
|
||||
|
||||
A3 --> C1
|
||||
B4 --> C2
|
||||
C4 --> D1
|
||||
C4 --> E1
|
||||
D4 --> E3
|
||||
A3 --> B4
|
||||
B8 --> C1
|
||||
B8 --> D3
|
||||
B1 --> C3
|
||||
|
||||
style A2 fill:#f9d77e,stroke:#333,stroke-width:2px
|
||||
style B5 fill:#f9d77e,stroke:#333,stroke-width:2px
|
||||
style C3 fill:#f9d77e,stroke:#333,stroke-width:2px
|
||||
style D3 fill:#f9d77e,stroke:#333,stroke-width:2px
|
||||
style E4 fill:#f9d77e,stroke:#333,stroke-width:2px
|
||||
style E5 fill:#a3e4d7,stroke:#333,stroke-width:2px
|
||||
style E6 fill:#aed6f1,stroke:#333,stroke-width:2px
|
||||
style E7 fill:#f5b7b1,stroke:#333,stroke-width:2px
|
||||
style B6 fill:#f9d77e,stroke:#333,stroke-width:2px
|
||||
style C6 fill:#f9d77e,stroke:#333,stroke-width:2px
|
||||
style D5 fill:#f9d77e,stroke:#333,stroke-width:2px
|
||||
style B5 fill:#a3e4d7,stroke:#333,stroke-width:2px
|
||||
style D6 fill:#a3e4d7,stroke:#333,stroke-width:2px
|
||||
style D7 fill:#aed6f1,stroke:#333,stroke-width:2px
|
||||
style D8 fill:#f5b7b1,stroke:#333,stroke-width:2px
|
||||
```
|
||||
|
||||
## Stage 1: Isolated Analysis (three separate sub-stages)
|
||||
## Stage 1: Isolated Analysis
|
||||
|
||||
1. **Job Analysis**: Extracts requirements from just the job description
|
||||
2. **Candidate Analysis**: Catalogs qualifications from just the resume/context
|
||||
3. **Mapping Analysis**: Identifies legitimate matches between requirements and qualifications
|
||||
2. **Candidate Analysis**: Catalogs qualifications for each job requirement from just the resume/context
|
||||
|
||||
## Stage 2: Resume Generation
|
||||
|
||||
Creates a tailored resume using only verified information from the mapping
|
||||
Creates a tailored resume using the skills collection and candidate information.
|
||||
|
||||
## Stage 3: Verification
|
||||
|
||||
@ -90,7 +88,7 @@ Creates a tailored resume using only verified information from the mapping
|
||||
|
||||
The system uses several techniques to prevent fabrication:
|
||||
|
||||
* **Isolation of Analysis Stages**: By analyzing the job and candidate separately, the system prevents the LLM from prematurely creating connections that might lead to fabrication.
|
||||
* **Isolation of Analysis Stages**: By analyzing the job and candidate separately, and having the LLM only provide evidence of a single skill per pass, the system prevents the LLM from prematurely creating connections that might lead to fabrication.
|
||||
* **Evidence Requirements**: Each qualification included must have explicit evidence from the original materials.
|
||||
* **Conservative Transferability**: The system is instructed to be conservative when claiming skills are transferable.
|
||||
* **Verification Layer**: A dedicated verification step acts as a safety check to catch any remaining fabrications.
|
||||
|
@ -6,32 +6,54 @@ import { Document } from './Document';
|
||||
|
||||
const AboutPage = (props: BackstoryPageProps) => {
|
||||
const { sessionId, submitQuery, setSnack, route, setRoute } = props;
|
||||
const [ page, setPage ] = useState<string>("");
|
||||
const [ subRoute, setSubRoute] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
console.log(`AboutPage: ${page} - route - ${route} - subRoute: ${subRoute}`);
|
||||
}, [page, route, subRoute]);
|
||||
const [subRoute, setSubRoute] = useState<string>("");
|
||||
const [page, setPage] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
if (route === undefined) { return; }
|
||||
const parts = route.split("/");
|
||||
if (parts.length === 0) { return; }
|
||||
setPage(parts[0]);
|
||||
if (parts.length > 1) {
|
||||
parts.shift();
|
||||
setSubRoute(parts.join("/"));
|
||||
parts.shift();
|
||||
setSubRoute(parts.join("/"));
|
||||
}, [route]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(`AboutPage: ${page} - sub-route - ${subRoute}`);
|
||||
}, [page, subRoute]);
|
||||
|
||||
useEffect(() => {
|
||||
if (route) {
|
||||
const parts = route.split("/");
|
||||
if (parts[0] !== page) {
|
||||
parts.shift();
|
||||
const incomingSubRoute = parts.join("/");
|
||||
if (incomingSubRoute !== subRoute) {
|
||||
setSubRoute(incomingSubRoute);
|
||||
}
|
||||
}
|
||||
} else if (subRoute) {
|
||||
setRoute && setRoute(subRoute);
|
||||
}
|
||||
}, [route, setPage, setSubRoute]);
|
||||
}, [page, route, setRoute, subRoute]);
|
||||
|
||||
useEffect(() => {
|
||||
let newRoute = page;
|
||||
if (subRoute) {
|
||||
newRoute += '/' + subRoute;
|
||||
}
|
||||
if (route !== newRoute && setRoute) {
|
||||
setRoute(newRoute);
|
||||
}
|
||||
}, [route, page, subRoute, setRoute]);
|
||||
|
||||
const onDocumentExpand = (document: string, open: boolean) => {
|
||||
console.log("Document expanded:", document, open);
|
||||
if (open) {
|
||||
setSubRoute("");
|
||||
setPage(document);
|
||||
if (setRoute) setRoute(document);
|
||||
} else {
|
||||
setSubRoute("");
|
||||
setPage("");
|
||||
if (setRoute) setRoute("");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,402 +1,40 @@
|
||||
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||
import Card from '@mui/material/Card';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
import Tabs from '@mui/material/Tabs';
|
||||
import Tab from '@mui/material/Tab';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Drawer from '@mui/material/Drawer';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Box from '@mui/material/Box';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
|
||||
import React, { useRef, useCallback } from 'react';
|
||||
import { BrowserRouter as Router, Routes, Route, useLocation } from "react-router-dom";
|
||||
import { SessionWrapper } from "./SessionWrapper";
|
||||
import { Main } from "./Main";
|
||||
import { Snack, SeverityType } from './Snack';
|
||||
import { ConversationHandle } from './Conversation';
|
||||
import { QueryOptions } from './ChatQuery';
|
||||
import { Scrollable } from './Scrollable';
|
||||
import { BackstoryPage, BackstoryTabProps } from './BackstoryTab';
|
||||
|
||||
import { connectionBase } from './Global';
|
||||
export function PathRouter({ setSnack }: { setSnack: any }) {
|
||||
const location = useLocation();
|
||||
const segments = location.pathname.split("/").filter(Boolean);
|
||||
const sessionId = segments[segments.length - 1];
|
||||
|
||||
import { HomePage } from './HomePage';
|
||||
import { ResumeBuilderPage } from './ResumeBuilderPage';
|
||||
import { VectorVisualizerPage } from './VectorVisualizer';
|
||||
import { AboutPage } from './AboutPage';
|
||||
import { ControlsPage } from './ControlsPage';
|
||||
|
||||
|
||||
import './App.css';
|
||||
import './Conversation.css';
|
||||
|
||||
import '@fontsource/roboto/300.css';
|
||||
import '@fontsource/roboto/400.css';
|
||||
import '@fontsource/roboto/500.css';
|
||||
import '@fontsource/roboto/700.css';
|
||||
|
||||
|
||||
|
||||
const isValidUUIDv4 = (str: string): boolean => {
|
||||
const pattern = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89ab][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/i;
|
||||
return pattern.test(str);
|
||||
return (
|
||||
<SessionWrapper setSnack={setSnack}>
|
||||
<Main setSnack={setSnack} sessionId={sessionId} />
|
||||
</SessionWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const App = () => {
|
||||
const [sessionId, setSessionId] = useState<string | undefined>(undefined);
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const [isMenuClosing, setIsMenuClosing] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState<number>(0);
|
||||
const isDesktop = useMediaQuery('(min-width:650px)');
|
||||
const prevIsDesktopRef = useRef<boolean>(isDesktop);
|
||||
const chatRef = useRef<ConversationHandle>(null);
|
||||
const snackRef = useRef<any>(null);
|
||||
const [subRoute, setSubRoute] = useState<string>("");
|
||||
function App() {
|
||||
const snackRef = useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (prevIsDesktopRef.current === isDesktop)
|
||||
return;
|
||||
|
||||
if (menuOpen) {
|
||||
setMenuOpen(false);
|
||||
}
|
||||
|
||||
prevIsDesktopRef.current = isDesktop;
|
||||
}, [isDesktop, setMenuOpen, menuOpen])
|
||||
const setSnack = useCallback((message: string, severity?: SeverityType) => {
|
||||
snackRef.current?.setSnack(message, severity);
|
||||
}, [snackRef]);
|
||||
|
||||
const setSnack = useCallback((message: string, severity?: SeverityType) => {
|
||||
snackRef.current?.setSnack(message, severity);
|
||||
}, [snackRef]);
|
||||
return (
|
||||
<>
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="*" element={<PathRouter setSnack={setSnack} />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
<Snack
|
||||
ref={snackRef}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const handleSubmitChatQuery = (prompt: string, tunables?: QueryOptions) => {
|
||||
console.log(`handleSubmitChatQuery: ${prompt} ${tunables || {}} -- `, chatRef.current ? ' sending' : 'no handler');
|
||||
chatRef.current?.submitQuery(prompt, tunables);
|
||||
setActiveTab(0);
|
||||
};
|
||||
|
||||
const tabs: BackstoryTabProps[] = useMemo(() => {
|
||||
const homeTab: BackstoryTabProps = {
|
||||
label: "",
|
||||
path: "",
|
||||
tabProps: {
|
||||
label: "Backstory",
|
||||
sx: { flexGrow: 1, fontSize: '1rem' },
|
||||
icon:
|
||||
<Avatar sx={{
|
||||
width: 24,
|
||||
height: 24
|
||||
}}
|
||||
variant="rounded"
|
||||
alt="Backstory logo"
|
||||
src="/logo192.png" />,
|
||||
iconPosition: "start"
|
||||
},
|
||||
children: <HomePage ref={chatRef} {...{ sessionId, setSnack, submitQuery: handleSubmitChatQuery, route: subRoute, setRoute: setSubRoute }} />
|
||||
};
|
||||
|
||||
const resumeBuilderTab: BackstoryTabProps = {
|
||||
label: "Resume Builder",
|
||||
path: "resume-builder",
|
||||
children: <ResumeBuilderPage {...{ sessionId, setSnack, submitQuery: handleSubmitChatQuery, route: subRoute, setRoute: setSubRoute }} />
|
||||
};
|
||||
|
||||
const contextVisualizerTab: BackstoryTabProps = {
|
||||
label: "Context Visualizer",
|
||||
path: "context-visualizer",
|
||||
children: <VectorVisualizerPage sx={{ p: 1 }} {...{ sessionId, setSnack, submitQuery: handleSubmitChatQuery, route: subRoute, setRoute: setSubRoute }} />
|
||||
};
|
||||
|
||||
const aboutTab = {
|
||||
label: "About",
|
||||
path: "about",
|
||||
children: <AboutPage {...{ sessionId, setSnack, submitQuery: handleSubmitChatQuery, route: subRoute, setRoute: setSubRoute }} />
|
||||
};
|
||||
|
||||
const controlsTab: BackstoryTabProps = {
|
||||
path: "controls",
|
||||
tabProps: {
|
||||
sx: { flexShrink: 1, flexGrow: 0, fontSize: '1rem' },
|
||||
icon: <SettingsIcon />
|
||||
},
|
||||
children: (
|
||||
<Scrollable
|
||||
autoscroll={false}
|
||||
sx={{
|
||||
maxWidth: "1024px",
|
||||
height: "calc(100vh - 72px)",
|
||||
flexDirection: "column",
|
||||
margin: "0 auto",
|
||||
p: 1,
|
||||
}}
|
||||
>
|
||||
{sessionId !== undefined &&
|
||||
<ControlsPage {...{ sessionId, setSnack, submitQuery: handleSubmitChatQuery, route: subRoute, setRoute: setSubRoute }} />
|
||||
}
|
||||
</Scrollable>
|
||||
)
|
||||
};
|
||||
|
||||
return [
|
||||
homeTab,
|
||||
resumeBuilderTab,
|
||||
contextVisualizerTab,
|
||||
aboutTab,
|
||||
controlsTab,
|
||||
];
|
||||
}, [sessionId, setSnack, subRoute]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (sessionId === undefined || activeTab > tabs.length - 1) { return; }
|
||||
console.log(`route - '${tabs[activeTab].path}', subRoute - '${subRoute}'`);
|
||||
|
||||
let path = tabs[activeTab].path ? `/${tabs[activeTab].path}` : '';
|
||||
if (subRoute) {
|
||||
path += `/${subRoute}`;
|
||||
}
|
||||
path += `/${sessionId}`;
|
||||
console.log('pushState: ', path);
|
||||
// window.history.pushState({}, '', path);
|
||||
}, [activeTab, sessionId, subRoute, tabs]);
|
||||
|
||||
const fetchSession = useCallback((async (pathParts?: string[]) => {
|
||||
try {
|
||||
const response = await fetch(connectionBase + `/api/context`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw Error("Server is temporarily down.");
|
||||
}
|
||||
const new_session = (await response.json()).id;
|
||||
console.log(`Session created: ${new_session}`);
|
||||
|
||||
if (pathParts === undefined) {
|
||||
setSessionId(new_session);
|
||||
const newPath = `/${new_session}`;
|
||||
window.history.replaceState({}, '', newPath);
|
||||
} else {
|
||||
const currentPath = pathParts.length < 2 ? '' : pathParts[0];
|
||||
let tabIndex = tabs.findIndex((tab) => tab.path === currentPath);
|
||||
if (-1 === tabIndex) {
|
||||
console.log(`Invalid path "${currentPath}" -- redirecting to default`);
|
||||
window.history.replaceState({}, '', `/${new_session}`);
|
||||
setActiveTab(0);
|
||||
} else {
|
||||
window.history.replaceState({}, '', `/${pathParts.join('/')}/${new_session}`);
|
||||
// tabs[tabIndex].route = pathParts[2] || "";
|
||||
setActiveTab(tabIndex);
|
||||
}
|
||||
setSessionId(new_session);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
setSnack("Server is temporarily down", "error");
|
||||
}
|
||||
}), [setSnack, tabs]);
|
||||
|
||||
useEffect(() => {
|
||||
const url = new URL(window.location.href);
|
||||
const pathParts = url.pathname.split('/').filter(Boolean); // [path, sessionId]
|
||||
|
||||
if (pathParts.length < 1) {
|
||||
console.log("No session id or path -- creating new session");
|
||||
fetchSession();
|
||||
} else {
|
||||
const currentPath = pathParts.length < 2 ? '' : pathParts[0];
|
||||
const path_session = pathParts.length < 2 ? pathParts[0] : pathParts[1];
|
||||
if (!isValidUUIDv4(path_session)) {
|
||||
console.log(`Invalid session id ${path_session}-- creating new session`);
|
||||
fetchSession();
|
||||
} else {
|
||||
let tabIndex = tabs.findIndex((tab) => tab.path === currentPath);
|
||||
if (-1 === tabIndex) {
|
||||
console.log(`Invalid path "${currentPath}" -- redirecting to default`);
|
||||
tabIndex = 0;
|
||||
}
|
||||
// tabs[tabIndex].route = pathParts[2] || ""
|
||||
setSessionId(path_session);
|
||||
setActiveTab(tabIndex);
|
||||
}
|
||||
}
|
||||
}, [setSessionId, setSnack, tabs, fetchSession]);
|
||||
|
||||
const handleMenuClose = () => {
|
||||
setIsMenuClosing(true);
|
||||
setMenuOpen(false);
|
||||
};
|
||||
|
||||
const handleMenuTransitionEnd = () => {
|
||||
setIsMenuClosing(false);
|
||||
};
|
||||
|
||||
const handleMenuToggle = () => {
|
||||
if (!isMenuClosing) {
|
||||
setMenuOpen(!menuOpen);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||
if (newValue > tabs.length) {
|
||||
return;
|
||||
}
|
||||
setActiveTab(newValue);
|
||||
const tabPath = tabs[newValue].path;
|
||||
let path = `/${sessionId}`;
|
||||
if (tabPath) {
|
||||
// if (openDocument) {
|
||||
// path = `/${tabPath}/${openDocument}/${sessionId}`;
|
||||
// } else {
|
||||
path = `/${tabPath}/${sessionId}`;
|
||||
// }
|
||||
}
|
||||
window.history.pushState({}, '', path);
|
||||
handleMenuClose();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handlePopState = () => {
|
||||
const url = new URL(window.location.href);
|
||||
const pathParts = url.pathname.split('/').filter(Boolean);
|
||||
const currentPath = pathParts.length < 2 ? '' : pathParts[0];
|
||||
const session = pathParts.length < 2 ? pathParts[0] : pathParts[1];
|
||||
|
||||
let tabIndex = tabs.findIndex((tab) => tab.path === currentPath);
|
||||
if (-1 === tabIndex) {
|
||||
console.log(`Invalid path "${currentPath}" -- redirecting to default`);
|
||||
tabIndex = 0;
|
||||
}
|
||||
setSessionId(session);
|
||||
setActiveTab(tabIndex);
|
||||
};
|
||||
|
||||
window.addEventListener('popstate', handlePopState);
|
||||
return () => window.removeEventListener('popstate', handlePopState);
|
||||
}, [setSessionId, tabs]);
|
||||
|
||||
/* toolbar height is 64px + 8px margin-top */
|
||||
const Offset = styled('div')(() => ({ minHeight: '72px', height: '72px' }));
|
||||
|
||||
return (
|
||||
<Box className="App"
|
||||
sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<CssBaseline />
|
||||
<AppBar
|
||||
position="fixed"
|
||||
sx={{
|
||||
zIndex: (theme) => theme.zIndex.drawer + 1,
|
||||
maxWidth: "100vw"
|
||||
}}
|
||||
>
|
||||
<Toolbar>
|
||||
<Box sx={{ display: "flex", flexGrow: 1, flexDirection: "row" }}>
|
||||
{!isDesktop &&
|
||||
<Box sx={{ display: "flex", flexGrow: 1, flexDirection: "row" }}>
|
||||
<IconButton
|
||||
sx={{ display: "flex", margin: 'auto 0px' }}
|
||||
size="large"
|
||||
edge="start"
|
||||
color="inherit"
|
||||
onClick={handleMenuToggle}
|
||||
>
|
||||
<Tooltip title="Navigation">
|
||||
<MenuIcon />
|
||||
</Tooltip>
|
||||
</IconButton>
|
||||
<Tooltip title="Backstory">
|
||||
<Box
|
||||
sx={{ m: 1, gap: 1, display: "flex", flexDirection: "row", alignItems: "center", fontWeight: "bold", fontSize: "1.0rem", cursor: "pointer" }}
|
||||
onClick={() => { setActiveTab(0); setMenuOpen(false); }}
|
||||
>
|
||||
<Avatar sx={{
|
||||
width: 24,
|
||||
height: 24
|
||||
}}
|
||||
variant="rounded"
|
||||
alt="Backstory logo"
|
||||
src="/logo192.png" />
|
||||
BACKSTORY
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
}
|
||||
|
||||
{menuOpen === false && isDesktop &&
|
||||
<Tabs sx={{ display: "flex", flexGrow: 1 }}
|
||||
value={activeTab}
|
||||
indicatorColor="secondary"
|
||||
textColor="inherit"
|
||||
variant="fullWidth"
|
||||
allowScrollButtonsMobile
|
||||
onChange={handleTabChange}
|
||||
aria-label="Backstory navigation">
|
||||
{tabs.map((tab, index) => <Tab key={index} value={index} label={tab.label} {...tab.tabProps} />)}
|
||||
</Tabs>
|
||||
}
|
||||
</Box>
|
||||
</Toolbar>
|
||||
|
||||
</AppBar>
|
||||
|
||||
<Offset />
|
||||
|
||||
<Box
|
||||
sx={{ display: "flex", flexGrow: 1, flexDirection: "column" }}
|
||||
>
|
||||
<Box
|
||||
component="nav"
|
||||
aria-label="mailbox folders"
|
||||
>
|
||||
<Drawer
|
||||
container={window.document.body}
|
||||
variant="temporary"
|
||||
open={menuOpen}
|
||||
onTransitionEnd={handleMenuTransitionEnd}
|
||||
onClose={handleMenuClose}
|
||||
sx={{
|
||||
display: 'block',
|
||||
'& .MuiDrawer-paper': { boxSizing: 'border-box' },
|
||||
}}
|
||||
slotProps={{
|
||||
root: {
|
||||
keepMounted: true, // Better open performance on mobile.
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Toolbar />
|
||||
<Card className="MenuCard">
|
||||
<Tabs sx={{ display: "flex", flexGrow: 1 }}
|
||||
orientation="vertical"
|
||||
value={activeTab}
|
||||
indicatorColor="secondary"
|
||||
textColor="inherit"
|
||||
variant="scrollable"
|
||||
allowScrollButtonsMobile
|
||||
onChange={handleTabChange}
|
||||
aria-label="Backstory navigation">
|
||||
{tabs.map((tab, index) => <Tab key={index} value={index} label={tab.label} {...tab.tabProps} />)}
|
||||
</Tabs>
|
||||
</Card>
|
||||
</Drawer>
|
||||
</Box>
|
||||
{
|
||||
tabs.map((tab: any, i: number) =>
|
||||
<BackstoryPage key={i} active={i === activeTab} path={tab.path}>{tab.children}</BackstoryPage>
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
|
||||
<Snack
|
||||
ref={snackRef}
|
||||
/>
|
||||
</Box >
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
export default App;
|
||||
|
@ -5,7 +5,7 @@ import { ChatSubmitQueryInterface } from './ChatQuery';
|
||||
import { SetSnackType } from './Snack';
|
||||
|
||||
interface BackstoryElementProps {
|
||||
sessionId: string | undefined,
|
||||
sessionId: string,
|
||||
setSnack: SetSnackType,
|
||||
submitQuery: ChatSubmitQueryInterface,
|
||||
sx?: SxProps<Theme>,
|
||||
|
@ -30,7 +30,6 @@ const BackstoryTextField = React.forwardRef<BackstoryTextFieldRef, BackstoryText
|
||||
const shadowRef = useRef<HTMLTextAreaElement>(null);
|
||||
const [editValue, setEditValue] = useState<string>(value);
|
||||
|
||||
console.log({ value, placeholder, editValue });
|
||||
// Sync editValue with prop value if it changes externally
|
||||
useEffect(() => {
|
||||
setEditValue(value || "");
|
||||
|
@ -158,10 +158,9 @@ function ChatBubble(props: ChatBubbleProps) {
|
||||
};
|
||||
|
||||
// Render Accordion for expandable content
|
||||
if (expandable || (role === 'content' && title)) {
|
||||
if (expandable || title) {
|
||||
// Determine if Accordion is controlled
|
||||
const isControlled = typeof expanded === 'boolean' && typeof onExpand === 'function';
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
expanded={isControlled ? expanded : undefined} // Omit expanded prop for uncontrolled
|
||||
|
@ -1,30 +1,32 @@
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
|
||||
type QueryOptions = {
|
||||
/* backstory/src/utils/message.py */
|
||||
type Tunables = {
|
||||
enable_rag?: boolean,
|
||||
enable_tools?: boolean,
|
||||
enable_context?: boolean,
|
||||
};
|
||||
|
||||
type ChatSubmitQueryInterface = (prompt: string, tunables?: QueryOptions) => void;
|
||||
/* backstory/src/server.py */
|
||||
type Query = {
|
||||
prompt: string,
|
||||
tunables?: Tunables,
|
||||
agent_options?: {},
|
||||
};
|
||||
|
||||
type ChatSubmitQueryInterface = (query: Query) => void;
|
||||
|
||||
interface ChatQueryInterface {
|
||||
prompt: string,
|
||||
tunables?: QueryOptions,
|
||||
query: Query,
|
||||
submitQuery?: ChatSubmitQueryInterface
|
||||
}
|
||||
|
||||
const ChatQuery = (props : ChatQueryInterface) => {
|
||||
const { prompt, submitQuery } = props;
|
||||
let tunables = props.tunables;
|
||||
|
||||
if (typeof (tunables) === "string") {
|
||||
tunables = JSON.parse(tunables);
|
||||
}
|
||||
const { query, submitQuery } = props;
|
||||
|
||||
if (submitQuery === undefined) {
|
||||
return (<Box>{prompt}</Box>);
|
||||
return (<Box>{query.prompt}</Box>);
|
||||
}
|
||||
return (
|
||||
<Button variant="outlined" sx={{
|
||||
@ -32,15 +34,15 @@ const ChatQuery = (props : ChatQueryInterface) => {
|
||||
borderColor: theme => theme.palette.custom.highlight,
|
||||
m: 1
|
||||
}}
|
||||
size="small" onClick={(e: any) => { submitQuery(prompt, tunables); }}>
|
||||
{prompt}
|
||||
size="small" onClick={(e: any) => { submitQuery(query); }}>
|
||||
{query.prompt}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export type {
|
||||
ChatQueryInterface,
|
||||
QueryOptions,
|
||||
Query,
|
||||
ChatSubmitQueryInterface,
|
||||
};
|
||||
|
||||
|
@ -86,7 +86,8 @@ const SystemInfoComponent: React.FC<{ systemInfo: SystemInfo | undefined }> = ({
|
||||
return <div className="SystemInfo">{systemElements}</div>;
|
||||
};
|
||||
|
||||
const ControlsPage = ({ sessionId, setSnack }: BackstoryPageProps) => {
|
||||
const ControlsPage = (props: BackstoryPageProps) => {
|
||||
const { setSnack, sessionId } = props;
|
||||
const [editSystemPrompt, setEditSystemPrompt] = useState<string>("");
|
||||
const [systemInfo, setSystemInfo] = useState<SystemInfo | undefined>(undefined);
|
||||
const [tools, setTools] = useState<Tool[]>([]);
|
||||
@ -281,7 +282,7 @@ const ControlsPage = ({ sessionId, setSnack }: BackstoryPageProps) => {
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
console.log("Server tunables: ", data);
|
||||
// console.log("Server tunables: ", data);
|
||||
setServerTunables(data);
|
||||
setSystemPrompt(data["system_prompt"]);
|
||||
setMessageHistoryLength(data["message_history_length"]);
|
||||
|
@ -1,48 +1,32 @@
|
||||
import React, { useState, useImperativeHandle, forwardRef, useEffect, useRef, useCallback } from 'react';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Button from '@mui/material/Button';
|
||||
import Box from '@mui/material/Box';
|
||||
import SendIcon from '@mui/icons-material/Send';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import { SxProps, Theme } from '@mui/material';
|
||||
import PropagateLoader from "react-spinners/PropagateLoader";
|
||||
|
||||
import { Message, MessageList, MessageData } from './Message';
|
||||
import { Message, MessageList, BackstoryMessage } from './Message';
|
||||
import { ContextStatus } from './ContextStatus';
|
||||
import { Scrollable } from './Scrollable';
|
||||
import { DeleteConfirmation } from './DeleteConfirmation';
|
||||
import { QueryOptions } from './ChatQuery';
|
||||
import { Query } from './ChatQuery';
|
||||
import './Conversation.css';
|
||||
import { BackstoryTextField, BackstoryTextFieldRef } from './BackstoryTextField';
|
||||
import { BackstoryElementProps } from './BackstoryTab';
|
||||
import { connectionBase } from './Global';
|
||||
|
||||
const loadingMessage: MessageData = { "role": "status", "content": "Establishing connection with server..." };
|
||||
const loadingMessage: BackstoryMessage = { "role": "status", "content": "Establishing connection with server..." };
|
||||
|
||||
type ConversationMode = 'chat' | 'job_description' | 'resume' | 'fact_check';
|
||||
|
||||
interface ConversationHandle {
|
||||
submitQuery: (prompt: string, options?: QueryOptions) => void;
|
||||
submitQuery: (query: Query) => void;
|
||||
fetchHistory: () => void;
|
||||
}
|
||||
interface BackstoryMessage {
|
||||
prompt: string;
|
||||
preamble: {};
|
||||
status: string;
|
||||
full_content: string;
|
||||
response: string; // Set when status === 'done' or 'error'
|
||||
chunk: string; // Used when status === 'streaming'
|
||||
metadata: {
|
||||
rag: { documents: [] };
|
||||
tools: string[];
|
||||
eval_count: number;
|
||||
eval_duration: number;
|
||||
prompt_eval_count: number;
|
||||
prompt_eval_duration: number;
|
||||
};
|
||||
actions: string[];
|
||||
timestamp: string;
|
||||
};
|
||||
|
||||
interface ConversationProps extends BackstoryElementProps {
|
||||
className?: string, // Override default className
|
||||
@ -59,36 +43,37 @@ interface ConversationProps extends BackstoryElementProps {
|
||||
messageFilter?: ((messages: MessageList) => MessageList) | undefined, // Filter callback to determine which Messages to display in Conversation
|
||||
messages?: MessageList, //
|
||||
sx?: SxProps<Theme>,
|
||||
onResponse?: ((message: MessageData) => void) | undefined, // Event called when a query completes (provides messages)
|
||||
onResponse?: ((message: BackstoryMessage) => void) | undefined, // Event called when a query completes (provides messages)
|
||||
};
|
||||
|
||||
const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
|
||||
actionLabel,
|
||||
className,
|
||||
defaultPrompts,
|
||||
defaultQuery,
|
||||
hideDefaultPrompts,
|
||||
hidePreamble,
|
||||
messageFilter,
|
||||
messages,
|
||||
onResponse,
|
||||
placeholder,
|
||||
preamble,
|
||||
resetAction,
|
||||
resetLabel,
|
||||
sessionId,
|
||||
setSnack,
|
||||
submitQuery,
|
||||
sx,
|
||||
type,
|
||||
}: ConversationProps, ref) => {
|
||||
const Conversation = forwardRef<ConversationHandle, ConversationProps>((props: ConversationProps, ref) => {
|
||||
const {
|
||||
sessionId,
|
||||
actionLabel,
|
||||
className,
|
||||
defaultPrompts,
|
||||
defaultQuery,
|
||||
hideDefaultPrompts,
|
||||
hidePreamble,
|
||||
messageFilter,
|
||||
messages,
|
||||
onResponse,
|
||||
placeholder,
|
||||
preamble,
|
||||
resetAction,
|
||||
resetLabel,
|
||||
setSnack,
|
||||
submitQuery,
|
||||
sx,
|
||||
type,
|
||||
} = props;
|
||||
const [contextUsedPercentage, setContextUsedPercentage] = useState<number>(0);
|
||||
const [processing, setProcessing] = useState<boolean>(false);
|
||||
const [countdown, setCountdown] = useState<number>(0);
|
||||
const [conversation, setConversation] = useState<MessageList>([]);
|
||||
const [filteredConversation, setFilteredConversation] = useState<MessageList>([]);
|
||||
const [processingMessage, setProcessingMessage] = useState<MessageData | undefined>(undefined);
|
||||
const [streamingMessage, setStreamingMessage] = useState<MessageData | undefined>(undefined);
|
||||
const [processingMessage, setProcessingMessage] = useState<BackstoryMessage | undefined>(undefined);
|
||||
const [streamingMessage, setStreamingMessage] = useState<BackstoryMessage | undefined>(undefined);
|
||||
const timerRef = useRef<any>(null);
|
||||
const [contextStatus, setContextStatus] = useState<ContextStatus>({ context_used: 0, max_context: 0 });
|
||||
const [contextWarningShown, setContextWarningShown] = useState<boolean>(false);
|
||||
@ -96,6 +81,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
|
||||
const conversationRef = useRef<MessageList>([]);
|
||||
const viewableElementRef = useRef<HTMLDivElement>(null);
|
||||
const backstoryTextRef = useRef<BackstoryTextFieldRef>(null);
|
||||
const stopRef = useRef(false);
|
||||
|
||||
// Keep the ref updated whenever items changes
|
||||
useEffect(() => {
|
||||
@ -181,14 +167,25 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
|
||||
|
||||
const backstoryMessages: BackstoryMessage[] = messages;
|
||||
|
||||
setConversation(backstoryMessages.flatMap((backstoryMessage: BackstoryMessage) => [{
|
||||
role: 'user',
|
||||
content: backstoryMessage.prompt || "",
|
||||
}, {
|
||||
...backstoryMessage,
|
||||
role: backstoryMessage.status === "done" ? "assistant" : backstoryMessage.status,
|
||||
setConversation(backstoryMessages.flatMap((backstoryMessage: BackstoryMessage) => {
|
||||
if (backstoryMessage.status === "partial") {
|
||||
return [{
|
||||
...backstoryMessage,
|
||||
role: "assistant",
|
||||
content: backstoryMessage.response || "",
|
||||
expanded: false,
|
||||
expandable: true,
|
||||
}]
|
||||
}
|
||||
return [{
|
||||
role: 'user',
|
||||
content: backstoryMessage.prompt || "",
|
||||
}, {
|
||||
...backstoryMessage,
|
||||
role: ['done'].includes(backstoryMessage.status || "") ? "assistant" : backstoryMessage.status,
|
||||
content: backstoryMessage.response || "",
|
||||
}] as MessageList));
|
||||
}] as MessageList;
|
||||
}));
|
||||
setNoInteractions(false);
|
||||
}
|
||||
setProcessingMessage(undefined);
|
||||
@ -242,12 +239,15 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
|
||||
};
|
||||
|
||||
const handleEnter = (value: string) => {
|
||||
sendQuery(value);
|
||||
const query: Query = {
|
||||
prompt: value
|
||||
}
|
||||
sendQuery(query);
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
submitQuery: (query: string, tunables?: QueryOptions) => {
|
||||
sendQuery(query, tunables);
|
||||
submitQuery: (query: Query) => {
|
||||
sendQuery(query);
|
||||
},
|
||||
fetchHistory: () => { return fetchHistory(); }
|
||||
}));
|
||||
@ -294,20 +294,27 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
|
||||
}
|
||||
};
|
||||
|
||||
const sendQuery = async (request: string, options?: QueryOptions) => {
|
||||
request = request.trim();
|
||||
const cancelQuery = () => {
|
||||
console.log("Stop query");
|
||||
stopRef.current = true;
|
||||
};
|
||||
|
||||
const sendQuery = async (query: Query) => {
|
||||
query.prompt = query.prompt.trim();
|
||||
|
||||
// If the request was empty, a default request was provided,
|
||||
// and there is no prompt for the user, send the default request.
|
||||
if (!request && defaultQuery && !prompt) {
|
||||
request = defaultQuery.trim();
|
||||
if (!query.prompt && defaultQuery && !prompt) {
|
||||
query.prompt = defaultQuery.trim();
|
||||
}
|
||||
|
||||
// Do not send an empty request.
|
||||
if (!request) {
|
||||
if (!query.prompt) {
|
||||
return;
|
||||
}
|
||||
|
||||
stopRef.current = false;
|
||||
|
||||
setNoInteractions(false);
|
||||
|
||||
setConversation([
|
||||
@ -315,7 +322,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
|
||||
{
|
||||
role: 'user',
|
||||
origin: type,
|
||||
content: request,
|
||||
content: query.prompt,
|
||||
disableCopy: true
|
||||
}
|
||||
]);
|
||||
@ -325,31 +332,16 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
|
||||
|
||||
try {
|
||||
setProcessing(true);
|
||||
// Create a unique ID for the processing message
|
||||
const processingId = Date.now().toString();
|
||||
|
||||
// Add initial processing message
|
||||
setProcessingMessage(
|
||||
{ role: 'status', content: 'Submitting request...', id: processingId, isProcessing: true }
|
||||
{ role: 'status', content: 'Submitting request...', disableCopy: true }
|
||||
);
|
||||
|
||||
// Add a small delay to ensure React has time to update the UI
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
|
||||
// Make the fetch request with proper headers
|
||||
let query;
|
||||
if (options) {
|
||||
query = {
|
||||
options: options,
|
||||
prompt: request.trim()
|
||||
}
|
||||
} else {
|
||||
query = {
|
||||
prompt: request.trim()
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch(connectionBase + `/api/chat/${sessionId}/${type}`, {
|
||||
const response = await fetch(connectionBase + `/api/${type}/${sessionId}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -379,17 +371,20 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
|
||||
|
||||
switch (update.status) {
|
||||
case 'done':
|
||||
console.log('Done processing:', update);
|
||||
stopCountdown();
|
||||
setStreamingMessage(undefined);
|
||||
setProcessingMessage(undefined);
|
||||
case 'partial':
|
||||
if (update.status === 'done') stopCountdown();
|
||||
if (update.status === 'done') setStreamingMessage(undefined);
|
||||
if (update.status === 'done') setProcessingMessage(undefined);
|
||||
const backstoryMessage: BackstoryMessage = update;
|
||||
setConversation([
|
||||
...conversationRef.current, {
|
||||
...backstoryMessage,
|
||||
role: 'assistant',
|
||||
origin: type,
|
||||
prompt: ['done', 'partial'].includes(update.status) ? update.prompt : '',
|
||||
content: backstoryMessage.response || "",
|
||||
expanded: update.status === "done" ? true : false,
|
||||
expandable: update.status === "done" ? false : true,
|
||||
}] as MessageList);
|
||||
// Add a small delay to ensure React has time to update the UI
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
@ -424,9 +419,9 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
|
||||
// Update processing message with immediate re-render
|
||||
if (update.status === "streaming") {
|
||||
streaming_response += update.chunk
|
||||
setStreamingMessage({ role: update.status, content: streaming_response });
|
||||
setStreamingMessage({ role: update.status, content: streaming_response, disableCopy: true });
|
||||
} else {
|
||||
setProcessingMessage({ role: update.status, content: update.response });
|
||||
setProcessingMessage({ role: update.status, content: update.response, disableCopy: true });
|
||||
/* Reset stream on non streaming message */
|
||||
streaming_response = ""
|
||||
}
|
||||
@ -437,12 +432,11 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
while (!stopRef.current) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
|
||||
const chunk = decoder.decode(value, { stream: true });
|
||||
|
||||
// Process each complete line immediately
|
||||
@ -470,26 +464,32 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
|
||||
}
|
||||
}
|
||||
|
||||
if (stopRef.current) {
|
||||
await reader.cancel();
|
||||
setProcessingMessage(undefined);
|
||||
setStreamingMessage(undefined);
|
||||
setSnack("Processing cancelled", "warning");
|
||||
}
|
||||
stopCountdown();
|
||||
setProcessing(false);
|
||||
stopRef.current = false;
|
||||
} catch (error) {
|
||||
console.error('Fetch error:', error);
|
||||
setSnack("Unable to process query", "error");
|
||||
setProcessingMessage({ role: 'error', content: "Unable to process query" });
|
||||
setProcessingMessage({ role: 'error', content: "Unable to process query", disableCopy: true });
|
||||
setTimeout(() => {
|
||||
setProcessingMessage(undefined);
|
||||
}, 5000);
|
||||
|
||||
stopRef.current = false;
|
||||
setProcessing(false);
|
||||
stopCountdown();
|
||||
// Add a small delay to ensure React has time to update the UI
|
||||
await new Promise(resolve => setTimeout(resolve, 0));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Scrollable
|
||||
className={className || "Conversation"}
|
||||
className={`${className || ""} Conversation`}
|
||||
autoscroll
|
||||
textFieldRef={viewableElementRef}
|
||||
fallbackThreshold={0.5}
|
||||
@ -559,11 +559,25 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
|
||||
sx={{ m: 1, gap: 1, flexGrow: 1 }}
|
||||
variant="contained"
|
||||
disabled={sessionId === undefined || processingMessage !== undefined}
|
||||
onClick={() => { sendQuery((backstoryTextRef.current && backstoryTextRef.current.getAndResetValue()) || ""); }}>
|
||||
onClick={() => { sendQuery({ prompt: (backstoryTextRef.current && backstoryTextRef.current.getAndResetValue()) || "" }); }}>
|
||||
{actionLabel}<SendIcon />
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip title="Cancel">
|
||||
<span style={{ display: "flex" }}> { /* This span is used to wrap the IconButton to ensure Tooltip works even when disabled */}
|
||||
<IconButton
|
||||
aria-label="cancel"
|
||||
onClick={() => { cancelQuery(); }}
|
||||
sx={{ display: "flex", margin: 'auto 0px' }}
|
||||
size="large"
|
||||
edge="start"
|
||||
disabled={stopRef.current || sessionId === undefined || processing === false}
|
||||
>
|
||||
<CancelIcon />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
{(noInteractions || !hideDefaultPrompts) && defaultPrompts !== undefined && defaultPrompts.length &&
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Message } from './Message';
|
||||
import { BackstoryElementProps } from './BackstoryTab';
|
||||
import { connectionBase } from './Global';
|
||||
|
||||
interface DocumentProps extends BackstoryElementProps {
|
||||
title: string;
|
||||
@ -13,7 +12,12 @@ interface DocumentProps extends BackstoryElementProps {
|
||||
}
|
||||
|
||||
const Document = (props: DocumentProps) => {
|
||||
const { setSnack, submitQuery, filepath, content, title, expanded, disableCopy, onExpand, sessionId } = props;
|
||||
const { sessionId, setSnack, submitQuery, filepath, content, title, expanded, disableCopy, onExpand } = props;
|
||||
const backstoryProps = {
|
||||
submitQuery,
|
||||
setSnack,
|
||||
sessionId
|
||||
}
|
||||
|
||||
const [document, setDocument] = useState<string>("");
|
||||
|
||||
@ -55,14 +59,11 @@ const Document = (props: DocumentProps) => {
|
||||
flexGrow: 0,
|
||||
},
|
||||
message: { role: 'content', title: title, content: document || content || "" },
|
||||
connectionBase,
|
||||
submitQuery,
|
||||
setSnack,
|
||||
expanded,
|
||||
disableCopy,
|
||||
onExpand,
|
||||
sessionId,
|
||||
}} />
|
||||
}}
|
||||
{...backstoryProps} />
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -14,6 +14,10 @@ const HomePage = forwardRef<ConversationHandle, BackstoryPageProps>((props: Back
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||
|
||||
if (sessionId === undefined) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const backstoryPreamble: MessageList = [
|
||||
{
|
||||
role: 'content',
|
||||
@ -32,10 +36,10 @@ What would you like to know about James?
|
||||
|
||||
const backstoryQuestions = [
|
||||
<Box sx={{ display: "flex", flexDirection: isMobile ? "column" : "row" }}>
|
||||
<ChatQuery prompt="What is James Ketrenos' work history?" tunables={{ enable_tools: false }} submitQuery={submitQuery} />
|
||||
<ChatQuery prompt="What programming languages has James used?" tunables={{ enable_tools: false }} submitQuery={submitQuery} />
|
||||
<ChatQuery prompt="What are James' professional strengths?" tunables={{ enable_tools: false }} submitQuery={submitQuery} />
|
||||
<ChatQuery prompt="What are today's headlines on CNBC.com?" tunables={{ enable_tools: true, enable_rag: false, enable_context: false }} submitQuery={submitQuery} />
|
||||
<ChatQuery query={{ prompt: "What is James Ketrenos' work history?", tunables: { enable_tools: false } }} submitQuery={submitQuery} />
|
||||
<ChatQuery query={{ prompt: "Provide an exhaustive list of programming languages James has used.", tunables: { enable_tools: false } }} submitQuery={submitQuery} />
|
||||
<ChatQuery query={{ prompt: "What are James' professional strengths?", tunables: { enable_tools: false } }} submitQuery={submitQuery} />
|
||||
<ChatQuery query={{ prompt: "What are today's headlines on CNBC.com?", tunables: { enable_tools: true, enable_rag: false, enable_context: false } }} submitQuery={submitQuery} />
|
||||
</Box>,
|
||||
<Box sx={{ p: 1 }}>
|
||||
<MuiMarkdown>
|
||||
|
21
frontend/src/LoadingPage.tsx
Normal file
21
frontend/src/LoadingPage.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import Box from '@mui/material/Box';
|
||||
import { BackstoryPageProps } from './BackstoryTab';
|
||||
import { BackstoryMessage, Message } from './Message';
|
||||
|
||||
const LoadingPage = (props: BackstoryPageProps) => {
|
||||
const backstoryPreamble: BackstoryMessage = {
|
||||
role: 'info',
|
||||
title: 'Please wait while connecting to Backstory...',
|
||||
disableCopy: true,
|
||||
content: '...',
|
||||
expandable: false,
|
||||
}
|
||||
|
||||
return <Box sx={{display: "flex", flexGrow: 1, maxWidth: "1024px", margin: "0 auto"}}>
|
||||
<Message message={backstoryPreamble} {...props} />
|
||||
</Box>
|
||||
};
|
||||
|
||||
export {
|
||||
LoadingPage
|
||||
};
|
@ -88,6 +88,10 @@ button {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.MessageContent div > p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.MenuCard.MuiCard-root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
328
frontend/src/Main.tsx
Normal file
328
frontend/src/Main.tsx
Normal file
@ -0,0 +1,328 @@
|
||||
import React, { useEffect, useState, useRef, useMemo } from 'react';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||
import Card from '@mui/material/Card';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
import Tabs from '@mui/material/Tabs';
|
||||
import Tab from '@mui/material/Tab';
|
||||
import Tooltip from '@mui/material/Tooltip';
|
||||
import AppBar from '@mui/material/AppBar';
|
||||
import Drawer from '@mui/material/Drawer';
|
||||
import Toolbar from '@mui/material/Toolbar';
|
||||
import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Box from '@mui/material/Box';
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
|
||||
import { ConversationHandle } from './Conversation';
|
||||
import { Query } from './ChatQuery';
|
||||
import { Scrollable } from './Scrollable';
|
||||
import { BackstoryPage, BackstoryTabProps } from './BackstoryTab';
|
||||
|
||||
import { HomePage } from './HomePage';
|
||||
import { LoadingPage } from './LoadingPage';
|
||||
import { ResumeBuilderPage } from './ResumeBuilderPage';
|
||||
import { VectorVisualizerPage } from './VectorVisualizer';
|
||||
import { AboutPage } from './AboutPage';
|
||||
import { ControlsPage } from './ControlsPage';
|
||||
import { SetSnackType } from './Snack';
|
||||
|
||||
import './Main.css';
|
||||
import './Conversation.css';
|
||||
|
||||
import '@fontsource/roboto/300.css';
|
||||
import '@fontsource/roboto/400.css';
|
||||
import '@fontsource/roboto/500.css';
|
||||
import '@fontsource/roboto/700.css';
|
||||
|
||||
interface MainProps {
|
||||
sessionId: string,
|
||||
setSnack: SetSnackType
|
||||
}
|
||||
const Main = (props: MainProps) => {
|
||||
const { sessionId } = props;
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const [isMenuClosing, setIsMenuClosing] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState<number>(0);
|
||||
const [tab, setTab] = useState<any>(undefined);
|
||||
const isDesktop = useMediaQuery('(min-width:650px)');
|
||||
const prevIsDesktopRef = useRef<boolean>(isDesktop);
|
||||
const chatRef = useRef<ConversationHandle>(null);
|
||||
const [subRoute, setSubRoute] = useState<string>("");
|
||||
const backstoryProps = useMemo(() => {
|
||||
const handleSubmitChatQuery = (query: Query) => {
|
||||
console.log(`handleSubmitChatQuery:`, query, chatRef.current ? ' sending' : 'no handler');
|
||||
chatRef.current?.submitQuery(query);
|
||||
setActiveTab(0);
|
||||
};
|
||||
return { ...props, route: subRoute, setRoute: setSubRoute, submitQuery: handleSubmitChatQuery };
|
||||
}, [props, setActiveTab, subRoute]);
|
||||
|
||||
useEffect(() => {
|
||||
if (prevIsDesktopRef.current === isDesktop)
|
||||
return;
|
||||
|
||||
if (menuOpen) {
|
||||
setMenuOpen(false);
|
||||
}
|
||||
|
||||
prevIsDesktopRef.current = isDesktop;
|
||||
}, [isDesktop, setMenuOpen, menuOpen])
|
||||
|
||||
const tabs: BackstoryTabProps[] = useMemo(() => {
|
||||
const homeTab: BackstoryTabProps = {
|
||||
label: "",
|
||||
path: "",
|
||||
tabProps: {
|
||||
label: "Backstory",
|
||||
sx: { flexGrow: 1, fontSize: '1rem' },
|
||||
icon:
|
||||
<Avatar sx={{
|
||||
width: 24,
|
||||
height: 24
|
||||
}}
|
||||
variant="rounded"
|
||||
alt="Backstory logo"
|
||||
src="/logo192.png" />,
|
||||
iconPosition: "start"
|
||||
},
|
||||
children: <HomePage ref={chatRef} {...backstoryProps} />
|
||||
};
|
||||
|
||||
const loadingTab: BackstoryTabProps = {
|
||||
...homeTab,
|
||||
children: <LoadingPage {...backstoryProps} />
|
||||
};
|
||||
|
||||
const resumeBuilderTab: BackstoryTabProps = {
|
||||
label: "Resume Builder",
|
||||
path: "resume-builder",
|
||||
children: <ResumeBuilderPage {...backstoryProps} />
|
||||
};
|
||||
|
||||
const contextVisualizerTab: BackstoryTabProps = {
|
||||
label: "Context Visualizer",
|
||||
path: "context-visualizer",
|
||||
children: <VectorVisualizerPage sx={{ p: 1 }} {...backstoryProps} />
|
||||
};
|
||||
|
||||
const aboutTab = {
|
||||
label: "About",
|
||||
path: "about",
|
||||
children: <AboutPage {...backstoryProps} />
|
||||
};
|
||||
|
||||
const controlsTab: BackstoryTabProps = {
|
||||
path: "controls",
|
||||
tabProps: {
|
||||
sx: { flexShrink: 1, flexGrow: 0, fontSize: '1rem' },
|
||||
icon: <SettingsIcon />
|
||||
},
|
||||
children: (
|
||||
<Scrollable
|
||||
autoscroll={false}
|
||||
sx={{
|
||||
maxWidth: "1024px",
|
||||
height: "calc(100vh - 72px)",
|
||||
flexDirection: "column",
|
||||
margin: "0 auto",
|
||||
p: 1,
|
||||
}}
|
||||
>
|
||||
<ControlsPage {...backstoryProps} />
|
||||
</Scrollable>
|
||||
)
|
||||
};
|
||||
|
||||
if (sessionId === undefined) {
|
||||
return [loadingTab];
|
||||
} else {
|
||||
return [
|
||||
homeTab,
|
||||
resumeBuilderTab,
|
||||
contextVisualizerTab,
|
||||
aboutTab,
|
||||
controlsTab,
|
||||
];
|
||||
}
|
||||
}, [backstoryProps, sessionId]);
|
||||
|
||||
const handleMenuClose = () => {
|
||||
setIsMenuClosing(true);
|
||||
setMenuOpen(false);
|
||||
};
|
||||
|
||||
const handleMenuTransitionEnd = () => {
|
||||
setIsMenuClosing(false);
|
||||
};
|
||||
|
||||
const handleMenuToggle = () => {
|
||||
if (!isMenuClosing) {
|
||||
setMenuOpen(!menuOpen);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (tab === undefined || tab === tabs[activeTab]) {
|
||||
return;
|
||||
}
|
||||
setTab(tabs[activeTab]);
|
||||
setSubRoute("");
|
||||
}, [tabs, activeTab, tab]);
|
||||
|
||||
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||
if (newValue > tabs.length) {
|
||||
console.log(`Invalid tab requested: ${newValue}`);
|
||||
return;
|
||||
}
|
||||
setActiveTab(newValue);
|
||||
handleMenuClose();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const pathParts = window.location.pathname.split('/').filter(Boolean);
|
||||
const currentPath = pathParts.length < 2 ? '' : pathParts[0];
|
||||
let currentSubRoute = pathParts.length > 2 ? pathParts.slice(1, -1).join('/') : '';
|
||||
let tabIndex = tabs.findIndex((tab) => tab.path === currentPath);
|
||||
if (tabIndex === -1) {
|
||||
console.log(`Invalid path "${currentPath}" -- redirecting to default`);
|
||||
tabIndex = 0
|
||||
currentSubRoute = ""
|
||||
}
|
||||
|
||||
setActiveTab(tabIndex);
|
||||
setTab(tabs[tabIndex]);
|
||||
setSubRoute(currentSubRoute);
|
||||
console.log(`Initial load set to tab ${tabs[tabIndex].path} subRoute: ${currentSubRoute}`);
|
||||
}, [tabs]);
|
||||
|
||||
useEffect(() => {
|
||||
if (tab === undefined || sessionId === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let path = tab.path ? `/${tab.path}` : '';
|
||||
if (subRoute) path += `/${subRoute}`;
|
||||
path += `/${sessionId}`;
|
||||
|
||||
if (path !== location.pathname) {
|
||||
console.log(`Pusing state ${path}`)
|
||||
navigate(path, { replace: true });
|
||||
}
|
||||
}, [tab, subRoute, sessionId, navigate, location.pathname]);
|
||||
|
||||
/* toolbar height is 64px + 8px margin-top */
|
||||
const Offset = styled('div')(() => ({ minHeight: '72px', height: '72px' }));
|
||||
|
||||
return (
|
||||
<Box className="App"
|
||||
sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<CssBaseline />
|
||||
<AppBar
|
||||
position="fixed"
|
||||
sx={{
|
||||
zIndex: (theme) => theme.zIndex.drawer + 1,
|
||||
maxWidth: "100vw"
|
||||
}}
|
||||
>
|
||||
<Toolbar>
|
||||
<Box sx={{ display: "flex", flexGrow: 1, flexDirection: "row" }}>
|
||||
{!isDesktop &&
|
||||
<Box sx={{ display: "flex", flexGrow: 1, flexDirection: "row" }}>
|
||||
<IconButton
|
||||
sx={{ display: "flex", margin: 'auto 0px' }}
|
||||
size="large"
|
||||
edge="start"
|
||||
color="inherit"
|
||||
onClick={handleMenuToggle}
|
||||
>
|
||||
<Tooltip title="Navigation">
|
||||
<MenuIcon />
|
||||
</Tooltip>
|
||||
</IconButton>
|
||||
<Tooltip title="Backstory">
|
||||
<Box
|
||||
sx={{ m: 1, gap: 1, display: "flex", flexDirection: "row", alignItems: "center", fontWeight: "bold", fontSize: "1.0rem", cursor: "pointer" }}
|
||||
onClick={() => { setActiveTab(0); setMenuOpen(false); }}
|
||||
>
|
||||
<Avatar sx={{
|
||||
width: 24,
|
||||
height: 24
|
||||
}}
|
||||
variant="rounded"
|
||||
alt="Backstory logo"
|
||||
src="/logo192.png" />
|
||||
BACKSTORY
|
||||
</Box>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
}
|
||||
|
||||
{menuOpen === false && isDesktop &&
|
||||
<Tabs sx={{ display: "flex", flexGrow: 1 }}
|
||||
value={activeTab}
|
||||
indicatorColor="secondary"
|
||||
textColor="inherit"
|
||||
variant="fullWidth"
|
||||
allowScrollButtonsMobile
|
||||
onChange={handleTabChange}
|
||||
aria-label="Backstory navigation">
|
||||
{tabs.map((tab, index) => <Tab key={index} value={index} label={tab.label} {...tab.tabProps} />)}
|
||||
</Tabs>
|
||||
}
|
||||
</Box>
|
||||
</Toolbar>
|
||||
|
||||
</AppBar>
|
||||
|
||||
<Offset />
|
||||
|
||||
<Box sx={{ display: "flex", flexGrow: 1, flexDirection: "column" }} >
|
||||
<Drawer
|
||||
container={window.document.body}
|
||||
variant="temporary"
|
||||
open={menuOpen}
|
||||
onTransitionEnd={handleMenuTransitionEnd}
|
||||
onClose={handleMenuClose}
|
||||
sx={{
|
||||
display: 'block',
|
||||
'& .MuiDrawer-paper': { boxSizing: 'border-box' },
|
||||
}}
|
||||
slotProps={{
|
||||
root: {
|
||||
keepMounted: true, // Better open performance on mobile.
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Toolbar />
|
||||
<Card className="MenuCard">
|
||||
<Tabs sx={{ display: "flex", flexGrow: 1 }}
|
||||
orientation="vertical"
|
||||
value={activeTab}
|
||||
indicatorColor="secondary"
|
||||
textColor="inherit"
|
||||
variant="scrollable"
|
||||
allowScrollButtonsMobile
|
||||
onChange={handleTabChange}
|
||||
aria-label="Backstory navigation">
|
||||
{tabs.map((tab, index) => <Tab key={index} value={index} label={tab.label} {...tab.tabProps} />)}
|
||||
</Tabs>
|
||||
</Card>
|
||||
</Drawer>
|
||||
{
|
||||
tabs.map((tab: any, i: number) =>
|
||||
<BackstoryPage key={i} active={i === activeTab} path={tab.path}>{tab.children}</BackstoryPage>
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
</Box >
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
Main
|
||||
}
|
@ -15,7 +15,6 @@ import Button from '@mui/material/Button';
|
||||
import CardContent from '@mui/material/CardContent';
|
||||
import CardActions from '@mui/material/CardActions';
|
||||
import Collapse from '@mui/material/Collapse';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import { ExpandMore } from './ExpandMore';
|
||||
import { SxProps, Theme } from '@mui/material';
|
||||
@ -47,11 +46,18 @@ type MessageRoles =
|
||||
'thinking' |
|
||||
'user';
|
||||
|
||||
type MessageData = {
|
||||
type BackstoryMessage = {
|
||||
// Only two required fields
|
||||
role: MessageRoles,
|
||||
content: string,
|
||||
status?: string, // streaming, done, error...
|
||||
response?: string,
|
||||
// Rest are optional
|
||||
prompt?: string;
|
||||
preamble?: {};
|
||||
status?: string;
|
||||
full_content?: string;
|
||||
response?: string; // Set when status === 'done', 'partial', or 'error'
|
||||
chunk?: string; // Used when status === 'streaming'
|
||||
timestamp?: string;
|
||||
disableCopy?: boolean,
|
||||
user?: string,
|
||||
title?: string,
|
||||
@ -79,16 +85,15 @@ interface MessageMetaData {
|
||||
eval_duration: number,
|
||||
prompt_eval_count: number,
|
||||
prompt_eval_duration: number,
|
||||
sessionId?: string,
|
||||
connectionBase: string,
|
||||
setSnack: SetSnackType,
|
||||
}
|
||||
|
||||
type MessageList = MessageData[];
|
||||
type MessageList = BackstoryMessage[];
|
||||
|
||||
interface MessageProps extends BackstoryElementProps {
|
||||
sx?: SxProps<Theme>,
|
||||
message: MessageData,
|
||||
message: BackstoryMessage,
|
||||
expanded?: boolean,
|
||||
onExpand?: (open: boolean) => void,
|
||||
className?: string,
|
||||
@ -112,6 +117,8 @@ const MessageMeta = (props: MessageMetaProps) => {
|
||||
} = props.metadata || {};
|
||||
const message: any = props.messageProps.message;
|
||||
|
||||
rag.forEach((r: any) => r.query = message.prompt);
|
||||
|
||||
let llm_submission: string = "<|system|>\n"
|
||||
llm_submission += message.system_prompt + "\n\n"
|
||||
llm_submission += message.context_prompt
|
||||
@ -184,31 +191,18 @@ const MessageMeta = (props: MessageMetaProps) => {
|
||||
</Accordion>
|
||||
}
|
||||
{
|
||||
rag.map((rag: any) => (
|
||||
<Accordion key={rag.name}>
|
||||
rag.map((collection: any) => (
|
||||
<Accordion key={collection.name}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Box sx={{ fontSize: "0.8rem" }}>
|
||||
Top RAG {rag.ids.length} matches from '{rag.name}' collection against embedding vector of {rag.query_embedding.length} dimensions
|
||||
Top {collection.ids.length} RAG matches from {collection.size} entries using an embedding vector of {collection.query_embedding.length} dimensions
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Box sx={{ fontSize: "0.8rem" }}>
|
||||
UMAP Vector Visualization of '{rag.name}' RAG
|
||||
</Box>
|
||||
<VectorVisualizer inline {...props.messageProps} {...props.metadata} rag={rag} />
|
||||
{rag.ids.map((id: number, index: number) => <Box key={index}>
|
||||
<Divider />
|
||||
<Box sx={{ whiteSpace: "nowrap", fontSize: "0.75rem", p: 0, m: 0, pt: 0.5 }}>Doc ID: {rag.ids[index]}</Box>
|
||||
<Box sx={{ fontSize: "0.75rem", display: "flex", flexDirection: "row", mb: 0.5, mt: 0.5 }}>
|
||||
<div style={{ display: "flex", flexDirection: "column", paddingRight: "1rem", minWidth: "10rem" }}>
|
||||
<div style={{ whiteSpace: "nowrap" }}>Distance: {Math.round(rag.distances[index] * 100) / 100}</div>
|
||||
<div style={{ whiteSpace: "nowrap" }}>Type: {rag.metadatas[index].doc_type}</div>
|
||||
<div style={{ whiteSpace: "nowrap" }}>Chunk Len: {rag.documents[index].length}</div>
|
||||
</div>
|
||||
<div style={{ display: "flex", padding: "3px", flexGrow: 1, border: "1px solid #E0E0E0", maxHeight: "5rem", overflow: "auto" }}>{rag.documents[index]}</div>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
<VectorVisualizer inline
|
||||
{...props.messageProps} {...props.metadata}
|
||||
rag={collection} />
|
||||
{/* { ...rag, query: message.prompt }} /> */}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
))
|
||||
@ -237,9 +231,14 @@ const MessageMeta = (props: MessageMetaProps) => {
|
||||
};
|
||||
|
||||
const Message = (props: MessageProps) => {
|
||||
const { message, submitQuery, sx, className, onExpand, expanded, sessionId, setSnack } = props;
|
||||
const { message, submitQuery, sx, className, onExpand, setSnack, sessionId, expanded } = props;
|
||||
const [metaExpanded, setMetaExpanded] = useState<boolean>(false);
|
||||
const textFieldRef = useRef(null);
|
||||
const backstoryProps = {
|
||||
submitQuery,
|
||||
sessionId,
|
||||
setSnack
|
||||
};
|
||||
|
||||
const handleMetaExpandClick = () => {
|
||||
setMetaExpanded(!metaExpanded);
|
||||
@ -254,14 +253,17 @@ const Message = (props: MessageProps) => {
|
||||
return (<></>);
|
||||
}
|
||||
|
||||
const formattedContent = message.content.trim() || "Waiting for LLM to spool up...";
|
||||
const formattedContent = message.content.trim();
|
||||
if (formattedContent === "") {
|
||||
return (<></>);
|
||||
}
|
||||
|
||||
return (
|
||||
<ChatBubble
|
||||
className={className || "Message"}
|
||||
className={`${className || ""} Message Message-${message.role}`}
|
||||
{...message}
|
||||
onExpand={onExpand}
|
||||
expanded={expanded}
|
||||
onExpand={onExpand}
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
@ -273,34 +275,24 @@ const Message = (props: MessageProps) => {
|
||||
...sx,
|
||||
}}>
|
||||
<CardContent ref={textFieldRef} sx={{ position: "relative", display: "flex", flexDirection: "column", overflowX: "auto", m: 0, p: 0, paddingBottom: '0px !important' }}>
|
||||
{message.role !== 'user' ?
|
||||
<Scrollable
|
||||
className="MessageContent"
|
||||
autoscroll
|
||||
fallbackThreshold={0.5}
|
||||
sx={{
|
||||
p: 0,
|
||||
m: 0,
|
||||
maxHeight: (message.role === "streaming") ? "20rem" : "unset",
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
overflow: "auto", /* Handles scrolling for the div */
|
||||
}}
|
||||
>
|
||||
<StyledMarkdown streaming={message.role === "streaming"} {...{ content: formattedContent, submitQuery, sessionId, setSnack }} />
|
||||
</Scrollable>
|
||||
:
|
||||
<Typography
|
||||
className="MessageContent"
|
||||
ref={textFieldRef}
|
||||
variant="body2"
|
||||
sx={{ display: "flex", color: 'text.secondary' }}>
|
||||
{message.content}
|
||||
</Typography>
|
||||
}
|
||||
<Scrollable
|
||||
className="MessageContent"
|
||||
autoscroll
|
||||
fallbackThreshold={0.5}
|
||||
sx={{
|
||||
p: 0,
|
||||
m: 0,
|
||||
maxHeight: (message.role === "streaming") ? "20rem" : "unset",
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
overflow: "auto", /* Handles scrolling for the div */
|
||||
}}
|
||||
>
|
||||
<StyledMarkdown streaming={message.role === "streaming"} content={formattedContent} {...backstoryProps} />
|
||||
</Scrollable>
|
||||
</CardContent>
|
||||
<CardActions disableSpacing sx={{ display: "flex", flexDirection: "row", justifyContent: "space-between", alignItems: "center", width: "100%", p: 0, m: 0 }}>
|
||||
{(message.disableCopy === undefined || message.disableCopy === false) && ["assistant", "content"].includes(message.role) && <CopyBubble content={message.content} />}
|
||||
{(message.disableCopy === undefined || message.disableCopy === false) && <CopyBubble content={message.content} />}
|
||||
{message.metadata && (
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||
<Button variant="text" onClick={handleMetaExpandClick} sx={{ color: "darkgrey", p: 0 }}>
|
||||
@ -309,7 +301,7 @@ const Message = (props: MessageProps) => {
|
||||
<ExpandMore
|
||||
expand={metaExpanded}
|
||||
onClick={handleMetaExpandClick}
|
||||
aria-expanded={expanded}
|
||||
aria-expanded={message.expanded}
|
||||
aria-label="show more"
|
||||
>
|
||||
<ExpandMoreIcon />
|
||||
@ -331,7 +323,8 @@ const Message = (props: MessageProps) => {
|
||||
export type {
|
||||
MessageProps,
|
||||
MessageList,
|
||||
MessageData,
|
||||
BackstoryMessage,
|
||||
MessageMetaData,
|
||||
MessageRoles,
|
||||
};
|
||||
|
||||
|
@ -6,8 +6,8 @@ import {
|
||||
} from '@mui/material';
|
||||
import { SxProps } from '@mui/material';
|
||||
|
||||
import { ChatQuery } from './ChatQuery';
|
||||
import { MessageList, MessageData } from './Message';
|
||||
import { ChatQuery, Query } from './ChatQuery';
|
||||
import { MessageList, BackstoryMessage } from './Message';
|
||||
import { Conversation } from './Conversation';
|
||||
import { BackstoryPageProps } from './BackstoryTab';
|
||||
|
||||
@ -19,12 +19,13 @@ import './ResumeBuilderPage.css';
|
||||
* A responsive component that displays job descriptions, generated resumes and fact checks
|
||||
* with different layouts for mobile and desktop views.
|
||||
*/
|
||||
const ResumeBuilderPage: React.FC<BackstoryPageProps> = ({
|
||||
sx,
|
||||
sessionId,
|
||||
setSnack,
|
||||
submitQuery,
|
||||
}) => {
|
||||
const ResumeBuilderPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
|
||||
const {
|
||||
sx,
|
||||
sessionId,
|
||||
setSnack,
|
||||
submitQuery,
|
||||
} = props
|
||||
// State for editing job description
|
||||
const [hasJobDescription, setHasJobDescription] = useState<boolean>(false);
|
||||
const [hasResume, setHasResume] = useState<boolean>(false);
|
||||
@ -42,18 +43,18 @@ const ResumeBuilderPage: React.FC<BackstoryPageProps> = ({
|
||||
setActiveTab(newValue);
|
||||
};
|
||||
|
||||
const handleJobQuery = (query: string) => {
|
||||
console.log(`handleJobQuery: ${query} -- `, jobConversationRef.current ? ' sending' : 'no handler');
|
||||
const handleJobQuery = (query: Query) => {
|
||||
console.log(`handleJobQuery: ${query.prompt} -- `, jobConversationRef.current ? ' sending' : 'no handler');
|
||||
jobConversationRef.current?.submitQuery(query);
|
||||
};
|
||||
|
||||
const handleResumeQuery = (query: string) => {
|
||||
console.log(`handleResumeQuery: ${query} -- `, resumeConversationRef.current ? ' sending' : 'no handler');
|
||||
const handleResumeQuery = (query: Query) => {
|
||||
console.log(`handleResumeQuery: ${query.prompt} -- `, resumeConversationRef.current ? ' sending' : 'no handler');
|
||||
resumeConversationRef.current?.submitQuery(query);
|
||||
};
|
||||
|
||||
const handleFactsQuery = (query: string) => {
|
||||
console.log(`handleFactsQuery: ${query} -- `, factsConversationRef.current ? ' sending' : 'no handler');
|
||||
const handleFactsQuery = (query: Query) => {
|
||||
console.log(`handleFactsQuery: ${query.prompt} -- `, factsConversationRef.current ? ' sending' : 'no handler');
|
||||
factsConversationRef.current?.submitQuery(query);
|
||||
};
|
||||
|
||||
@ -62,11 +63,6 @@ const ResumeBuilderPage: React.FC<BackstoryPageProps> = ({
|
||||
return [];
|
||||
}
|
||||
|
||||
if (messages.length > 2) {
|
||||
setHasResume(true);
|
||||
setHasFacts(true);
|
||||
}
|
||||
|
||||
if (messages.length > 0) {
|
||||
messages[0].role = 'content';
|
||||
messages[0].title = 'Job Description';
|
||||
@ -74,6 +70,19 @@ const ResumeBuilderPage: React.FC<BackstoryPageProps> = ({
|
||||
messages[0].expandable = true;
|
||||
}
|
||||
|
||||
if (-1 !== messages.findIndex(m => m.status === 'done')) {
|
||||
setHasResume(true);
|
||||
setHasFacts(true);
|
||||
}
|
||||
|
||||
return messages;
|
||||
|
||||
if (messages.length > 1) {
|
||||
setHasResume(true);
|
||||
setHasFacts(true);
|
||||
}
|
||||
|
||||
|
||||
if (messages.length > 3) {
|
||||
// messages[2] is Show job requirements
|
||||
messages[3].role = 'job-requirements';
|
||||
@ -95,6 +104,8 @@ const ResumeBuilderPage: React.FC<BackstoryPageProps> = ({
|
||||
return [];
|
||||
}
|
||||
|
||||
return messages;
|
||||
|
||||
if (messages.length > 1) {
|
||||
// messages[0] is Show Qualifications
|
||||
messages[1].role = 'qualifications';
|
||||
@ -139,7 +150,7 @@ const ResumeBuilderPage: React.FC<BackstoryPageProps> = ({
|
||||
return filtered;
|
||||
}, []);
|
||||
|
||||
const jobResponse = useCallback(async (message: MessageData) => {
|
||||
const jobResponse = useCallback(async (message: BackstoryMessage) => {
|
||||
console.log('onJobResponse', message);
|
||||
if (message.actions && message.actions.includes("job_description")) {
|
||||
await jobConversationRef.current.fetchHistory();
|
||||
@ -155,12 +166,12 @@ const ResumeBuilderPage: React.FC<BackstoryPageProps> = ({
|
||||
}
|
||||
}, [setHasFacts, setHasResume, setActiveTab]);
|
||||
|
||||
const resumeResponse = useCallback((message: MessageData): void => {
|
||||
const resumeResponse = useCallback((message: BackstoryMessage): void => {
|
||||
console.log('onResumeResponse', message);
|
||||
setHasFacts(true);
|
||||
}, [setHasFacts]);
|
||||
|
||||
const factsResponse = useCallback((message: MessageData): void => {
|
||||
const factsResponse = useCallback((message: BackstoryMessage): void => {
|
||||
console.log('onFactsResponse', message);
|
||||
}, []);
|
||||
|
||||
@ -183,23 +194,27 @@ const ResumeBuilderPage: React.FC<BackstoryPageProps> = ({
|
||||
console.log('renderJobDescriptionView');
|
||||
const jobDescriptionQuestions = [
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
<ChatQuery prompt="What are the key skills necessary for this position?" tunables={{ enable_tools: false }} submitQuery={handleJobQuery} />
|
||||
<ChatQuery prompt="How much should this position pay (accounting for inflation)?" tunables={{ enable_tools: false }} submitQuery={handleJobQuery} />
|
||||
<ChatQuery query={{ prompt: "What are the key skills necessary for this position?", tunables: { enable_tools: false } }} submitQuery={handleJobQuery} />
|
||||
<ChatQuery query={{ prompt: "How much should this position pay (accounting for inflation)?", tunables: { enable_tools: false } }} submitQuery={handleJobQuery} />
|
||||
</Box>,
|
||||
];
|
||||
|
||||
const jobDescriptionPreamble: MessageList = [{
|
||||
role: 'info',
|
||||
content: `Once you paste a job description and press **Generate Resume**, the system will perform the following actions:
|
||||
content: `Once you paste a job description and press **Generate Resume**, Backstory will perform the following actions:
|
||||
|
||||
1. **RAG**: Collects information from the RAG database relavent to the job description
|
||||
2. **Isolated Analysis**: Three sub-stages
|
||||
1. **Job Analysis**: Extracts requirements from job description only
|
||||
2. **Candidate Analysis**: Catalogs qualifications from resume/context only
|
||||
3. **Mapping Analysis**: Identifies legitimate matches between requirements and qualifications
|
||||
3. **Resume Generation**: Uses mapping output to create a tailored resume with evidence-based content
|
||||
4. **Verification**: Performs fact-checking to catch any remaining fabrications
|
||||
1. **Re-generation**: If verification does not pass, a second attempt is made to correct any issues`
|
||||
1. **Job Analysis**: LLM extracts requirements from '\`Job Description\`' to generate a list of desired '\`Skills\`'.
|
||||
2. **Candidate Analysis**: LLM determines candidate qualifications by performing skill assessments.
|
||||
|
||||
For each '\`Skill\`' from **Job Analysis** phase:
|
||||
|
||||
1. **RAG**: Retrieval Augmented Generation collection is queried for context related content for each '\`Skill\`'.
|
||||
2. **Evidence Creation**: LLM is queried to generate supporting evidence of '\`Skill\`' from the '\`RAG\`' and '\`Candidate Resume\`'.
|
||||
3. **Resume Generation**: LLM is provided the output from the **Candidate Analysis:Evidence Creation** phase and asked to generate a professional resume.
|
||||
|
||||
See [About > Resume Generation Architecture](/about/resume-generation) for more details.
|
||||
`,
|
||||
disableCopy: true
|
||||
}];
|
||||
|
||||
|
||||
@ -251,8 +266,8 @@ const ResumeBuilderPage: React.FC<BackstoryPageProps> = ({
|
||||
const renderResumeView = useCallback((sx: SxProps) => {
|
||||
const resumeQuestions = [
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
<ChatQuery prompt="Is this resume a good fit for the provided job description?" tunables={{ enable_tools: false }} submitQuery={handleResumeQuery} />
|
||||
<ChatQuery prompt="Provide a more concise resume." tunables={{ enable_tools: false }} submitQuery={handleResumeQuery} />
|
||||
<ChatQuery query={{ prompt: "Is this resume a good fit for the provided job description?", tunables: { enable_tools: false } }} submitQuery={handleResumeQuery} />
|
||||
<ChatQuery query={{ prompt: "Provide a more concise resume.", tunables: { enable_tools: false } }} submitQuery={handleResumeQuery} />
|
||||
</Box>,
|
||||
];
|
||||
|
||||
@ -300,7 +315,7 @@ const ResumeBuilderPage: React.FC<BackstoryPageProps> = ({
|
||||
const renderFactCheckView = useCallback((sx: SxProps) => {
|
||||
const factsQuestions = [
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
<ChatQuery prompt="Rewrite the resume to address any discrepancies." tunables={{ enable_tools: false }} submitQuery={handleFactsQuery} />
|
||||
<ChatQuery query={{ prompt: "Rewrite the resume to address any discrepancies.", tunables: { enable_tools: false } }} submitQuery={handleFactsQuery} />
|
||||
</Box>,
|
||||
];
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user