Compare commits

..

No commits in common. "538caba9f4d9f24d96f6c4995f6807460cbfff7d" and "0216515492318a2f1953a223f73ea1c0e329345c" have entirely different histories.

31 changed files with 697 additions and 727 deletions

View File

@ -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

View File

@ -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:

View File

@ -1,46 +1,8 @@
{
"files": {
"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",
"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",
"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",
@ -114,44 +76,12 @@
"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.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"
"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"
},
"entrypoints": [
"static/css/main.3c3ddc39.css",
"static/js/main.b1c48cdf.js"
"static/css/main.8e56f513.css",
"static/js/main.6f07f748.js"
]
}

View File

@ -1,21 +0,0 @@
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

View File

@ -1,11 +1,15 @@
## Backstory is three things
The backstory about Backstory...
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.
## 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.
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.
3. **A curated expert about you** -- as a potential job seeker, you can self host this environment and generate resumes for yourself.
-or-
2. 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).
@ -25,4 +29,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 :)

View File

@ -1,100 +0,0 @@
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.

View File

@ -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.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>
<!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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
"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

View File

@ -0,0 +1,149 @@
/*!
* 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

View File

@ -1,80 +0,0 @@
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
};

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import React, { ReactElement, JSXElementConstructor, 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,22 +14,20 @@ 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 { ConversationHandle } from './Conversation';
import { QueryOptions } from './ChatQuery';
import { VectorVisualizer } from './VectorVisualizer';
import { Controls } from './Controls';
import { Conversation, ConversationHandle } from './Conversation';
import { ChatQuery, QueryOptions } from './ChatQuery';
import { Scrollable } from './Scrollable';
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 { BackstoryTab } from './BackstoryTab';
import { Document } from './Document';
import './App.css';
import './Conversation.css';
@ -39,7 +37,31 @@ 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;
@ -54,8 +76,9 @@ 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)
@ -78,13 +101,51 @@ const App = () => {
setActiveTab(0);
};
const tabs: BackstoryTabProps[] = useMemo(() => {
const homeTab: BackstoryTabProps = {
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 [{
label: "",
path: "",
tabProps: {
label: "Backstory",
sx: { flexGrow: 1, fontSize: '1rem' },
sx: tabSx,
icon:
<Avatar sx={{
width: 24,
@ -95,73 +156,121 @@ 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: (
<Scrollable
autoscroll={false}
<Conversation
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>
ref={chatRef}
{...{
type: "chat",
prompt: "What would you like to know about James?",
resetLabel: "chat",
sessionId,
connectionBase,
setSnack,
preamble: backstoryPreamble,
defaultPrompts: backstoryQuestions
}}
/>
)
};
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]);
}, {
label: "Resume Builder",
path: "resume-builder",
children: (
<ResumeBuilder sx={{
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]);
const fetchSession = useCallback((async (pathParts?: string[]) => {
try {
@ -191,7 +300,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);
@ -221,7 +330,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);
}
@ -388,7 +497,7 @@ const App = () => {
</Box>
{
tabs.map((tab: any, i: number) =>
<BackstoryPage key={i} active={i === activeTab} path={tab.path}>{tab.children}</BackstoryPage>
<BackstoryTab key={i} active={i === activeTab}>{tab.children}</BackstoryTab>
)
}
</Box>

View File

@ -1,54 +1,28 @@
import React, { ReactElement, JSXElementConstructor } from 'react';
import React from 'react';
import Box from '@mui/material/Box';
import { SxProps, Theme } from '@mui/material';
import { ChatSubmitQueryInterface } from './ChatQuery';
import { SetSnackType } from './Snack';
interface BackstoryElementProps {
sessionId: string | undefined,
setSnack: SetSnackType,
submitQuery: ChatSubmitQueryInterface,
sx?: SxProps<Theme>,
}
interface BackstoryPageProps extends BackstoryElementProps {
route?: string,
setRoute?: (route: string) => void,
};
interface BackstoryTabProps {
label?: string,
path: string,
children?: ReactElement<BackstoryPageProps>,
active?: boolean,
children?: React.ReactNode;
sx?: SxProps<Theme>,
className?: string,
tabProps?: {
label?: string,
sx?: SxProps,
icon?: string | ReactElement<unknown, string | JSXElementConstructor<any>> | undefined,
iconPosition?: "bottom" | "top" | "start" | "end" | undefined
}
};
active: boolean,
}
function BackstoryPage(props: BackstoryTabProps) {
const { className, active, children } = props;
function BackstoryTab(props: BackstoryTabProps) {
const { className, active, children, sx, ...other } = props;
return (
<Box
className={ className || "BackstoryTab"}
sx={{ "display": active ? "flex" : "none", p: 0, m: 0, borders: "none" }}
sx={{ "display": active ? "flex": "none", ...sx }}
{...other}
>
{children}
</Box>
);
}
export type {
BackstoryPageProps,
BackstoryTabProps,
BackstoryElementProps,
};
export {
BackstoryPage
BackstoryTab
}

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import { Box } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { SxProps, Theme } from '@mui/material';
@ -20,11 +20,12 @@ interface ChatBubbleProps {
title?: string;
expanded?: boolean;
expandable?: boolean;
onExpand?: (open: boolean) => void;
onExpand?: () => void;
}
function ChatBubble(props: ChatBubbleProps) {
const { role, children, sx, className, title, onExpand, expandable, expanded } = props;
const { role, children, sx, className, title, onExpand, expandable }: ChatBubbleProps = props;
const [expanded, setExpanded] = useState<boolean>((props.expanded === undefined) ? true : props.expanded);
const theme = useTheme();
@ -32,12 +33,12 @@ function ChatBubble(props: ChatBubbleProps) {
const defaultStyle = {
padding: theme.spacing(1, 2),
fontSize: '0.875rem',
alignSelf: 'flex-start',
alignSelf: 'flex-start', // Left-aligned is used by default
maxWidth: '100%',
minWidth: '100%',
height: 'fit-content',
'& > *': {
color: 'inherit',
color: 'inherit', // Children inherit 'color' from parent
overflow: 'hidden',
m: 0,
},
@ -45,151 +46,130 @@ function ChatBubble(props: ChatBubbleProps) {
mb: 0,
m: 0,
p: 0,
},
};
}
}
const styles: any = {
assistant: {
'assistant': {
...defaultStyle,
backgroundColor: theme.palette.primary.main,
border: `1px solid ${theme.palette.secondary.main}`,
borderRadius: `${defaultRadius} ${defaultRadius} ${defaultRadius} 0`,
color: theme.palette.primary.contrastText,
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
},
content: {
'content': {
...defaultStyle,
backgroundColor: '#F5F2EA',
border: `1px solid ${theme.palette.custom.highlight}`,
backgroundColor: '#F5F2EA', // Light cream background for easy reading
border: `1px solid ${theme.palette.custom.highlight}`, // Golden Ochre border
borderRadius: 0,
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,
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
},
error: {
'error': {
...defaultStyle,
backgroundColor: '#F8E7E7',
border: `1px solid #D83A3A`,
backgroundColor: '#F8E7E7', // Soft light red background
border: `1px solid #D83A3A`, // Prominent red border
borderRadius: defaultRadius,
maxWidth: '90%',
minWidth: '90%',
alignSelf: 'center',
color: '#8B2525',
color: '#8B2525', // Deep red text for good contrast
padding: '10px 16px',
boxShadow: '0 1px 3px rgba(216, 58, 58, 0.15)',
boxShadow: '0 1px 3px rgba(216, 58, 58, 0.15)', // Subtle shadow with red tint
},
'fact-check': 'qualifications',
'job-description': 'content',
'job-requirements': 'qualifications',
info: {
'info': {
...defaultStyle,
backgroundColor: '#BFD8D8',
border: `1px solid ${theme.palette.secondary.main}`,
backgroundColor: '#BFD8D8', // Softened Dusty Teal
border: `1px solid ${theme.palette.secondary.main}`, // Dusty Teal
borderRadius: defaultRadius,
color: theme.palette.text.primary,
color: theme.palette.text.primary, // Charcoal Black (#2E2E2E) — much better contrast
opacity: 0.95,
},
processing: 'status',
qualifications: {
'processing': "status",
'qualifications': {
...defaultStyle,
backgroundColor: theme.palette.primary.light,
border: `1px solid ${theme.palette.secondary.main}`,
borderRadius: `${defaultRadius} ${defaultRadius} ${defaultRadius} 0`,
color: theme.palette.primary.contrastText,
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
},
resume: 'content',
searching: 'status',
status: {
'resume': 'content',
'searching': 'status',
'status': {
...defaultStyle,
backgroundColor: 'rgba(74, 122, 125, 0.15)',
border: `1px solid ${theme.palette.secondary.light}`,
backgroundColor: 'rgba(74, 122, 125, 0.15)', // Translucent dusty teal
border: `1px solid ${theme.palette.secondary.light}`, // Lighter dusty teal
borderRadius: '4px',
maxWidth: '75%',
minWidth: '75%',
alignSelf: 'center',
color: theme.palette.secondary.dark,
fontWeight: 500,
fontSize: '0.95rem',
color: theme.palette.secondary.dark, // Darker dusty teal for text
fontWeight: 500, // Slightly bolder than normal
fontSize: '0.95rem', // Slightly smaller
padding: '8px 12px',
opacity: 0.9,
transition: 'opacity 0.3s ease-in-out',
transition: 'opacity 0.3s ease-in-out', // Smooth fade effect for appearing/disappearing
},
streaming: 'assistant',
system: {
'streaming': "assistant",
'system': {
...defaultStyle,
backgroundColor: '#EDEAE0',
border: `1px dashed ${theme.palette.custom.highlight}`,
backgroundColor: '#EDEAE0', // Soft warm gray that plays nice with #D3CDBF
border: `1px dashed ${theme.palette.custom.highlight}`, // Golden Ochre
borderRadius: defaultRadius,
maxWidth: '90%',
minWidth: '90%',
alignSelf: 'center',
color: theme.palette.text.primary,
color: theme.palette.text.primary, // Charcoal Black
fontStyle: 'italic',
},
thinking: 'status',
user: {
'thinking': "status",
'user': {
...defaultStyle,
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,
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
},
};
// Resolve string references in styles
for (const [key, value] of Object.entries(styles)) {
if (typeof value === 'string') {
styles[key] = styles[value];
if (typeof (value) === "string") {
(styles as any)[key] = styles[value];
}
}
const icons: any = {
error: <ErrorOutline color="error" />,
info: <InfoOutline color="info" />,
processing: <LocationSearchingIcon />,
searching: <Memory />,
thinking: <Psychology />,
tooling: <LocationSearchingIcon />,
"error": <ErrorOutline color='error' />,
"info": <InfoOutline color='info' />,
"processing": <LocationSearchingIcon />,
// "streaming": <Stream />,
"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={isControlled ? expanded : undefined} // Omit expanded prop for uncontrolled
defaultExpanded={expanded} // Default to collapsed for uncontrolled Accordion
expanded={expanded}
className={className}
onChange={(_event, newExpanded) => {
if (isControlled && onExpand) {
onExpand(newExpanded); // Call onExpand with new state
}
}}
onChange={() => { onExpand && onExpand(); setExpanded(!expanded); }}
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}
@ -198,20 +178,10 @@ 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>

View File

@ -7,12 +7,10 @@ type QueryOptions = {
enable_context?: boolean,
};
type ChatSubmitQueryInterface = (prompt: string, tunables?: QueryOptions) => void;
interface ChatQueryInterface {
prompt: string,
tunables?: QueryOptions,
submitQuery?: ChatSubmitQueryInterface
submitQuery?: (prompt: string, tunables?: QueryOptions) => void
}
const ChatQuery = (props : ChatQueryInterface) => {
@ -41,7 +39,6 @@ const ChatQuery = (props : ChatQueryInterface) => {
export type {
ChatQueryInterface,
QueryOptions,
ChatSubmitQueryInterface,
};
export {

View File

@ -14,9 +14,7 @@ import Typography from '@mui/material/Typography';
// import ResetIcon from '@mui/icons-material/History';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { connectionBase } from './Global';
import { BackstoryPageProps } from './BackstoryTab';
import { restyle } from 'plotly.js';
import { SetSnackType } from './Snack';
interface ServerTunables {
system_prompt: string,
@ -34,6 +32,12 @@ type Tool = {
returns?: any
};
interface ControlsParams {
connectionBase: string,
sessionId: string | undefined,
setSnack: SetSnackType,
};
type GPUInfo = {
name: string,
memory: number,
@ -86,7 +90,7 @@ const SystemInfoComponent: React.FC<{ systemInfo: SystemInfo | undefined }> = ({
return <div className="SystemInfo">{systemElements}</div>;
};
const ControlsPage = ({ sessionId, setSnack }: BackstoryPageProps) => {
const Controls = ({ sessionId, setSnack, connectionBase }: ControlsParams) => {
const [editSystemPrompt, setEditSystemPrompt] = useState<string>("");
const [systemInfo, setSystemInfo] = useState<SystemInfo | undefined>(undefined);
const [tools, setTools] = useState<Tool[]>([]);
@ -122,7 +126,7 @@ const ControlsPage = ({ sessionId, setSnack }: BackstoryPageProps) => {
sendSystemPrompt(systemPrompt);
}, [systemPrompt, sessionId, setSnack, serverTunables]);
}, [systemPrompt, connectionBase, sessionId, setSnack, serverTunables]);
useEffect(() => {
if (serverTunables === undefined || messageHistoryLength === serverTunables.message_history_length || !messageHistoryLength || sessionId === undefined) {
@ -153,7 +157,7 @@ const ControlsPage = ({ sessionId, setSnack }: BackstoryPageProps) => {
sendMessageHistoryLength(messageHistoryLength);
}, [messageHistoryLength, setMessageHistoryLength, sessionId, setSnack, serverTunables]);
}, [messageHistoryLength, setMessageHistoryLength, connectionBase, sessionId, setSnack, serverTunables]);
const reset = async (types: ("rags" | "tools" | "history" | "system_prompt" | "message_history_length")[], message: string = "Update successful.") => {
try {
@ -216,7 +220,7 @@ const ControlsPage = ({ sessionId, setSnack }: BackstoryPageProps) => {
console.error('Error obtaining system information:', error);
setSnack("Unable to obtain system information.", "error");
});
}, [systemInfo, setSystemInfo, setSnack, sessionId])
}, [systemInfo, setSystemInfo, connectionBase, setSnack, sessionId])
useEffect(() => {
setEditSystemPrompt(systemPrompt.trim());
@ -290,7 +294,7 @@ const ControlsPage = ({ sessionId, setSnack }: BackstoryPageProps) => {
}
fetchTunables();
}, [sessionId, setServerTunables, setSystemPrompt, setMessageHistoryLength, serverTunables, setTools, setRags]);
}, [sessionId, connectionBase, setServerTunables, setSystemPrompt, setMessageHistoryLength, serverTunables, setTools, setRags]);
const toggle = async (type: string, index: number) => {
switch (type) {
@ -429,6 +433,11 @@ const ControlsPage = ({ sessionId, setSnack }: BackstoryPageProps) => {
</div>);
}
export type {
ControlsParams
};
export {
ControlsPage
Controls
};

View File

@ -8,14 +8,13 @@ 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..." };
@ -45,7 +44,7 @@ interface BackstoryMessage {
timestamp: string;
};
interface ConversationProps extends BackstoryElementProps {
interface ConversationProps {
className?: string, // Override default className
type: ConversationMode, // Type of Conversation chat
prompt?: string, // Prompt to display in TextField input
@ -53,6 +52,9 @@ interface ConversationProps extends BackstoryElementProps {
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
@ -66,26 +68,26 @@ interface ConversationProps extends BackstoryElementProps {
};
const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
actionLabel,
className,
defaultPrompts,
defaultQuery,
type,
prompt,
emptyPrompt,
actionLabel,
resetAction,
multiline,
resetLabel,
connectionBase,
sessionId,
setSnack,
defaultPrompts,
hideDefaultPrompts,
defaultQuery,
preamble,
hidePreamble,
messageFilter,
messages,
multiline,
onResponse,
prompt,
preamble,
resetAction,
resetLabel,
sessionId,
setSnack,
submitQuery,
sx,
type,
onResponse
}: ConversationProps, ref) => {
const [query, setQuery] = useState<string>("");
const [contextUsedPercentage, setContextUsedPercentage] = useState<number>(0);
@ -131,7 +133,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
}
};
fetchContextStatus();
}, [setContextStatus, setSnack, sessionId, type]);
}, [setContextStatus, connectionBase, setSnack, sessionId, type]);
/* Transform the 'Conversation' by filtering via callback, then adding
* preamble and messages based on whether the conversation
@ -211,7 +213,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
setSnack("Unable to obtain chat history.", "error");
}
};
}, [setConversation, updateContextStatus, setSnack, type, sessionId]);
}, [setConversation, updateContextStatus, connectionBase, setSnack, type, sessionId]);
// Set the initial chat history to "loading" or the welcome message if loaded.
useEffect(() => {
@ -511,16 +513,16 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(({
>
{
filteredConversation.map((message, index) =>
<Message key={index} expanded={message.expanded === undefined ? true : message.expanded} {...{ sendQuery, message, connectionBase, sessionId, setSnack, submitQuery }} />
<Message key={index} {...{ sendQuery, message, connectionBase, sessionId, setSnack }} />
)
}
{
processingMessage !== undefined &&
<Message {...{ sendQuery, connectionBase, sessionId, setSnack, message: processingMessage, submitQuery }} />
<Message {...{ sendQuery, connectionBase, sessionId, setSnack, message: processingMessage }} />
}
{
streamingMessage !== undefined &&
<Message {...{ sendQuery, connectionBase, sessionId, setSnack, message: streamingMessage, submitQuery }} />
<Message {...{ sendQuery, connectionBase, sessionId, setSnack, message: streamingMessage }} />
}
<Box sx={{
display: "flex",

View File

@ -1,19 +1,21 @@
import React, { useState, useEffect } from 'react';
import { Message } from './Message';
import { BackstoryElementProps } from './BackstoryTab';
import { connectionBase } from './Global';
import { Message, MessageSubmitQuery } from './Message';
import { SetSnackType } from './Snack';
interface DocumentProps extends BackstoryElementProps {
interface DocumentProps {
title: string;
expanded?: boolean;
filepath: string;
content?: string;
setSnack: SetSnackType;
submitQuery?: MessageSubmitQuery;
connectionBase: string;
disableCopy?: boolean;
onExpand?: (open: boolean) => void;
onExpand?: () => void;
}
const Document = (props: DocumentProps) => {
const { setSnack, submitQuery, filepath, content, title, expanded, disableCopy, onExpand, sessionId } = props;
const { setSnack, submitQuery, connectionBase, filepath, content, title, expanded, disableCopy, onExpand } = props;
const [document, setDocument] = useState<string>("");
@ -61,7 +63,6 @@ const Document = (props: DocumentProps) => {
expanded,
disableCopy,
onExpand,
sessionId,
}} />
);
};

View File

@ -1,13 +0,0 @@
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
};

View File

@ -1,69 +0,0 @@
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
};

View File

@ -28,7 +28,6 @@ import { VectorVisualizer } from './VectorVisualizer';
import { SetSnackType } from './Snack';
import { CopyBubble } from './CopyBubble';
import { Scrollable } from './Scrollable';
import { BackstoryElementProps } from './BackstoryTab';
type MessageRoles =
'assistant' |
@ -84,13 +83,19 @@ interface MessageMetaData {
setSnack: SetSnackType,
}
type MessageSubmitQuery = (text: string) => void;
type MessageList = MessageData[];
interface MessageProps extends BackstoryElementProps {
interface MessageProps {
sx?: SxProps<Theme>,
message: MessageData,
expanded?: boolean,
onExpand?: (open: boolean) => void,
// expanded?: boolean, // Provided as part of MessageData
onExpand?: () => void,
submitQuery?: MessageSubmitQuery,
sessionId?: string,
connectionBase: string,
setSnack: SetSnackType,
className?: string,
};
@ -237,12 +242,12 @@ const MessageMeta = (props: MessageMetaProps) => {
};
const Message = (props: MessageProps) => {
const { message, submitQuery, sx, className, onExpand, expanded, sessionId, setSnack } = props;
const [metaExpanded, setMetaExpanded] = useState<boolean>(false);
const { message, submitQuery, sx, className, onExpand } = props;
const [expanded, setExpanded] = useState<boolean>(false);
const textFieldRef = useRef(null);
const handleMetaExpandClick = () => {
setMetaExpanded(!metaExpanded);
const handleExpandClick = () => {
setExpanded(!expanded);
};
if (message === undefined) {
@ -261,7 +266,6 @@ const Message = (props: MessageProps) => {
className={className || "Message"}
{...message}
onExpand={onExpand}
expanded={expanded}
sx={{
display: "flex",
flexDirection: "column",
@ -287,7 +291,7 @@ const Message = (props: MessageProps) => {
overflow: "auto", /* Handles scrolling for the div */
}}
>
<StyledMarkdown {...{ content: formattedContent, submitQuery, sessionId, setSnack }} />
<StyledMarkdown {...{ content: formattedContent, submitQuery }} />
</Scrollable>
:
<Typography
@ -303,12 +307,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={handleMetaExpandClick} sx={{ color: "darkgrey", p: 0 }}>
<Button variant="text" onClick={handleExpandClick} sx={{ color: "darkgrey", p: 0 }}>
LLM information for this query
</Button>
<ExpandMore
expand={metaExpanded}
onClick={handleMetaExpandClick}
expand={expanded}
onClick={handleExpandClick}
aria-expanded={expanded}
aria-label="show more"
>
@ -318,7 +322,7 @@ const Message = (props: MessageProps) => {
)}
</CardActions>
{message.metadata && <>
<Collapse in={metaExpanded} timeout="auto" unmountOnExit>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<CardContent>
<MessageMeta messageProps={props} metadata={message.metadata} />
</CardContent>
@ -333,6 +337,7 @@ export type {
MessageList,
MessageData,
MessageRoles,
MessageSubmitQuery
};
export {

View File

@ -4,14 +4,21 @@ import {
Tab,
Box,
} from '@mui/material';
import { SxProps } from '@mui/material';
import { SxProps, Theme } 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 './ResumeBuilderPage.css';
import './ResumeBuilder.css';
interface ResumeBuilderProps {
connectionBase: string,
sessionId: string | undefined,
setSnack: SetSnackType,
sx?: SxProps<Theme>;
};
/**
* ResumeBuilder component
@ -19,11 +26,12 @@ 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> = ({
const ResumeBuilder: React.FC<ResumeBuilderProps> = ({
sx,
connectionBase,
sessionId,
setSnack,
submitQuery,
setSnack
}) => {
// State for editing job description
const [hasJobDescription, setHasJobDescription] = useState<boolean>(false);
@ -79,7 +87,7 @@ const ResumeBuilderPage: React.FC<BackstoryPageProps> = ({
messages[3].role = 'job-requirements';
messages[3].title = 'Job Requirements';
messages[3].disableCopy = false;
messages[3].expanded = false;
messages[3].expanded = true;
messages[3].expandable = true;
}
@ -128,7 +136,7 @@ const ResumeBuilderPage: React.FC<BackstoryPageProps> = ({
messages[1].role = 'fact-check';
messages[1].title = 'Fact Check';
messages[1].disableCopy = false;
messages[1].expanded = true;
messages[1].expanded = false;
messages[1].expandable = true;
}
@ -217,8 +225,8 @@ const ResumeBuilderPage: React.FC<BackstoryPageProps> = ({
resetAction: resetJobDescription,
onResponse: jobResponse,
sessionId,
connectionBase,
setSnack,
submitQuery,
sx,
}}
/>
@ -236,13 +244,13 @@ const ResumeBuilderPage: React.FC<BackstoryPageProps> = ({
resetAction: resetJobDescription,
onResponse: jobResponse,
sessionId,
connectionBase,
setSnack,
submitQuery,
sx,
}}
/>
}
}, [filterJobDescriptionMessages, hasJobDescription, sessionId, setSnack, jobResponse, resetJobDescription, hasFacts, hasResume, submitQuery]);
}, [connectionBase, filterJobDescriptionMessages, hasJobDescription, sessionId, setSnack, jobResponse, resetJobDescription, hasFacts, hasResume]);
/**
* Renders the resume view with loading indicator
@ -267,8 +275,8 @@ const ResumeBuilderPage: React.FC<BackstoryPageProps> = ({
onResponse: resumeResponse,
resetAction: resetResume,
sessionId,
connectionBase,
setSnack,
submitQuery,
sx,
}}
/>
@ -284,14 +292,14 @@ const ResumeBuilderPage: React.FC<BackstoryPageProps> = ({
onResponse: resumeResponse,
resetAction: resetResume,
sessionId,
connectionBase,
setSnack,
defaultPrompts: resumeQuestions,
submitQuery,
sx,
}}
/>
}
}, [filterResumeMessages, hasFacts, sessionId, setSnack, resumeResponse, resetResume, hasResume, submitQuery]);
}, [connectionBase, filterResumeMessages, hasFacts, sessionId, setSnack, resumeResponse, resetResume, hasResume]);
/**
* Renders the fact check view
@ -315,12 +323,12 @@ const ResumeBuilderPage: React.FC<BackstoryPageProps> = ({
resetAction: resetFacts,
onResponse: factsResponse,
sessionId,
submitQuery,
connectionBase,
setSnack,
sx,
}}
/>
}, [ sessionId, setSnack, factsResponse, filterFactsMessages, resetFacts, hasResume, hasFacts, submitQuery]);
}, [connectionBase, sessionId, setSnack, factsResponse, filterFactsMessages, resetFacts, hasResume, hasFacts]);
return (
<Box className="ResumeBuilder"
@ -361,7 +369,11 @@ const ResumeBuilderPage: React.FC<BackstoryPageProps> = ({
);
};
export {
ResumeBuilderPage
export type {
ResumeBuilderProps
};
export {
ResumeBuilder
};

View File

@ -1,6 +0,0 @@
.ResumeBuilder .JsonViewScrollable {
min-height: unset !important;
max-height: 30rem !important;
border: 1px solid orange;
overflow-x: auto !important;
}

View File

@ -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 } from './ChatQuery';
import { ChatQuery, QueryOptions } 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 extends BackstoryElementProps {
interface StyledMarkdownProps {
className?: string,
content: string,
sx?: SxProps,
submitQuery?: (prompt: string, tunables?: QueryOptions) => void,
};
const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProps) => {
@ -31,13 +31,10 @@ 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 autoscroll className="JsonViewScrollable">
return <Scrollable className="JsonViewScrollable">
<JsonView
className="JsonView"
style={{
@ -53,7 +50,6 @@ const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProp
displayDataTypes={false}
objectSortKeys={false}
collapsed={false}
shortenTextAfterLength={100}
value={JSON.parse(fixed)}>
<JsonView.String
render={({ children, ...reset }) => {
@ -85,13 +81,17 @@ const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProp
}
}
},
ChatQuery: {
chatQuery: undefined
};
if (submitQuery) {
overrides.ChatQuery = {
component: ChatQuery,
props: {
submitQuery,
},
}
};
};
}
return <Box
className={`MuiMarkdown ${className || ""}`}

View File

@ -8,21 +8,16 @@ 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;
@ -51,8 +46,14 @@ 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[];
@ -108,7 +109,7 @@ const symbolMap: Record<string, string> = {
};
const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualizerProps) => {
const { setSnack, rag, inline, sessionId, sx, submitQuery } = props;
const { setSnack, rag, inline, connectionBase, sessionId, sx } = props;
const [plotData, setPlotData] = useState<PlotData | null>(null);
const [newQuery, setNewQuery] = useState<string>('');
const [newQueryEmbedding, setNewQueryEmbedding] = useState<ChromaResult | undefined>(undefined);
@ -150,7 +151,7 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
};
fetchCollection();
}, [result, setResult, setSnack, sessionId, view2D])
}, [result, setResult, connectionBase, setSnack, sessionId, view2D])
useEffect(() => {
if (!result || !result.embeddings) return;
@ -436,7 +437,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."} {...{ sessionId, setSnack, submitQuery }} />
<StyledMarkdown sx={{ p: 1, pt: 0 }} content={tooltip?.content || "Select a node in the visualization."} />
</Scrollable>
}
</Card>
@ -447,7 +448,7 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
}
{
!inline &&
!inline &&
<Box className="Query" sx={{ display: "flex", flexDirection: "row", p: 1 }}>
<TextField
variant="outlined"
@ -468,21 +469,8 @@ 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,
VectorVisualizerPage
VectorVisualizer
};

View File

@ -11,9 +11,9 @@ scrape_configs:
insecure_skip_verify: true
- job_name: 'backstory-prod'
scrape_interval: 5s
scrape_interval: 30s
metrics_path: /metrics
scheme: http
scheme: https
static_configs:
- targets: ['backstory-prod:8911']
tls_config:

View File

@ -197,7 +197,6 @@ 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