Compare commits
4 Commits
0216515492
...
538caba9f4
Author | SHA1 | Date | |
---|---|---|---|
538caba9f4 | |||
b6dd4878c8 | |||
97425a6aad | |||
c97d24be68 |
@ -359,8 +359,8 @@ WORKDIR /opt/ollama
|
||||
#ENV OLLAMA_VERSION=https://github.com/intel/ipex-llm/releases/download/v2.3.0-nightly/ollama-ipex-llm-2.3.0b20250415-ubuntu.tgz
|
||||
|
||||
# NOTE: NO longer at github.com/intel -- now at ipex-llm
|
||||
#ENV OLLAMA_VERSION=https://github.com/ipex-llm/ipex-llm/releases/download/v2.2.0/ollama-ipex-llm-2.2.0-ubuntu.tgz
|
||||
ENV OLLAMA_VERSION=https://github.com/ipex-llm/ipex-llm/releases/download/v2.3.0-nightly/ollama-ipex-llm-2.3.0b20250429-ubuntu.tgz
|
||||
ENV OLLAMA_VERSION=https://github.com/ipex-llm/ipex-llm/releases/download/v2.2.0/ollama-ipex-llm-2.2.0-ubuntu.tgz
|
||||
#ENV OLLAMA_VERSION=https://github.com/ipex-llm/ipex-llm/releases/download/v2.3.0-nightly/ollama-ipex-llm-2.3.0b20250429-ubuntu.tgz
|
||||
RUN wget -qO - ${OLLAMA_VERSION} | \
|
||||
tar --strip-components=1 -C . -xzv
|
||||
|
||||
|
@ -180,22 +180,22 @@ services:
|
||||
- ./cache/grafana:/var/lib/grafana
|
||||
|
||||
|
||||
loki:
|
||||
image: grafana/loki
|
||||
container_name: loki
|
||||
restart: "always"
|
||||
# env_file:
|
||||
# - .env.grafana
|
||||
ports:
|
||||
- 3211:3100 # Grafana
|
||||
networks:
|
||||
- internal
|
||||
command:
|
||||
- -config.file=/loki-config.yaml
|
||||
volumes:
|
||||
# - ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
- ./loki-config.yaml:/loki-config.yaml
|
||||
- ./cache/loki:/loki
|
||||
# loki:
|
||||
# image: grafana/loki
|
||||
# container_name: loki
|
||||
# restart: "always"
|
||||
# # env_file:
|
||||
# # - .env.grafana
|
||||
# ports:
|
||||
# - 3211:3100 # Grafana
|
||||
# networks:
|
||||
# - internal
|
||||
# command:
|
||||
# - -config.file=/loki-config.yaml
|
||||
# volumes:
|
||||
# # - ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
# - ./loki-config.yaml:/loki-config.yaml
|
||||
# - ./cache/loki:/loki
|
||||
|
||||
|
||||
networks:
|
||||
|
@ -1,8 +1,46 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.8e56f513.css",
|
||||
"main.js": "/static/js/main.6f07f748.js",
|
||||
"static/js/453.ec6f47ad.chunk.js": "/static/js/453.ec6f47ad.chunk.js",
|
||||
"main.css": "/static/css/main.3c3ddc39.css",
|
||||
"main.js": "/static/js/main.b1c48cdf.js",
|
||||
"static/js/732.b7e64c48.chunk.js": "/static/js/732.b7e64c48.chunk.js",
|
||||
"static/js/467.ed8fe19b.chunk.js": "/static/js/467.ed8fe19b.chunk.js",
|
||||
"static/js/761.64498d2a.chunk.js": "/static/js/761.64498d2a.chunk.js",
|
||||
"static/js/358.7641aa7d.chunk.js": "/static/js/358.7641aa7d.chunk.js",
|
||||
"static/js/448.0ebff170.chunk.js": "/static/js/448.0ebff170.chunk.js",
|
||||
"static/js/461.dcde3ae6.chunk.js": "/static/js/461.dcde3ae6.chunk.js",
|
||||
"static/js/109.fea37f16.chunk.js": "/static/js/109.fea37f16.chunk.js",
|
||||
"static/js/203.e56567f8.chunk.js": "/static/js/203.e56567f8.chunk.js",
|
||||
"static/js/621.a72de2cb.chunk.js": "/static/js/621.a72de2cb.chunk.js",
|
||||
"static/js/320.f30ff8c0.chunk.js": "/static/js/320.f30ff8c0.chunk.js",
|
||||
"static/js/314.5b42713c.chunk.js": "/static/js/314.5b42713c.chunk.js",
|
||||
"static/js/972.19d4b287.chunk.js": "/static/js/972.19d4b287.chunk.js",
|
||||
"static/js/126.9837c9af.chunk.js": "/static/js/126.9837c9af.chunk.js",
|
||||
"static/js/147.660f25b1.chunk.js": "/static/js/147.660f25b1.chunk.js",
|
||||
"static/js/807.a9b3c2ae.chunk.js": "/static/js/807.a9b3c2ae.chunk.js",
|
||||
"static/js/859.88148fa8.chunk.js": "/static/js/859.88148fa8.chunk.js",
|
||||
"static/js/282.876bf6b3.chunk.js": "/static/js/282.876bf6b3.chunk.js",
|
||||
"static/js/255.28a7c83e.chunk.js": "/static/js/255.28a7c83e.chunk.js",
|
||||
"static/js/922.10b19c61.chunk.js": "/static/js/922.10b19c61.chunk.js",
|
||||
"static/js/370.721ed12b.chunk.js": "/static/js/370.721ed12b.chunk.js",
|
||||
"static/js/674.cb2cf2f8.chunk.js": "/static/js/674.cb2cf2f8.chunk.js",
|
||||
"static/js/622.95b51007.chunk.js": "/static/js/622.95b51007.chunk.js",
|
||||
"static/js/824.71eb7c8f.chunk.js": "/static/js/824.71eb7c8f.chunk.js",
|
||||
"static/js/415.d031fb39.chunk.js": "/static/js/415.d031fb39.chunk.js",
|
||||
"static/js/349.dfb2510e.chunk.js": "/static/js/349.dfb2510e.chunk.js",
|
||||
"static/js/626.6df36496.chunk.js": "/static/js/626.6df36496.chunk.js",
|
||||
"static/js/453.6fae039d.chunk.js": "/static/js/453.6fae039d.chunk.js",
|
||||
"static/js/372.1ba03aa4.chunk.js": "/static/js/372.1ba03aa4.chunk.js",
|
||||
"static/js/974.879246cb.chunk.js": "/static/js/974.879246cb.chunk.js",
|
||||
"static/js/914.0546aa7a.chunk.js": "/static/js/914.0546aa7a.chunk.js",
|
||||
"static/js/987.378e3c51.chunk.js": "/static/js/987.378e3c51.chunk.js",
|
||||
"static/js/318.239ef60c.chunk.js": "/static/js/318.239ef60c.chunk.js",
|
||||
"static/js/929.7d5d6402.chunk.js": "/static/js/929.7d5d6402.chunk.js",
|
||||
"static/js/854.2162dcf9.chunk.js": "/static/js/854.2162dcf9.chunk.js",
|
||||
"static/js/502.89ac9055.chunk.js": "/static/js/502.89ac9055.chunk.js",
|
||||
"static/js/646.3df37337.chunk.js": "/static/js/646.3df37337.chunk.js",
|
||||
"static/js/62.785a92ce.chunk.js": "/static/js/62.785a92ce.chunk.js",
|
||||
"static/js/35.b34bde96.chunk.js": "/static/js/35.b34bde96.chunk.js",
|
||||
"static/js/355.143eaed8.chunk.js": "/static/js/355.143eaed8.chunk.js",
|
||||
"static/media/roboto-latin-700-normal.woff2": "/static/media/roboto-latin-700-normal.c4d6cab43bec89049809.woff2",
|
||||
"static/media/roboto-latin-500-normal.woff2": "/static/media/roboto-latin-500-normal.599f66a60bdf974e578e.woff2",
|
||||
"static/media/roboto-latin-300-normal.woff2": "/static/media/roboto-latin-300-normal.db56943a88e4852343ae.woff2",
|
||||
@ -76,12 +114,44 @@
|
||||
"static/media/roboto-greek-ext-500-normal.woff": "/static/media/roboto-greek-ext-500-normal.1964239c2800b6bd7e39.woff",
|
||||
"static/media/roboto-greek-ext-300-normal.woff": "/static/media/roboto-greek-ext-300-normal.60729cafbded24073dfb.woff",
|
||||
"index.html": "/index.html",
|
||||
"main.8e56f513.css.map": "/static/css/main.8e56f513.css.map",
|
||||
"main.6f07f748.js.map": "/static/js/main.6f07f748.js.map",
|
||||
"453.ec6f47ad.chunk.js.map": "/static/js/453.ec6f47ad.chunk.js.map"
|
||||
"main.3c3ddc39.css.map": "/static/css/main.3c3ddc39.css.map",
|
||||
"main.b1c48cdf.js.map": "/static/js/main.b1c48cdf.js.map",
|
||||
"732.b7e64c48.chunk.js.map": "/static/js/732.b7e64c48.chunk.js.map",
|
||||
"467.ed8fe19b.chunk.js.map": "/static/js/467.ed8fe19b.chunk.js.map",
|
||||
"761.64498d2a.chunk.js.map": "/static/js/761.64498d2a.chunk.js.map",
|
||||
"358.7641aa7d.chunk.js.map": "/static/js/358.7641aa7d.chunk.js.map",
|
||||
"448.0ebff170.chunk.js.map": "/static/js/448.0ebff170.chunk.js.map",
|
||||
"461.dcde3ae6.chunk.js.map": "/static/js/461.dcde3ae6.chunk.js.map",
|
||||
"109.fea37f16.chunk.js.map": "/static/js/109.fea37f16.chunk.js.map",
|
||||
"203.e56567f8.chunk.js.map": "/static/js/203.e56567f8.chunk.js.map",
|
||||
"621.a72de2cb.chunk.js.map": "/static/js/621.a72de2cb.chunk.js.map",
|
||||
"320.f30ff8c0.chunk.js.map": "/static/js/320.f30ff8c0.chunk.js.map",
|
||||
"314.5b42713c.chunk.js.map": "/static/js/314.5b42713c.chunk.js.map",
|
||||
"972.19d4b287.chunk.js.map": "/static/js/972.19d4b287.chunk.js.map",
|
||||
"126.9837c9af.chunk.js.map": "/static/js/126.9837c9af.chunk.js.map",
|
||||
"147.660f25b1.chunk.js.map": "/static/js/147.660f25b1.chunk.js.map",
|
||||
"807.a9b3c2ae.chunk.js.map": "/static/js/807.a9b3c2ae.chunk.js.map",
|
||||
"859.88148fa8.chunk.js.map": "/static/js/859.88148fa8.chunk.js.map",
|
||||
"282.876bf6b3.chunk.js.map": "/static/js/282.876bf6b3.chunk.js.map",
|
||||
"255.28a7c83e.chunk.js.map": "/static/js/255.28a7c83e.chunk.js.map",
|
||||
"922.10b19c61.chunk.js.map": "/static/js/922.10b19c61.chunk.js.map",
|
||||
"370.721ed12b.chunk.js.map": "/static/js/370.721ed12b.chunk.js.map",
|
||||
"674.cb2cf2f8.chunk.js.map": "/static/js/674.cb2cf2f8.chunk.js.map",
|
||||
"622.95b51007.chunk.js.map": "/static/js/622.95b51007.chunk.js.map",
|
||||
"824.71eb7c8f.chunk.js.map": "/static/js/824.71eb7c8f.chunk.js.map",
|
||||
"415.d031fb39.chunk.js.map": "/static/js/415.d031fb39.chunk.js.map",
|
||||
"349.dfb2510e.chunk.js.map": "/static/js/349.dfb2510e.chunk.js.map",
|
||||
"626.6df36496.chunk.js.map": "/static/js/626.6df36496.chunk.js.map",
|
||||
"453.6fae039d.chunk.js.map": "/static/js/453.6fae039d.chunk.js.map",
|
||||
"854.2162dcf9.chunk.js.map": "/static/js/854.2162dcf9.chunk.js.map",
|
||||
"502.89ac9055.chunk.js.map": "/static/js/502.89ac9055.chunk.js.map",
|
||||
"646.3df37337.chunk.js.map": "/static/js/646.3df37337.chunk.js.map",
|
||||
"62.785a92ce.chunk.js.map": "/static/js/62.785a92ce.chunk.js.map",
|
||||
"35.b34bde96.chunk.js.map": "/static/js/35.b34bde96.chunk.js.map",
|
||||
"355.143eaed8.chunk.js.map": "/static/js/355.143eaed8.chunk.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.8e56f513.css",
|
||||
"static/js/main.6f07f748.js"
|
||||
"static/css/main.3c3ddc39.css",
|
||||
"static/js/main.b1c48cdf.js"
|
||||
]
|
||||
}
|
21
frontend/deployed/docs/about-app.md
Normal file
21
frontend/deployed/docs/about-app.md
Normal file
@ -0,0 +1,21 @@
|
||||
Backstory is developed using:
|
||||
|
||||
## Frontend
|
||||
|
||||
* React
|
||||
* MUI
|
||||
* Plotly.js
|
||||
* MuiMarkdown
|
||||
* Mermaid
|
||||
|
||||
## Backend
|
||||
|
||||
* Python
|
||||
* FastAPI
|
||||
* HuggingFace Transformers
|
||||
* Ollama
|
||||
* Backstory Agent Framework
|
||||
* Prometheus
|
||||
* Grafana
|
||||
* ze-monitor
|
||||
* Jupyter Notebook
|
@ -1,15 +1,11 @@
|
||||
The backstory about Backstory...
|
||||
## Backstory is three things
|
||||
|
||||
## Backstory is two things
|
||||
|
||||
1. An interactive Q&A -- let potential employers ask questions about an individual's work history (aka "Backstory".) Based on the content the job seeker has provided to the RAG system, that can provide insights into that individual's resume and curriculum vitae that are often left out when people are trying to fit everything onto one page.
|
||||
1. **An interactive Q&A** -- let potential employers ask questions about an individual's work history (aka "Backstory".) Based on the content the job seeker has provided to the RAG system, that can provide insights into that individual's resume and curriculum vitae that are often left out when people are trying to fit everything onto one page.
|
||||
|
||||
|
||||
2. A resume builder -- if you have a job position, and you think this person might be a candidate, paste your job description and have a resume produced based on their data. If it looks interesting, reach out to them. If not, hopefully you've gained some insight into what drives them.
|
||||
2. **A resume builder** -- if you have a job position, and you think this person might be a candidate, paste your job description and have a resume produced based on their data. If it looks interesting, reach out to them. If not, hopefully you've gained some insight into what drives them.
|
||||
|
||||
-or-
|
||||
|
||||
2. A curated expert about you -- as a potential job seeker, you can self host this environment and generate resumes for yourself.
|
||||
3. **A curated expert about you** -- as a potential job seeker, you can self host this environment and generate resumes for yourself.
|
||||
|
||||
While this project was generally built for self-hosting with open source models, you can use any of the frontier models. The API adapters in this project can be configured to use infrastructure hosted from Anthropic, Google, Grok, and OpenAI (alphabetical.) For information, see [https://github.com/jketreno/backstory/README.md](https://github.com/jketreno/backstory/README.md#Frontier_Models).
|
||||
|
||||
@ -29,4 +25,4 @@ A. I could; but I don't want to store your data. I also don't want to have to be
|
||||
|
||||
Q. <ChatQuery prompt="Why can't I just ask Backstory these questions?" tunables={{ "enable_tools": false }} />
|
||||
|
||||
A. Try it. See what you find out :)
|
||||
A. Try it. See what you find out :)
|
||||
|
100
frontend/deployed/docs/resume-generation.md
Normal file
100
frontend/deployed/docs/resume-generation.md
Normal file
@ -0,0 +1,100 @@
|
||||
The system follows a carefully designed pipeline with isolated stages to prevent fabrication:
|
||||
|
||||
## System Architecture Overview
|
||||
|
||||
The system uses a pipeline of isolated analysis and generation steps:
|
||||
|
||||
1. **Stage 1: Isolated Analysis** (three sub-stages)
|
||||
- **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
|
||||
|
||||
2. **Stage 2: Resume Generation**
|
||||
- Uses mapping output to create a tailored resume with evidence-based content
|
||||
|
||||
3. **Stage 3: Verification**
|
||||
- Performs fact-checking to catch any remaining fabrications
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph "Stage 1: Isolated Analysis"
|
||||
subgraph "Stage 1A: Job Analysis"
|
||||
A1[Job Description Input] --> A2[Job Analysis LLM]
|
||||
A2 --> A3[Job Requirements JSON]
|
||||
end
|
||||
|
||||
subgraph "Stage 1B: Candidate Analysis"
|
||||
B1[Resume & Context Input] --> B2[Candidate Analysis LLM]
|
||||
B2 --> B3[Candidate Qualifications JSON]
|
||||
end
|
||||
|
||||
subgraph "Stage 1C: Mapping Analysis"
|
||||
C1[Job Requirements JSON] --> C2[Candidate Qualifications JSON]
|
||||
C2 --> C3[Mapping Analysis LLM]
|
||||
C3 --> C4[Skills Mapping JSON]
|
||||
end
|
||||
end
|
||||
|
||||
subgraph "Stage 2: Resume Generation"
|
||||
D1[Skills Mapping JSON] --> D2[Original Resume Reference]
|
||||
D2 --> D3[Resume Generation LLM]
|
||||
D3 --> D4[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
|
||||
end
|
||||
|
||||
A3 --> C1
|
||||
B3 --> C2
|
||||
C4 --> D1
|
||||
D4 --> E3
|
||||
|
||||
style A2 fill:#f9d77e,stroke:#333,stroke-width:2px
|
||||
style B2 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
|
||||
```
|
||||
|
||||
## Stage 1: Isolated Analysis (three separate sub-stages)
|
||||
|
||||
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
|
||||
|
||||
## Stage 2: Resume Generation
|
||||
|
||||
Creates a tailored resume using only verified information from the mapping
|
||||
|
||||
## Stage 3: Verification
|
||||
|
||||
1. Performs fact-checking to catch any remaining fabrications
|
||||
2. Corrects issues if needed and re-verifies
|
||||
|
||||
### Key Anti-Fabrication Mechanisms
|
||||
|
||||
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.
|
||||
* **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.
|
||||
* **Strict JSON Structures**: Using structured JSON formats ensures information flows properly between stages.
|
||||
|
||||
## Implementation Details
|
||||
|
||||
* **Prompt Engineering**: Each stage has carefully designed prompts with clear instructions and output formats.
|
||||
* **Error Handling**: Comprehensive validation and error handling throughout the pipeline.
|
||||
* **Correction Loop**: If verification fails, the system attempts to correct issues and re-verify.
|
||||
* **Traceability**: Information in the final resume can be traced back to specific evidence in the original materials.
|
||||
|
@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Interactive chat with an enhanced LLM."/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Backstory</title><script defer="defer" src="/static/js/main.6f07f748.js"></script><link href="/static/css/main.8e56f513.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Interactive chat with an enhanced LLM."/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Backstory</title><script defer="defer" src="/static/js/main.b1c48cdf.js"></script><link href="/static/css/main.3c3ddc39.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,2 +0,0 @@
|
||||
"use strict";(self.webpackChunkbackstory=self.webpackChunkbackstory||[]).push([[453],{453:(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.ec6f47ad.chunk.js.map
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,149 +0,0 @@
|
||||
/*!
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*! 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.
|
||||
*/
|
File diff suppressed because one or more lines are too long
80
frontend/src/AboutPage.tsx
Normal file
80
frontend/src/AboutPage.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
import { Scrollable } from './Scrollable';
|
||||
import { BackstoryPageProps } from './BackstoryTab';
|
||||
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]);
|
||||
|
||||
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("/"));
|
||||
}
|
||||
}, [route, setPage, setSubRoute]);
|
||||
|
||||
const onDocumentExpand = (document: string, open: boolean) => {
|
||||
console.log("Document expanded:", document, open);
|
||||
if (open) {
|
||||
setPage(document);
|
||||
if (setRoute) setRoute(document);
|
||||
} else {
|
||||
setPage("");
|
||||
if (setRoute) setRoute("");
|
||||
}
|
||||
}
|
||||
|
||||
return <Scrollable
|
||||
autoscroll={false}
|
||||
sx={{
|
||||
maxWidth: "1024px",
|
||||
height: "calc(100vh - 72px)",
|
||||
flexDirection: "column",
|
||||
margin: "0 auto",
|
||||
p: 1,
|
||||
}}
|
||||
>
|
||||
<Document {...{
|
||||
title: "About",
|
||||
filepath: "/docs/about.md",
|
||||
onExpand: (open: boolean) => { onDocumentExpand('about', open); },
|
||||
expanded: page === 'about',
|
||||
sessionId,
|
||||
submitQuery: submitQuery,
|
||||
setSnack,
|
||||
}} />
|
||||
<Document {...{
|
||||
title: "Resume Generation Architecture",
|
||||
filepath: "/docs/resume-generation.md",
|
||||
onExpand: (open: boolean) => { onDocumentExpand('resume-generation', open); },
|
||||
expanded: page === 'resume-generation',
|
||||
sessionId,
|
||||
submitQuery: submitQuery,
|
||||
setSnack,
|
||||
}} />
|
||||
<Document {...{
|
||||
title: "Application Architecture",
|
||||
filepath: "/docs/about-app.md",
|
||||
onExpand: (open: boolean) => { onDocumentExpand('about-app', open); },
|
||||
expanded: page === 'about-app',
|
||||
sessionId,
|
||||
submitQuery: submitQuery,
|
||||
setSnack,
|
||||
}} />
|
||||
</Scrollable>;
|
||||
};
|
||||
|
||||
export {
|
||||
AboutPage
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import React, { ReactElement, JSXElementConstructor, useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||
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';
|
||||
@ -14,20 +14,22 @@ 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 { useTheme } from '@mui/material/styles';
|
||||
import { SxProps } from '@mui/material';
|
||||
|
||||
|
||||
import { ResumeBuilder } from './ResumeBuilder';
|
||||
import { MessageList } from './Message';
|
||||
import { Snack, SeverityType } from './Snack';
|
||||
import { VectorVisualizer } from './VectorVisualizer';
|
||||
import { Controls } from './Controls';
|
||||
import { Conversation, ConversationHandle } from './Conversation';
|
||||
import { ChatQuery, QueryOptions } from './ChatQuery';
|
||||
import { ConversationHandle } from './Conversation';
|
||||
import { QueryOptions } from './ChatQuery';
|
||||
import { Scrollable } from './Scrollable';
|
||||
import { BackstoryTab } from './BackstoryTab';
|
||||
import { Document } from './Document';
|
||||
import { BackstoryPage, BackstoryTabProps } from './BackstoryTab';
|
||||
|
||||
import { connectionBase } from './Global';
|
||||
|
||||
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';
|
||||
@ -37,31 +39,7 @@ import '@fontsource/roboto/400.css';
|
||||
import '@fontsource/roboto/500.css';
|
||||
import '@fontsource/roboto/700.css';
|
||||
|
||||
import MuiMarkdown from 'mui-markdown';
|
||||
|
||||
const getConnectionBase = (loc: any): string => {
|
||||
console.log(`getConnectionBase(${loc})`)
|
||||
if (!loc.host.match(/.*battle-linux.*/)) {
|
||||
return loc.protocol + "//" + loc.host;
|
||||
} else {
|
||||
return loc.protocol + "//battle-linux.ketrenos.com:8912";
|
||||
}
|
||||
}
|
||||
|
||||
const connectionBase = getConnectionBase(window.location);
|
||||
|
||||
interface TabProps {
|
||||
label?: string,
|
||||
path: string,
|
||||
route?: string,
|
||||
children?: any,
|
||||
tabProps?: {
|
||||
label?: string,
|
||||
sx?: SxProps,
|
||||
icon?: string | ReactElement<unknown, string | JSXElementConstructor<any>> | undefined,
|
||||
iconPosition?: "bottom" | "top" | "start" | "end" | undefined
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
@ -76,9 +54,8 @@ const App = () => {
|
||||
const isDesktop = useMediaQuery('(min-width:650px)');
|
||||
const prevIsDesktopRef = useRef<boolean>(isDesktop);
|
||||
const chatRef = useRef<ConversationHandle>(null);
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const snackRef = useRef<any>(null);
|
||||
const [subRoute, setSubRoute] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
if (prevIsDesktopRef.current === isDesktop)
|
||||
@ -101,51 +78,13 @@ const App = () => {
|
||||
setActiveTab(0);
|
||||
};
|
||||
|
||||
const onDocumentExpand = (document: string) => {
|
||||
console.log("Document expanded:", document);
|
||||
}
|
||||
|
||||
const tabs: TabProps[] = useMemo(() => {
|
||||
console.log(document);
|
||||
const backstoryPreamble: MessageList = [
|
||||
{
|
||||
role: 'content',
|
||||
title: 'Welcome to Backstory',
|
||||
disableCopy: true,
|
||||
content: `
|
||||
Backstory is a RAG enabled expert system with access to real-time data running self-hosted
|
||||
(no cloud) versions of industry leading Large and Small Language Models (LLM/SLMs).
|
||||
It was written by James Ketrenos in order to provide answers to
|
||||
questions potential employers may have about his work history.
|
||||
|
||||
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={handleSubmitChatQuery} />
|
||||
<ChatQuery prompt="What programming languages has James used?" tunables={{ enable_tools: false }} submitQuery={handleSubmitChatQuery} />
|
||||
<ChatQuery prompt="What are James' professional strengths?" tunables={{ enable_tools: false }} submitQuery={handleSubmitChatQuery} />
|
||||
<ChatQuery prompt="What are today's headlines on CNBC.com?" tunables={{ enable_tools: true, enable_rag: false, enable_context: false }} submitQuery={handleSubmitChatQuery} />
|
||||
</Box>,
|
||||
<Box sx={{ p: 1 }}>
|
||||
<MuiMarkdown>
|
||||
As with all LLM interactions, the results may not be 100% accurate. If you have questions about my career,
|
||||
I'd love to hear from you. You can send me an email at **james_backstory@ketrenos.com**.
|
||||
</MuiMarkdown>
|
||||
</Box>
|
||||
];
|
||||
|
||||
const tabSx = { flexGrow: 1, fontSize: '1rem' };
|
||||
|
||||
return [{
|
||||
const tabs: BackstoryTabProps[] = useMemo(() => {
|
||||
const homeTab: BackstoryTabProps = {
|
||||
label: "",
|
||||
path: "",
|
||||
tabProps: {
|
||||
label: "Backstory",
|
||||
sx: tabSx,
|
||||
sx: { flexGrow: 1, fontSize: '1rem' },
|
||||
icon:
|
||||
<Avatar sx={{
|
||||
width: 24,
|
||||
@ -156,121 +95,73 @@ const App = () => {
|
||||
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 settingsTab: BackstoryTabProps = {
|
||||
path: "settings",
|
||||
tabProps: {
|
||||
sx: { flexShrink: 1, flexGrow: 0, fontSize: '1rem' },
|
||||
icon: <SettingsIcon />
|
||||
},
|
||||
children: (
|
||||
<Conversation
|
||||
<Scrollable
|
||||
autoscroll={false}
|
||||
sx={{
|
||||
maxWidth: "1024px",
|
||||
height: "calc(100vh - 72px)",
|
||||
}}
|
||||
ref={chatRef}
|
||||
{...{
|
||||
type: "chat",
|
||||
prompt: "What would you like to know about James?",
|
||||
resetLabel: "chat",
|
||||
sessionId,
|
||||
connectionBase,
|
||||
setSnack,
|
||||
preamble: backstoryPreamble,
|
||||
defaultPrompts: backstoryQuestions
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}, {
|
||||
label: "Resume Builder",
|
||||
path: "resume-builder",
|
||||
children: (
|
||||
<ResumeBuilder sx={{
|
||||
flexDirection: "column",
|
||||
margin: "0 auto",
|
||||
height: "calc(100vh - 72px)",
|
||||
overflow: "auto",
|
||||
backgroundColor: "#F5F5F5",
|
||||
display: "flex",
|
||||
flexGrow: 1
|
||||
}} {...{ setSnack, connectionBase, sessionId }}
|
||||
/>
|
||||
)
|
||||
}, {
|
||||
label: "Context Visualizer",
|
||||
path: "context-visualizer",
|
||||
children:
|
||||
<Scrollable
|
||||
sx={{
|
||||
maxWidth: "1024px",
|
||||
height: "calc(100vh - 72px)",
|
||||
}}
|
||||
>
|
||||
<VectorVisualizer sx={{ p: 1 }} {...{ connectionBase, sessionId, setSnack }} />
|
||||
</Scrollable>
|
||||
}, {
|
||||
label: "About",
|
||||
path: "about",
|
||||
children: (
|
||||
<Scrollable
|
||||
autoscroll={false}
|
||||
sx={{
|
||||
maxWidth: "1024px",
|
||||
height: "calc(100vh - 72px)",
|
||||
flexDirection: "column",
|
||||
margin: "0 auto",
|
||||
p: 1,
|
||||
}}
|
||||
>
|
||||
<Document {...{
|
||||
title: "About",
|
||||
filepath: "/docs/about.md",
|
||||
onExpand: () => { onDocumentExpand('about'); },
|
||||
expanded: false,//openDocument === 'about',
|
||||
sessionId,
|
||||
connectionBase,
|
||||
submitQuery: handleSubmitChatQuery,
|
||||
setSnack,
|
||||
}} />
|
||||
<Document {...{
|
||||
title: "Resume Generation Architecture",
|
||||
filepath: "/docs/resume-generation.md",
|
||||
onExpand: () => { onDocumentExpand('resume-generation'); },
|
||||
expanded: false, //openDocument === 'about',
|
||||
sessionId,
|
||||
connectionBase,
|
||||
submitQuery: handleSubmitChatQuery,
|
||||
setSnack,
|
||||
}} />
|
||||
<Document {...{
|
||||
title: "Application Architecture",
|
||||
filepath: "/docs/about-app.md",
|
||||
onExpand: () => { onDocumentExpand('resume-generation'); },
|
||||
expanded: false, //openDocument === 'about-app',
|
||||
sessionId,
|
||||
connectionBase,
|
||||
submitQuery: handleSubmitChatQuery,
|
||||
setSnack,
|
||||
}} />
|
||||
</Scrollable>
|
||||
)
|
||||
}, {
|
||||
path: "settings",
|
||||
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 &&
|
||||
<Controls {...{ sessionId, setSnack, connectionBase }} />
|
||||
}
|
||||
</Scrollable>
|
||||
)
|
||||
}];
|
||||
}, [sessionId, setSnack, isMobile]);
|
||||
p: 1,
|
||||
}}
|
||||
>
|
||||
{sessionId !== undefined &&
|
||||
<ControlsPage {...{ sessionId, setSnack, submitQuery: handleSubmitChatQuery, route: subRoute, setRoute: setSubRoute }} />
|
||||
}
|
||||
</Scrollable>
|
||||
)
|
||||
};
|
||||
|
||||
return [
|
||||
homeTab,
|
||||
resumeBuilderTab,
|
||||
contextVisualizerTab,
|
||||
aboutTab,
|
||||
settingsTab,
|
||||
];
|
||||
}, [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 {
|
||||
@ -300,7 +191,7 @@ const App = () => {
|
||||
setActiveTab(0);
|
||||
} else {
|
||||
window.history.replaceState({}, '', `/${pathParts.join('/')}/${new_session}`);
|
||||
tabs[tabIndex].route = pathParts[2] || "";
|
||||
// tabs[tabIndex].route = pathParts[2] || "";
|
||||
setActiveTab(tabIndex);
|
||||
}
|
||||
setSessionId(new_session);
|
||||
@ -330,7 +221,7 @@ const App = () => {
|
||||
console.log(`Invalid path "${currentPath}" -- redirecting to default`);
|
||||
tabIndex = 0;
|
||||
}
|
||||
tabs[tabIndex].route = pathParts[2] || ""
|
||||
// tabs[tabIndex].route = pathParts[2] || ""
|
||||
setSessionId(path_session);
|
||||
setActiveTab(tabIndex);
|
||||
}
|
||||
@ -497,7 +388,7 @@ const App = () => {
|
||||
</Box>
|
||||
{
|
||||
tabs.map((tab: any, i: number) =>
|
||||
<BackstoryTab key={i} active={i === activeTab}>{tab.children}</BackstoryTab>
|
||||
<BackstoryPage key={i} active={i === activeTab} path={tab.path}>{tab.children}</BackstoryPage>
|
||||
)
|
||||
}
|
||||
</Box>
|
||||
|
@ -1,28 +1,54 @@
|
||||
import React from 'react';
|
||||
import React, { ReactElement, JSXElementConstructor } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import { SxProps, Theme } from '@mui/material';
|
||||
import { ChatSubmitQueryInterface } from './ChatQuery';
|
||||
import { SetSnackType } from './Snack';
|
||||
|
||||
interface BackstoryTabProps {
|
||||
children?: React.ReactNode;
|
||||
interface BackstoryElementProps {
|
||||
sessionId: string | undefined,
|
||||
setSnack: SetSnackType,
|
||||
submitQuery: ChatSubmitQueryInterface,
|
||||
sx?: SxProps<Theme>,
|
||||
className?: string,
|
||||
active: boolean,
|
||||
}
|
||||
|
||||
function BackstoryTab(props: BackstoryTabProps) {
|
||||
const { className, active, children, sx, ...other } = props;
|
||||
interface BackstoryPageProps extends BackstoryElementProps {
|
||||
route?: string,
|
||||
setRoute?: (route: string) => void,
|
||||
};
|
||||
|
||||
interface BackstoryTabProps {
|
||||
label?: string,
|
||||
path: string,
|
||||
children?: ReactElement<BackstoryPageProps>,
|
||||
active?: boolean,
|
||||
className?: string,
|
||||
tabProps?: {
|
||||
label?: string,
|
||||
sx?: SxProps,
|
||||
icon?: string | ReactElement<unknown, string | JSXElementConstructor<any>> | undefined,
|
||||
iconPosition?: "bottom" | "top" | "start" | "end" | undefined
|
||||
}
|
||||
};
|
||||
|
||||
function BackstoryPage(props: BackstoryTabProps) {
|
||||
const { className, active, children } = props;
|
||||
|
||||
return (
|
||||
<Box
|
||||
className={ className || "BackstoryTab"}
|
||||
sx={{ "display": active ? "flex": "none", ...sx }}
|
||||
{...other}
|
||||
sx={{ "display": active ? "flex" : "none", p: 0, m: 0, borders: "none" }}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export type {
|
||||
BackstoryPageProps,
|
||||
BackstoryTabProps,
|
||||
BackstoryElementProps,
|
||||
};
|
||||
|
||||
export {
|
||||
BackstoryTab
|
||||
BackstoryPage
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { SxProps, Theme } from '@mui/material';
|
||||
@ -20,12 +20,11 @@ interface ChatBubbleProps {
|
||||
title?: string;
|
||||
expanded?: boolean;
|
||||
expandable?: boolean;
|
||||
onExpand?: () => void;
|
||||
onExpand?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
function ChatBubble(props: ChatBubbleProps) {
|
||||
const { role, children, sx, className, title, onExpand, expandable }: ChatBubbleProps = props;
|
||||
const [expanded, setExpanded] = useState<boolean>((props.expanded === undefined) ? true : props.expanded);
|
||||
const { role, children, sx, className, title, onExpand, expandable, expanded } = props;
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
@ -33,12 +32,12 @@ function ChatBubble(props: ChatBubbleProps) {
|
||||
const defaultStyle = {
|
||||
padding: theme.spacing(1, 2),
|
||||
fontSize: '0.875rem',
|
||||
alignSelf: 'flex-start', // Left-aligned is used by default
|
||||
alignSelf: 'flex-start',
|
||||
maxWidth: '100%',
|
||||
minWidth: '100%',
|
||||
height: 'fit-content',
|
||||
'& > *': {
|
||||
color: 'inherit', // Children inherit 'color' from parent
|
||||
color: 'inherit',
|
||||
overflow: 'hidden',
|
||||
m: 0,
|
||||
},
|
||||
@ -46,130 +45,151 @@ function ChatBubble(props: ChatBubbleProps) {
|
||||
mb: 0,
|
||||
m: 0,
|
||||
p: 0,
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const styles: any = {
|
||||
'assistant': {
|
||||
assistant: {
|
||||
...defaultStyle,
|
||||
backgroundColor: theme.palette.primary.main, // Midnight Blue (#1A2536)
|
||||
border: `1px solid ${theme.palette.secondary.main}`, // Dusty Teal (#4A7A7D)
|
||||
borderRadius: `${defaultRadius} ${defaultRadius} ${defaultRadius} 0`, // Rounded, flat bottom-left for assistant
|
||||
color: theme.palette.primary.contrastText, // Warm Gray (#D3CDBF) for text
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
border: `1px solid ${theme.palette.secondary.main}`,
|
||||
borderRadius: `${defaultRadius} ${defaultRadius} ${defaultRadius} 0`,
|
||||
color: theme.palette.primary.contrastText,
|
||||
},
|
||||
'content': {
|
||||
content: {
|
||||
...defaultStyle,
|
||||
backgroundColor: '#F5F2EA', // Light cream background for easy reading
|
||||
border: `1px solid ${theme.palette.custom.highlight}`, // Golden Ochre border
|
||||
backgroundColor: '#F5F2EA',
|
||||
border: `1px solid ${theme.palette.custom.highlight}`,
|
||||
borderRadius: 0,
|
||||
alignSelf: 'center', // Centered in the chat
|
||||
color: theme.palette.text.primary, // Charcoal Black for maximum readability
|
||||
padding: '8px 8px', // More generous padding for better text framing
|
||||
marginBottom: '0px', // Space between content and conversation
|
||||
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.05)', // Subtle elevation
|
||||
fontSize: '0.9rem', // Slightly smaller than default
|
||||
lineHeight: '1.3', // More compact line height
|
||||
fontFamily: theme.typography.fontFamily, // Consistent font with your theme
|
||||
alignSelf: 'center',
|
||||
color: theme.palette.text.primary,
|
||||
padding: '8px 8px',
|
||||
marginBottom: '0px',
|
||||
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.05)',
|
||||
fontSize: '0.9rem',
|
||||
lineHeight: '1.3',
|
||||
fontFamily: theme.typography.fontFamily,
|
||||
},
|
||||
'error': {
|
||||
error: {
|
||||
...defaultStyle,
|
||||
backgroundColor: '#F8E7E7', // Soft light red background
|
||||
border: `1px solid #D83A3A`, // Prominent red border
|
||||
backgroundColor: '#F8E7E7',
|
||||
border: `1px solid #D83A3A`,
|
||||
borderRadius: defaultRadius,
|
||||
maxWidth: '90%',
|
||||
minWidth: '90%',
|
||||
alignSelf: 'center',
|
||||
color: '#8B2525', // Deep red text for good contrast
|
||||
color: '#8B2525',
|
||||
padding: '10px 16px',
|
||||
boxShadow: '0 1px 3px rgba(216, 58, 58, 0.15)', // Subtle shadow with red tint
|
||||
boxShadow: '0 1px 3px rgba(216, 58, 58, 0.15)',
|
||||
},
|
||||
'fact-check': 'qualifications',
|
||||
'job-description': 'content',
|
||||
'job-requirements': 'qualifications',
|
||||
'info': {
|
||||
info: {
|
||||
...defaultStyle,
|
||||
backgroundColor: '#BFD8D8', // Softened Dusty Teal
|
||||
border: `1px solid ${theme.palette.secondary.main}`, // Dusty Teal
|
||||
backgroundColor: '#BFD8D8',
|
||||
border: `1px solid ${theme.palette.secondary.main}`,
|
||||
borderRadius: defaultRadius,
|
||||
color: theme.palette.text.primary, // Charcoal Black (#2E2E2E) — much better contrast
|
||||
color: theme.palette.text.primary,
|
||||
opacity: 0.95,
|
||||
},
|
||||
'processing': "status",
|
||||
'qualifications': {
|
||||
processing: 'status',
|
||||
qualifications: {
|
||||
...defaultStyle,
|
||||
backgroundColor: theme.palette.primary.light, // Lighter shade, e.g., Soft Blue (#2A3B56)
|
||||
border: `1px solid ${theme.palette.secondary.main}`, // Keep Dusty Teal (#4A7A7D) for contrast
|
||||
borderRadius: `${defaultRadius} ${defaultRadius} ${defaultRadius} 0`, // Unchanged
|
||||
color: theme.palette.primary.contrastText, // Warm Gray (#D3CDBF) for readable text
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
border: `1px solid ${theme.palette.secondary.main}`,
|
||||
borderRadius: `${defaultRadius} ${defaultRadius} ${defaultRadius} 0`,
|
||||
color: theme.palette.primary.contrastText,
|
||||
},
|
||||
'resume': 'content',
|
||||
'searching': 'status',
|
||||
'status': {
|
||||
resume: 'content',
|
||||
searching: 'status',
|
||||
status: {
|
||||
...defaultStyle,
|
||||
backgroundColor: 'rgba(74, 122, 125, 0.15)', // Translucent dusty teal
|
||||
border: `1px solid ${theme.palette.secondary.light}`, // Lighter dusty teal
|
||||
backgroundColor: 'rgba(74, 122, 125, 0.15)',
|
||||
border: `1px solid ${theme.palette.secondary.light}`,
|
||||
borderRadius: '4px',
|
||||
maxWidth: '75%',
|
||||
minWidth: '75%',
|
||||
alignSelf: 'center',
|
||||
color: theme.palette.secondary.dark, // Darker dusty teal for text
|
||||
fontWeight: 500, // Slightly bolder than normal
|
||||
fontSize: '0.95rem', // Slightly smaller
|
||||
color: theme.palette.secondary.dark,
|
||||
fontWeight: 500,
|
||||
fontSize: '0.95rem',
|
||||
padding: '8px 12px',
|
||||
opacity: 0.9,
|
||||
transition: 'opacity 0.3s ease-in-out', // Smooth fade effect for appearing/disappearing
|
||||
transition: 'opacity 0.3s ease-in-out',
|
||||
},
|
||||
'streaming': "assistant",
|
||||
'system': {
|
||||
streaming: 'assistant',
|
||||
system: {
|
||||
...defaultStyle,
|
||||
backgroundColor: '#EDEAE0', // Soft warm gray that plays nice with #D3CDBF
|
||||
border: `1px dashed ${theme.palette.custom.highlight}`, // Golden Ochre
|
||||
backgroundColor: '#EDEAE0',
|
||||
border: `1px dashed ${theme.palette.custom.highlight}`,
|
||||
borderRadius: defaultRadius,
|
||||
maxWidth: '90%',
|
||||
minWidth: '90%',
|
||||
alignSelf: 'center',
|
||||
color: theme.palette.text.primary, // Charcoal Black
|
||||
color: theme.palette.text.primary,
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
'thinking': "status",
|
||||
'user': {
|
||||
thinking: 'status',
|
||||
user: {
|
||||
...defaultStyle,
|
||||
backgroundColor: theme.palette.background.default, // Warm Gray (#D3CDBF)
|
||||
border: `1px solid ${theme.palette.custom.highlight}`, // Golden Ochre (#D4A017)
|
||||
borderRadius: `${defaultRadius} ${defaultRadius} 0 ${defaultRadius}`, // Rounded, flat bottom-right for user
|
||||
alignSelf: 'flex-end', // Right-aligned for user
|
||||
color: theme.palette.primary.main, // Midnight Blue (#1A2536) for text
|
||||
backgroundColor: theme.palette.background.default,
|
||||
border: `1px solid ${theme.palette.custom.highlight}`,
|
||||
borderRadius: `${defaultRadius} ${defaultRadius} 0 ${defaultRadius}`,
|
||||
alignSelf: 'flex-end',
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
};
|
||||
|
||||
// Resolve string references in styles
|
||||
for (const [key, value] of Object.entries(styles)) {
|
||||
if (typeof (value) === "string") {
|
||||
(styles as any)[key] = styles[value];
|
||||
if (typeof value === 'string') {
|
||||
styles[key] = styles[value];
|
||||
}
|
||||
}
|
||||
|
||||
const icons: any = {
|
||||
"error": <ErrorOutline color='error' />,
|
||||
"info": <InfoOutline color='info' />,
|
||||
"processing": <LocationSearchingIcon />,
|
||||
// "streaming": <Stream />,
|
||||
"searching": <Memory />,
|
||||
"thinking": <Psychology />,
|
||||
"tooling": <LocationSearchingIcon />,
|
||||
error: <ErrorOutline color="error" />,
|
||||
info: <InfoOutline color="info" />,
|
||||
processing: <LocationSearchingIcon />,
|
||||
searching: <Memory />,
|
||||
thinking: <Psychology />,
|
||||
tooling: <LocationSearchingIcon />,
|
||||
};
|
||||
|
||||
// Render Accordion for expandable content
|
||||
if (expandable || (role === 'content' && title)) {
|
||||
// Determine if Accordion is controlled
|
||||
const isControlled = typeof expanded === 'boolean' && typeof onExpand === 'function';
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
expanded={expanded}
|
||||
expanded={isControlled ? expanded : undefined} // Omit expanded prop for uncontrolled
|
||||
defaultExpanded={expanded} // Default to collapsed for uncontrolled Accordion
|
||||
className={className}
|
||||
onChange={() => { onExpand && onExpand(); setExpanded(!expanded); }}
|
||||
onChange={(_event, newExpanded) => {
|
||||
if (isControlled && onExpand) {
|
||||
onExpand(newExpanded); // Call onExpand with new state
|
||||
}
|
||||
}}
|
||||
sx={{ ...styles[role], ...sx }}
|
||||
>
|
||||
<AccordionSummary
|
||||
expandIcon={<ExpandMoreIcon />}
|
||||
slotProps={{ content: { sx: { fontWeight: 'bold', fontSize: '1.1rem', m: 0, p: 0, display: 'flex', justifyItems: 'center' } } }}
|
||||
slotProps={{
|
||||
content: {
|
||||
sx: {
|
||||
fontWeight: 'bold',
|
||||
fontSize: '1.1rem',
|
||||
m: 0,
|
||||
p: 0,
|
||||
display: 'flex',
|
||||
justifyItems: 'center',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{title || ""}
|
||||
{title || ''}
|
||||
</AccordionSummary>
|
||||
<AccordionDetails sx={{ mt: 0, mb: 0, p: 0, pl: 2, pr: 2 }}>
|
||||
{children}
|
||||
@ -178,10 +198,20 @@ function ChatBubble(props: ChatBubbleProps) {
|
||||
);
|
||||
}
|
||||
|
||||
// Render non-expandable content
|
||||
return (
|
||||
<Box className={className} sx={{ ...(role in styles ? styles[role] : styles["status"]), gap: 1, display: "flex", ...sx, flexDirection: "row" }}>
|
||||
<Box
|
||||
className={className}
|
||||
sx={{
|
||||
...(role in styles ? styles[role] : styles['status']),
|
||||
gap: 1,
|
||||
display: 'flex',
|
||||
...sx,
|
||||
flexDirection: 'row',
|
||||
}}
|
||||
>
|
||||
{icons[role] !== undefined && icons[role]}
|
||||
<Box sx={{ p: 0, m: 0, gap: 0, display: "flex", flexGrow: 1, flexDirection: "column" }}>
|
||||
<Box sx={{ p: 0, m: 0, gap: 0, display: 'flex', flexGrow: 1, flexDirection: 'column' }}>
|
||||
{children}
|
||||
</Box>
|
||||
</Box>
|
||||
|
@ -7,10 +7,12 @@ type QueryOptions = {
|
||||
enable_context?: boolean,
|
||||
};
|
||||
|
||||
type ChatSubmitQueryInterface = (prompt: string, tunables?: QueryOptions) => void;
|
||||
|
||||
interface ChatQueryInterface {
|
||||
prompt: string,
|
||||
tunables?: QueryOptions,
|
||||
submitQuery?: (prompt: string, tunables?: QueryOptions) => void
|
||||
submitQuery?: ChatSubmitQueryInterface
|
||||
}
|
||||
|
||||
const ChatQuery = (props : ChatQueryInterface) => {
|
||||
@ -39,6 +41,7 @@ const ChatQuery = (props : ChatQueryInterface) => {
|
||||
export type {
|
||||
ChatQueryInterface,
|
||||
QueryOptions,
|
||||
ChatSubmitQueryInterface,
|
||||
};
|
||||
|
||||
export {
|
||||
|
@ -14,7 +14,9 @@ import Typography from '@mui/material/Typography';
|
||||
// import ResetIcon from '@mui/icons-material/History';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
|
||||
import { SetSnackType } from './Snack';
|
||||
import { connectionBase } from './Global';
|
||||
import { BackstoryPageProps } from './BackstoryTab';
|
||||
import { restyle } from 'plotly.js';
|
||||
|
||||
interface ServerTunables {
|
||||
system_prompt: string,
|
||||
@ -32,12 +34,6 @@ type Tool = {
|
||||
returns?: any
|
||||
};
|
||||
|
||||
interface ControlsParams {
|
||||
connectionBase: string,
|
||||
sessionId: string | undefined,
|
||||
setSnack: SetSnackType,
|
||||
};
|
||||
|
||||
type GPUInfo = {
|
||||
name: string,
|
||||
memory: number,
|
||||
@ -90,7 +86,7 @@ const SystemInfoComponent: React.FC<{ systemInfo: SystemInfo | undefined }> = ({
|
||||
return <div className="SystemInfo">{systemElements}</div>;
|
||||
};
|
||||
|
||||
const Controls = ({ sessionId, setSnack, connectionBase }: ControlsParams) => {
|
||||
const ControlsPage = ({ sessionId, setSnack }: BackstoryPageProps) => {
|
||||
const [editSystemPrompt, setEditSystemPrompt] = useState<string>("");
|
||||
const [systemInfo, setSystemInfo] = useState<SystemInfo | undefined>(undefined);
|
||||
const [tools, setTools] = useState<Tool[]>([]);
|
||||
@ -126,7 +122,7 @@ const Controls = ({ sessionId, setSnack, connectionBase }: ControlsParams) => {
|
||||
|
||||
sendSystemPrompt(systemPrompt);
|
||||
|
||||
}, [systemPrompt, connectionBase, sessionId, setSnack, serverTunables]);
|
||||
}, [systemPrompt, sessionId, setSnack, serverTunables]);
|
||||
|
||||
useEffect(() => {
|
||||
if (serverTunables === undefined || messageHistoryLength === serverTunables.message_history_length || !messageHistoryLength || sessionId === undefined) {
|
||||
@ -157,7 +153,7 @@ const Controls = ({ sessionId, setSnack, connectionBase }: ControlsParams) => {
|
||||
|
||||
sendMessageHistoryLength(messageHistoryLength);
|
||||
|
||||
}, [messageHistoryLength, setMessageHistoryLength, connectionBase, sessionId, setSnack, serverTunables]);
|
||||
}, [messageHistoryLength, setMessageHistoryLength, sessionId, setSnack, serverTunables]);
|
||||
|
||||
const reset = async (types: ("rags" | "tools" | "history" | "system_prompt" | "message_history_length")[], message: string = "Update successful.") => {
|
||||
try {
|
||||
@ -220,7 +216,7 @@ const Controls = ({ sessionId, setSnack, connectionBase }: ControlsParams) => {
|
||||
console.error('Error obtaining system information:', error);
|
||||
setSnack("Unable to obtain system information.", "error");
|
||||
});
|
||||
}, [systemInfo, setSystemInfo, connectionBase, setSnack, sessionId])
|
||||
}, [systemInfo, setSystemInfo, setSnack, sessionId])
|
||||
|
||||
useEffect(() => {
|
||||
setEditSystemPrompt(systemPrompt.trim());
|
||||
@ -294,7 +290,7 @@ const Controls = ({ sessionId, setSnack, connectionBase }: ControlsParams) => {
|
||||
}
|
||||
|
||||
fetchTunables();
|
||||
}, [sessionId, connectionBase, setServerTunables, setSystemPrompt, setMessageHistoryLength, serverTunables, setTools, setRags]);
|
||||
}, [sessionId, setServerTunables, setSystemPrompt, setMessageHistoryLength, serverTunables, setTools, setRags]);
|
||||
|
||||
const toggle = async (type: string, index: number) => {
|
||||
switch (type) {
|
||||
@ -433,11 +429,6 @@ const Controls = ({ sessionId, setSnack, connectionBase }: ControlsParams) => {
|
||||
</div>);
|
||||
}
|
||||
|
||||
|
||||
export type {
|
||||
ControlsParams
|
||||
};
|
||||
|
||||
export {
|
||||
Controls
|
||||
ControlsPage
|
||||
};
|
@ -8,13 +8,14 @@ import { SxProps, Theme } from '@mui/material';
|
||||
import PropagateLoader from "react-spinners/PropagateLoader";
|
||||
|
||||
import { Message, MessageList, MessageData } from './Message';
|
||||
import { SetSnackType } from './Snack';
|
||||
import { ContextStatus } from './ContextStatus';
|
||||
import { Scrollable } from './Scrollable';
|
||||
import { DeleteConfirmation } from './DeleteConfirmation';
|
||||
import { QueryOptions } from './ChatQuery';
|
||||
import './Conversation.css';
|
||||
import { BackstoryTextField } from './BackstoryTextField';
|
||||
import { BackstoryElementProps } from './BackstoryTab';
|
||||
import { connectionBase } from './Global';
|
||||
|
||||
const loadingMessage: MessageData = { "role": "status", "content": "Establishing connection with server..." };
|
||||
|
||||
@ -44,7 +45,7 @@ interface BackstoryMessage {
|
||||
timestamp: string;
|
||||
};
|
||||
|
||||
interface ConversationProps {
|
||||
interface ConversationProps extends BackstoryElementProps {
|
||||
className?: string, // Override default className
|
||||
type: ConversationMode, // Type of Conversation chat
|
||||
prompt?: string, // Prompt to display in TextField input
|
||||
@ -52,9 +53,6 @@ interface ConversationProps {
|
||||
resetAction?: () => void, // Callback when Reset is pressed
|
||||
multiline?: boolean, // Render TextField as multiline or not
|
||||
resetLabel?: string, // Label to put on Reset button
|
||||
connectionBase: string, // Base URL for fetch() calls
|
||||
sessionId?: string, // Session ID for fetch() calls
|
||||
setSnack: SetSnackType, // Callback to display snack popups
|
||||
defaultPrompts?: React.ReactElement[], // Set of Elements to display after the TextField
|
||||
defaultQuery?: string, // Default text to populate the TextField input
|
||||
emptyPrompt?: string, // If input is not shown and an action is taken, send this prompt
|
||||
@ -68,26 +66,26 @@ interface ConversationProps {
|
||||
};
|
||||
|
||||
const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
|
||||
className,
|
||||
type,
|
||||
prompt,
|
||||
emptyPrompt,
|
||||
actionLabel,
|
||||
resetAction,
|
||||
multiline,
|
||||
resetLabel,
|
||||
connectionBase,
|
||||
sessionId,
|
||||
setSnack,
|
||||
className,
|
||||
defaultPrompts,
|
||||
hideDefaultPrompts,
|
||||
defaultQuery,
|
||||
preamble,
|
||||
emptyPrompt,
|
||||
hideDefaultPrompts,
|
||||
hidePreamble,
|
||||
messageFilter,
|
||||
messages,
|
||||
multiline,
|
||||
onResponse,
|
||||
prompt,
|
||||
preamble,
|
||||
resetAction,
|
||||
resetLabel,
|
||||
sessionId,
|
||||
setSnack,
|
||||
submitQuery,
|
||||
sx,
|
||||
onResponse
|
||||
type,
|
||||
}: ConversationProps, ref) => {
|
||||
const [query, setQuery] = useState<string>("");
|
||||
const [contextUsedPercentage, setContextUsedPercentage] = useState<number>(0);
|
||||
@ -133,7 +131,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
|
||||
}
|
||||
};
|
||||
fetchContextStatus();
|
||||
}, [setContextStatus, connectionBase, setSnack, sessionId, type]);
|
||||
}, [setContextStatus, setSnack, sessionId, type]);
|
||||
|
||||
/* Transform the 'Conversation' by filtering via callback, then adding
|
||||
* preamble and messages based on whether the conversation
|
||||
@ -213,7 +211,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
|
||||
setSnack("Unable to obtain chat history.", "error");
|
||||
}
|
||||
};
|
||||
}, [setConversation, updateContextStatus, connectionBase, setSnack, type, sessionId]);
|
||||
}, [setConversation, updateContextStatus, setSnack, type, sessionId]);
|
||||
|
||||
// Set the initial chat history to "loading" or the welcome message if loaded.
|
||||
useEffect(() => {
|
||||
@ -513,16 +511,16 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
|
||||
>
|
||||
{
|
||||
filteredConversation.map((message, index) =>
|
||||
<Message key={index} {...{ sendQuery, message, connectionBase, sessionId, setSnack }} />
|
||||
<Message key={index} expanded={message.expanded === undefined ? true : message.expanded} {...{ sendQuery, message, connectionBase, sessionId, setSnack, submitQuery }} />
|
||||
)
|
||||
}
|
||||
{
|
||||
processingMessage !== undefined &&
|
||||
<Message {...{ sendQuery, connectionBase, sessionId, setSnack, message: processingMessage }} />
|
||||
<Message {...{ sendQuery, connectionBase, sessionId, setSnack, message: processingMessage, submitQuery }} />
|
||||
}
|
||||
{
|
||||
streamingMessage !== undefined &&
|
||||
<Message {...{ sendQuery, connectionBase, sessionId, setSnack, message: streamingMessage }} />
|
||||
<Message {...{ sendQuery, connectionBase, sessionId, setSnack, message: streamingMessage, submitQuery }} />
|
||||
}
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
|
@ -1,21 +1,19 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Message, MessageSubmitQuery } from './Message';
|
||||
import { SetSnackType } from './Snack';
|
||||
import { Message } from './Message';
|
||||
import { BackstoryElementProps } from './BackstoryTab';
|
||||
import { connectionBase } from './Global';
|
||||
|
||||
interface DocumentProps {
|
||||
interface DocumentProps extends BackstoryElementProps {
|
||||
title: string;
|
||||
expanded?: boolean;
|
||||
filepath: string;
|
||||
content?: string;
|
||||
setSnack: SetSnackType;
|
||||
submitQuery?: MessageSubmitQuery;
|
||||
connectionBase: string;
|
||||
disableCopy?: boolean;
|
||||
onExpand?: () => void;
|
||||
onExpand?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
const Document = (props: DocumentProps) => {
|
||||
const { setSnack, submitQuery, connectionBase, filepath, content, title, expanded, disableCopy, onExpand } = props;
|
||||
const { setSnack, submitQuery, filepath, content, title, expanded, disableCopy, onExpand, sessionId } = props;
|
||||
|
||||
const [document, setDocument] = useState<string>("");
|
||||
|
||||
@ -63,6 +61,7 @@ const Document = (props: DocumentProps) => {
|
||||
expanded,
|
||||
disableCopy,
|
||||
onExpand,
|
||||
sessionId,
|
||||
}} />
|
||||
);
|
||||
};
|
||||
|
13
frontend/src/Global.tsx
Normal file
13
frontend/src/Global.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
const getConnectionBase = (loc: any): string => {
|
||||
if (!loc.host.match(/.*battle-linux.*/)) {
|
||||
return loc.protocol + "//" + loc.host;
|
||||
} else {
|
||||
return loc.protocol + "//battle-linux.ketrenos.com:8912";
|
||||
}
|
||||
}
|
||||
|
||||
const connectionBase = getConnectionBase(window.location);
|
||||
|
||||
export {
|
||||
connectionBase
|
||||
};
|
69
frontend/src/HomePage.tsx
Normal file
69
frontend/src/HomePage.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||
import Box from '@mui/material/Box';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import MuiMarkdown from 'mui-markdown';
|
||||
|
||||
import { BackstoryPageProps } from './BackstoryTab';
|
||||
import { Conversation, ConversationHandle } from './Conversation';
|
||||
import { ChatQuery } from './ChatQuery';
|
||||
import { MessageList } from './Message';
|
||||
|
||||
const HomePage = forwardRef<ConversationHandle, BackstoryPageProps>((props: BackstoryPageProps, ref) => {
|
||||
const { sessionId, setSnack, submitQuery } = props;
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||
|
||||
const backstoryPreamble: MessageList = [
|
||||
{
|
||||
role: 'content',
|
||||
title: 'Welcome to Backstory',
|
||||
disableCopy: true,
|
||||
content: `
|
||||
Backstory is a RAG enabled expert system with access to real-time data running self-hosted
|
||||
(no cloud) versions of industry leading Large and Small Language Models (LLM/SLMs).
|
||||
It was written by James Ketrenos in order to provide answers to
|
||||
questions potential employers may have about his work history.
|
||||
|
||||
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} />
|
||||
</Box>,
|
||||
<Box sx={{ p: 1 }}>
|
||||
<MuiMarkdown>
|
||||
As with all LLM interactions, the results may not be 100% accurate. If you have questions about my career,
|
||||
I'd love to hear from you. You can send me an email at **james_backstory@ketrenos.com**.
|
||||
</MuiMarkdown>
|
||||
</Box>
|
||||
];
|
||||
|
||||
return <Conversation
|
||||
sx={{
|
||||
maxWidth: "1024px",
|
||||
height: "calc(100vh - 72px)",
|
||||
}}
|
||||
ref={ref}
|
||||
{...{
|
||||
type: "chat",
|
||||
prompt: "What would you like to know about James?",
|
||||
resetLabel: "chat",
|
||||
sessionId,
|
||||
setSnack,
|
||||
preamble: backstoryPreamble,
|
||||
defaultPrompts: backstoryQuestions,
|
||||
submitQuery,
|
||||
}}
|
||||
/>;
|
||||
});
|
||||
|
||||
export {
|
||||
HomePage
|
||||
};
|
@ -28,6 +28,7 @@ import { VectorVisualizer } from './VectorVisualizer';
|
||||
import { SetSnackType } from './Snack';
|
||||
import { CopyBubble } from './CopyBubble';
|
||||
import { Scrollable } from './Scrollable';
|
||||
import { BackstoryElementProps } from './BackstoryTab';
|
||||
|
||||
type MessageRoles =
|
||||
'assistant' |
|
||||
@ -83,19 +84,13 @@ interface MessageMetaData {
|
||||
setSnack: SetSnackType,
|
||||
}
|
||||
|
||||
type MessageSubmitQuery = (text: string) => void;
|
||||
|
||||
type MessageList = MessageData[];
|
||||
|
||||
interface MessageProps {
|
||||
interface MessageProps extends BackstoryElementProps {
|
||||
sx?: SxProps<Theme>,
|
||||
message: MessageData,
|
||||
// expanded?: boolean, // Provided as part of MessageData
|
||||
onExpand?: () => void,
|
||||
submitQuery?: MessageSubmitQuery,
|
||||
sessionId?: string,
|
||||
connectionBase: string,
|
||||
setSnack: SetSnackType,
|
||||
expanded?: boolean,
|
||||
onExpand?: (open: boolean) => void,
|
||||
className?: string,
|
||||
};
|
||||
|
||||
@ -242,12 +237,12 @@ const MessageMeta = (props: MessageMetaProps) => {
|
||||
};
|
||||
|
||||
const Message = (props: MessageProps) => {
|
||||
const { message, submitQuery, sx, className, onExpand } = props;
|
||||
const [expanded, setExpanded] = useState<boolean>(false);
|
||||
const { message, submitQuery, sx, className, onExpand, expanded, sessionId, setSnack } = props;
|
||||
const [metaExpanded, setMetaExpanded] = useState<boolean>(false);
|
||||
const textFieldRef = useRef(null);
|
||||
|
||||
const handleExpandClick = () => {
|
||||
setExpanded(!expanded);
|
||||
const handleMetaExpandClick = () => {
|
||||
setMetaExpanded(!metaExpanded);
|
||||
};
|
||||
|
||||
if (message === undefined) {
|
||||
@ -266,6 +261,7 @@ const Message = (props: MessageProps) => {
|
||||
className={className || "Message"}
|
||||
{...message}
|
||||
onExpand={onExpand}
|
||||
expanded={expanded}
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
@ -291,7 +287,7 @@ const Message = (props: MessageProps) => {
|
||||
overflow: "auto", /* Handles scrolling for the div */
|
||||
}}
|
||||
>
|
||||
<StyledMarkdown {...{ content: formattedContent, submitQuery }} />
|
||||
<StyledMarkdown {...{ content: formattedContent, submitQuery, sessionId, setSnack }} />
|
||||
</Scrollable>
|
||||
:
|
||||
<Typography
|
||||
@ -307,12 +303,12 @@ const Message = (props: MessageProps) => {
|
||||
{(message.disableCopy === undefined || message.disableCopy === false) && ["assistant", "content"].includes(message.role) && <CopyBubble content={message.content} />}
|
||||
{message.metadata && (
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||
<Button variant="text" onClick={handleExpandClick} sx={{ color: "darkgrey", p: 0 }}>
|
||||
<Button variant="text" onClick={handleMetaExpandClick} sx={{ color: "darkgrey", p: 0 }}>
|
||||
LLM information for this query
|
||||
</Button>
|
||||
<ExpandMore
|
||||
expand={expanded}
|
||||
onClick={handleExpandClick}
|
||||
expand={metaExpanded}
|
||||
onClick={handleMetaExpandClick}
|
||||
aria-expanded={expanded}
|
||||
aria-label="show more"
|
||||
>
|
||||
@ -322,7 +318,7 @@ const Message = (props: MessageProps) => {
|
||||
)}
|
||||
</CardActions>
|
||||
{message.metadata && <>
|
||||
<Collapse in={expanded} timeout="auto" unmountOnExit>
|
||||
<Collapse in={metaExpanded} timeout="auto" unmountOnExit>
|
||||
<CardContent>
|
||||
<MessageMeta messageProps={props} metadata={message.metadata} />
|
||||
</CardContent>
|
||||
@ -337,7 +333,6 @@ export type {
|
||||
MessageList,
|
||||
MessageData,
|
||||
MessageRoles,
|
||||
MessageSubmitQuery
|
||||
};
|
||||
|
||||
export {
|
||||
|
6
frontend/src/ResumeBuilderPage.css
Normal file
6
frontend/src/ResumeBuilderPage.css
Normal file
@ -0,0 +1,6 @@
|
||||
.ResumeBuilder .JsonViewScrollable {
|
||||
min-height: unset !important;
|
||||
max-height: 30rem !important;
|
||||
border: 1px solid orange;
|
||||
overflow-x: auto !important;
|
||||
}
|
@ -4,21 +4,14 @@ import {
|
||||
Tab,
|
||||
Box,
|
||||
} from '@mui/material';
|
||||
import { SxProps, Theme } from '@mui/material';
|
||||
import { SxProps } from '@mui/material';
|
||||
|
||||
import { ChatQuery } from './ChatQuery';
|
||||
import { MessageList, MessageData } from './Message';
|
||||
import { SetSnackType } from './Snack';
|
||||
import { Conversation } from './Conversation';
|
||||
import { BackstoryPageProps } from './BackstoryTab';
|
||||
|
||||
import './ResumeBuilder.css';
|
||||
|
||||
interface ResumeBuilderProps {
|
||||
connectionBase: string,
|
||||
sessionId: string | undefined,
|
||||
setSnack: SetSnackType,
|
||||
sx?: SxProps<Theme>;
|
||||
};
|
||||
import './ResumeBuilderPage.css';
|
||||
|
||||
/**
|
||||
* ResumeBuilder component
|
||||
@ -26,12 +19,11 @@ interface ResumeBuilderProps {
|
||||
* A responsive component that displays job descriptions, generated resumes and fact checks
|
||||
* with different layouts for mobile and desktop views.
|
||||
*/
|
||||
const ResumeBuilder: React.FC<ResumeBuilderProps> = ({
|
||||
const ResumeBuilderPage: React.FC<BackstoryPageProps> = ({
|
||||
sx,
|
||||
connectionBase,
|
||||
sessionId,
|
||||
setSnack
|
||||
|
||||
setSnack,
|
||||
submitQuery,
|
||||
}) => {
|
||||
// State for editing job description
|
||||
const [hasJobDescription, setHasJobDescription] = useState<boolean>(false);
|
||||
@ -87,7 +79,7 @@ const ResumeBuilder: React.FC<ResumeBuilderProps> = ({
|
||||
messages[3].role = 'job-requirements';
|
||||
messages[3].title = 'Job Requirements';
|
||||
messages[3].disableCopy = false;
|
||||
messages[3].expanded = true;
|
||||
messages[3].expanded = false;
|
||||
messages[3].expandable = true;
|
||||
}
|
||||
|
||||
@ -136,7 +128,7 @@ const ResumeBuilder: React.FC<ResumeBuilderProps> = ({
|
||||
messages[1].role = 'fact-check';
|
||||
messages[1].title = 'Fact Check';
|
||||
messages[1].disableCopy = false;
|
||||
messages[1].expanded = false;
|
||||
messages[1].expanded = true;
|
||||
messages[1].expandable = true;
|
||||
}
|
||||
|
||||
@ -225,8 +217,8 @@ const ResumeBuilder: React.FC<ResumeBuilderProps> = ({
|
||||
resetAction: resetJobDescription,
|
||||
onResponse: jobResponse,
|
||||
sessionId,
|
||||
connectionBase,
|
||||
setSnack,
|
||||
submitQuery,
|
||||
sx,
|
||||
}}
|
||||
/>
|
||||
@ -244,13 +236,13 @@ const ResumeBuilder: React.FC<ResumeBuilderProps> = ({
|
||||
resetAction: resetJobDescription,
|
||||
onResponse: jobResponse,
|
||||
sessionId,
|
||||
connectionBase,
|
||||
setSnack,
|
||||
submitQuery,
|
||||
sx,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
}, [connectionBase, filterJobDescriptionMessages, hasJobDescription, sessionId, setSnack, jobResponse, resetJobDescription, hasFacts, hasResume]);
|
||||
}, [filterJobDescriptionMessages, hasJobDescription, sessionId, setSnack, jobResponse, resetJobDescription, hasFacts, hasResume, submitQuery]);
|
||||
|
||||
/**
|
||||
* Renders the resume view with loading indicator
|
||||
@ -275,8 +267,8 @@ const ResumeBuilder: React.FC<ResumeBuilderProps> = ({
|
||||
onResponse: resumeResponse,
|
||||
resetAction: resetResume,
|
||||
sessionId,
|
||||
connectionBase,
|
||||
setSnack,
|
||||
submitQuery,
|
||||
sx,
|
||||
}}
|
||||
/>
|
||||
@ -292,14 +284,14 @@ const ResumeBuilder: React.FC<ResumeBuilderProps> = ({
|
||||
onResponse: resumeResponse,
|
||||
resetAction: resetResume,
|
||||
sessionId,
|
||||
connectionBase,
|
||||
setSnack,
|
||||
defaultPrompts: resumeQuestions,
|
||||
submitQuery,
|
||||
sx,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
}, [connectionBase, filterResumeMessages, hasFacts, sessionId, setSnack, resumeResponse, resetResume, hasResume]);
|
||||
}, [filterResumeMessages, hasFacts, sessionId, setSnack, resumeResponse, resetResume, hasResume, submitQuery]);
|
||||
|
||||
/**
|
||||
* Renders the fact check view
|
||||
@ -323,12 +315,12 @@ const ResumeBuilder: React.FC<ResumeBuilderProps> = ({
|
||||
resetAction: resetFacts,
|
||||
onResponse: factsResponse,
|
||||
sessionId,
|
||||
connectionBase,
|
||||
submitQuery,
|
||||
setSnack,
|
||||
sx,
|
||||
}}
|
||||
/>
|
||||
}, [connectionBase, sessionId, setSnack, factsResponse, filterFactsMessages, resetFacts, hasResume, hasFacts]);
|
||||
}, [ sessionId, setSnack, factsResponse, filterFactsMessages, resetFacts, hasResume, hasFacts, submitQuery]);
|
||||
|
||||
return (
|
||||
<Box className="ResumeBuilder"
|
||||
@ -369,11 +361,7 @@ const ResumeBuilder: React.FC<ResumeBuilderProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export type {
|
||||
ResumeBuilderProps
|
||||
};
|
||||
|
||||
export {
|
||||
ResumeBuilder
|
||||
ResumeBuilderPage
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { MuiMarkdown } from 'mui-markdown';
|
||||
import { SxProps, useTheme } from '@mui/material/styles';
|
||||
import { Link } from '@mui/material';
|
||||
import { ChatQuery, QueryOptions } from './ChatQuery';
|
||||
import { ChatQuery } from './ChatQuery';
|
||||
import Box from '@mui/material/Box';
|
||||
import JsonView from '@uiw/react-json-view';
|
||||
import { vscodeTheme } from '@uiw/react-json-view/vscode';
|
||||
@ -11,12 +11,12 @@ import { Scrollable } from './Scrollable';
|
||||
import { jsonrepair } from 'jsonrepair';
|
||||
|
||||
import './StyledMarkdown.css';
|
||||
import { BackstoryElementProps } from './BackstoryTab';
|
||||
|
||||
interface StyledMarkdownProps {
|
||||
interface StyledMarkdownProps extends BackstoryElementProps {
|
||||
className?: string,
|
||||
content: string,
|
||||
sx?: SxProps,
|
||||
submitQuery?: (prompt: string, tunables?: QueryOptions) => void,
|
||||
};
|
||||
|
||||
const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProps) => {
|
||||
@ -31,10 +31,13 @@ const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProp
|
||||
if (className === "lang-mermaid") {
|
||||
return <Mermaid className="Mermaid" chart={content} />;
|
||||
}
|
||||
if (className === "lang-markdown") {
|
||||
return <MuiMarkdown children={content} />;
|
||||
}
|
||||
if (className === "lang-json") {
|
||||
try {
|
||||
const fixed = jsonrepair(content);
|
||||
return <Scrollable className="JsonViewScrollable">
|
||||
return <Scrollable autoscroll className="JsonViewScrollable">
|
||||
<JsonView
|
||||
className="JsonView"
|
||||
style={{
|
||||
@ -50,6 +53,7 @@ const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProp
|
||||
displayDataTypes={false}
|
||||
objectSortKeys={false}
|
||||
collapsed={false}
|
||||
shortenTextAfterLength={100}
|
||||
value={JSON.parse(fixed)}>
|
||||
<JsonView.String
|
||||
render={({ children, ...reset }) => {
|
||||
@ -81,17 +85,13 @@ const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProp
|
||||
}
|
||||
}
|
||||
},
|
||||
chatQuery: undefined
|
||||
};
|
||||
|
||||
if (submitQuery) {
|
||||
overrides.ChatQuery = {
|
||||
ChatQuery: {
|
||||
component: ChatQuery,
|
||||
props: {
|
||||
submitQuery,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return <Box
|
||||
className={`MuiMarkdown ${className || ""}`}
|
||||
|
@ -8,16 +8,21 @@ import Button from '@mui/material/Button';
|
||||
import SendIcon from '@mui/icons-material/Send';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Switch from '@mui/material/Switch';
|
||||
import { SxProps, Theme } from '@mui/material';
|
||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import JsonView from '@uiw/react-json-view';
|
||||
|
||||
import { SetSnackType } from './Snack';
|
||||
import { Scrollable } from './Scrollable';
|
||||
import { StyledMarkdown } from './StyledMarkdown';
|
||||
import { connectionBase } from './Global';
|
||||
|
||||
import './VectorVisualizer.css';
|
||||
import { BackstoryPageProps } from './BackstoryTab';
|
||||
|
||||
interface VectorVisualizerProps extends BackstoryPageProps {
|
||||
inline?: boolean;
|
||||
rag?: any;
|
||||
};
|
||||
|
||||
interface Metadata {
|
||||
doc_type?: string;
|
||||
@ -46,14 +51,8 @@ interface PlotData {
|
||||
layout: Partial<Plotly.Layout>;
|
||||
}
|
||||
|
||||
interface VectorVisualizerProps {
|
||||
connectionBase: string;
|
||||
sessionId?: string;
|
||||
setSnack: SetSnackType;
|
||||
inline?: boolean;
|
||||
rag?: any;
|
||||
sx?: SxProps<Theme>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
interface ChromaResult {
|
||||
distances: number[];
|
||||
@ -109,7 +108,7 @@ const symbolMap: Record<string, string> = {
|
||||
};
|
||||
|
||||
const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualizerProps) => {
|
||||
const { setSnack, rag, inline, connectionBase, sessionId, sx } = props;
|
||||
const { setSnack, rag, inline, sessionId, sx, submitQuery } = props;
|
||||
const [plotData, setPlotData] = useState<PlotData | null>(null);
|
||||
const [newQuery, setNewQuery] = useState<string>('');
|
||||
const [newQueryEmbedding, setNewQueryEmbedding] = useState<ChromaResult | undefined>(undefined);
|
||||
@ -151,7 +150,7 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
|
||||
};
|
||||
|
||||
fetchCollection();
|
||||
}, [result, setResult, connectionBase, setSnack, sessionId, view2D])
|
||||
}, [result, setResult, setSnack, sessionId, view2D])
|
||||
|
||||
useEffect(() => {
|
||||
if (!result || !result.embeddings) return;
|
||||
@ -437,7 +436,7 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
|
||||
wordBreak: 'break-all',
|
||||
}}
|
||||
>
|
||||
<StyledMarkdown sx={{ p: 1, pt: 0 }} content={tooltip?.content || "Select a node in the visualization."} />
|
||||
<StyledMarkdown sx={{ p: 1, pt: 0 }} content={tooltip?.content || "Select a node in the visualization."} {...{ sessionId, setSnack, submitQuery }} />
|
||||
</Scrollable>
|
||||
}
|
||||
</Card>
|
||||
@ -448,7 +447,7 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
|
||||
}
|
||||
|
||||
{
|
||||
!inline &&
|
||||
!inline &&
|
||||
<Box className="Query" sx={{ display: "flex", flexDirection: "row", p: 1 }}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
@ -469,8 +468,21 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const VectorVisualizerPage: React.FC<VectorVisualizerProps> = (props: VectorVisualizerProps) => {
|
||||
return <Scrollable
|
||||
sx={{
|
||||
maxWidth: "1024px",
|
||||
height: "calc(100vh - 72px)",
|
||||
}}
|
||||
>
|
||||
<VectorVisualizer {...props} />
|
||||
</Scrollable>;
|
||||
};
|
||||
|
||||
export type { VectorVisualizerProps, ResultData };
|
||||
|
||||
export {
|
||||
VectorVisualizer
|
||||
VectorVisualizer,
|
||||
VectorVisualizerPage
|
||||
};
|
||||
|
@ -11,9 +11,9 @@ scrape_configs:
|
||||
insecure_skip_verify: true
|
||||
|
||||
- job_name: 'backstory-prod'
|
||||
scrape_interval: 30s
|
||||
scrape_interval: 5s
|
||||
metrics_path: /metrics
|
||||
scheme: https
|
||||
scheme: http
|
||||
static_configs:
|
||||
- targets: ['backstory-prod:8911']
|
||||
tls_config:
|
||||
|
@ -197,6 +197,7 @@ class JobDescription(Agent):
|
||||
job_requirements = message.metadata["generate_factual_tailored_resume"]["job_requirements"]["results"]
|
||||
new_message.response = f"```json\n\n{json.dumps(job_requirements, indent=2)}\n```\n"
|
||||
new_message.status = "done"
|
||||
new_message.actions = [ "job_description" ]
|
||||
self.conversation.add(new_message)
|
||||
|
||||
self.system_prompt = system_user_qualifications
|
||||
|
Loading…
x
Reference in New Issue
Block a user