All UI seems to work
This commit is contained in:
parent
c97d24be68
commit
97425a6aad
@ -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"
|
||||
]
|
||||
}
|
@ -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 :)
|
||||
|
@ -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
@ -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,46 +78,10 @@ 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 tabs: BackstoryTabProps[] = useMemo(() => {
|
||||
const tabSx = { flexGrow: 1, fontSize: '1rem' };
|
||||
|
||||
return [{
|
||||
const homeTab: BackstoryTabProps = {
|
||||
label: "",
|
||||
path: "",
|
||||
tabProps: {
|
||||
@ -156,121 +97,59 @@ 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]);
|
||||
|
||||
const fetchSession = useCallback((async (pathParts?: string[]) => {
|
||||
try {
|
||||
@ -300,7 +179,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 +209,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 +376,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, { useState, useEffect } from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { SxProps, Theme } from '@mui/material';
|
||||
@ -9,7 +9,7 @@ import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import LocationSearchingIcon from '@mui/icons-material/LocationSearching';
|
||||
|
||||
import { MessageRoles } from './Message';
|
||||
import { ErrorOutline, InfoOutline, Memory, Psychology, /* Stream, */ } from '@mui/icons-material';
|
||||
import { ErrorOutline, InfoOutline, Memory, Message, Psychology, /* Stream, */ } from '@mui/icons-material';
|
||||
|
||||
interface ChatBubbleProps {
|
||||
role: MessageRoles,
|
||||
@ -20,12 +20,13 @@ 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 }: ChatBubbleProps = props;
|
||||
|
||||
console.log("ChatBubbble():", props.expanded);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
@ -162,7 +163,7 @@ function ChatBubble(props: ChatBubbleProps) {
|
||||
<Accordion
|
||||
expanded={expanded}
|
||||
className={className}
|
||||
onChange={() => { onExpand && onExpand(); setExpanded(!expanded); }}
|
||||
onChange={() => { console.log(`onChange(${expanded} inverse)`); onExpand && onExpand(!expanded); }}
|
||||
sx={{ ...styles[role], ...sx }}
|
||||
>
|
||||
<AccordionSummary
|
||||
|
@ -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 {
|
||||
|
@ -1,443 +0,0 @@
|
||||
import React, { useState, useEffect, ReactElement } from 'react';
|
||||
// import FormGroup from '@mui/material/FormGroup';
|
||||
// import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
// import Switch from '@mui/material/Switch';
|
||||
// import Divider from '@mui/material/Divider';
|
||||
// import TextField from '@mui/material/TextField';
|
||||
import Accordion from '@mui/material/Accordion';
|
||||
import AccordionActions from '@mui/material/AccordionActions';
|
||||
import AccordionSummary from '@mui/material/AccordionSummary';
|
||||
import AccordionDetails from '@mui/material/AccordionDetails';
|
||||
import Typography from '@mui/material/Typography';
|
||||
// import Button from '@mui/material/Button';
|
||||
// import Box from '@mui/material/Box';
|
||||
// import ResetIcon from '@mui/icons-material/History';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
|
||||
import { SetSnackType } from './Snack';
|
||||
|
||||
interface ServerTunables {
|
||||
system_prompt: string,
|
||||
message_history_length: number,
|
||||
tools: Tool[],
|
||||
rags: Tool[]
|
||||
};
|
||||
|
||||
type Tool = {
|
||||
type: string,
|
||||
enabled: boolean
|
||||
name: string,
|
||||
description: string,
|
||||
parameters?: any,
|
||||
returns?: any
|
||||
};
|
||||
|
||||
interface ControlsParams {
|
||||
connectionBase: string,
|
||||
sessionId: string | undefined,
|
||||
setSnack: SetSnackType,
|
||||
};
|
||||
|
||||
type GPUInfo = {
|
||||
name: string,
|
||||
memory: number,
|
||||
discrete: boolean
|
||||
}
|
||||
|
||||
type SystemInfo = {
|
||||
"Installed RAM": string,
|
||||
"Graphics Card": GPUInfo[],
|
||||
"CPU": string
|
||||
};
|
||||
|
||||
const SystemInfoComponent: React.FC<{ systemInfo: SystemInfo | undefined }> = ({ systemInfo }) => {
|
||||
const [systemElements, setSystemElements] = useState<ReactElement[]>([]);
|
||||
|
||||
const convertToSymbols = (text: string) => {
|
||||
return text
|
||||
.replace(/\(R\)/g, '®') // Replace (R) with the ® symbol
|
||||
.replace(/\(C\)/g, '©') // Replace (C) with the © symbol
|
||||
.replace(/\(TM\)/g, '™'); // Replace (TM) with the ™ symbol
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (systemInfo === undefined) {
|
||||
return;
|
||||
}
|
||||
const elements = Object.entries(systemInfo).flatMap(([k, v]) => {
|
||||
// If v is an array, repeat for each card
|
||||
if (Array.isArray(v)) {
|
||||
return v.map((card, index) => (
|
||||
<div key={index} className="SystemInfoItem">
|
||||
<div>{convertToSymbols(k)} {index}</div>
|
||||
<div>{convertToSymbols(card.name)} {card.discrete ? `w/ ${Math.round(card.memory / (1024 * 1024 * 1024))}GB RAM` : "(integrated)"}</div>
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
||||
// If it's not an array, handle normally
|
||||
return (
|
||||
<div key={k} className="SystemInfoItem">
|
||||
<div>{convertToSymbols(k)}</div>
|
||||
<div>{convertToSymbols(String(v))}</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
setSystemElements(elements);
|
||||
}, [systemInfo]);
|
||||
|
||||
return <div className="SystemInfo">{systemElements}</div>;
|
||||
};
|
||||
|
||||
const Controls = ({ sessionId, setSnack, connectionBase }: ControlsParams) => {
|
||||
const [editSystemPrompt, setEditSystemPrompt] = useState<string>("");
|
||||
const [systemInfo, setSystemInfo] = useState<SystemInfo | undefined>(undefined);
|
||||
const [tools, setTools] = useState<Tool[]>([]);
|
||||
const [rags, setRags] = useState<Tool[]>([]);
|
||||
const [systemPrompt, setSystemPrompt] = useState<string>("");
|
||||
const [messageHistoryLength, setMessageHistoryLength] = useState<number>(5);
|
||||
const [serverTunables, setServerTunables] = useState<ServerTunables | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (serverTunables === undefined || systemPrompt === serverTunables.system_prompt || !systemPrompt.trim() || sessionId === undefined) {
|
||||
return;
|
||||
}
|
||||
const sendSystemPrompt = async (prompt: string) => {
|
||||
try {
|
||||
const response = await fetch(connectionBase + `/api/tunables/${sessionId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ "system_prompt": prompt }),
|
||||
});
|
||||
|
||||
const tunables = await response.json();
|
||||
serverTunables.system_prompt = tunables.system_prompt;
|
||||
setSystemPrompt(tunables.system_prompt)
|
||||
setSnack("System prompt updated", "success");
|
||||
} catch (error) {
|
||||
console.error('Fetch error:', error);
|
||||
setSnack("System prompt update failed", "error");
|
||||
}
|
||||
};
|
||||
|
||||
sendSystemPrompt(systemPrompt);
|
||||
|
||||
}, [systemPrompt, connectionBase, sessionId, setSnack, serverTunables]);
|
||||
|
||||
useEffect(() => {
|
||||
if (serverTunables === undefined || messageHistoryLength === serverTunables.message_history_length || !messageHistoryLength || sessionId === undefined) {
|
||||
return;
|
||||
}
|
||||
const sendMessageHistoryLength = async (length: number) => {
|
||||
try {
|
||||
const response = await fetch(connectionBase + `/api/tunables/${sessionId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ "message_history_length": length }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const newLength = data["message_history_length"];
|
||||
if (newLength !== messageHistoryLength) {
|
||||
setMessageHistoryLength(newLength);
|
||||
setSnack("Message history length updated", "success");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fetch error:', error);
|
||||
setSnack("Message history length update failed", "error");
|
||||
}
|
||||
};
|
||||
|
||||
sendMessageHistoryLength(messageHistoryLength);
|
||||
|
||||
}, [messageHistoryLength, setMessageHistoryLength, connectionBase, sessionId, setSnack, serverTunables]);
|
||||
|
||||
const reset = async (types: ("rags" | "tools" | "history" | "system_prompt" | "message_history_length")[], message: string = "Update successful.") => {
|
||||
try {
|
||||
const response = await fetch(connectionBase + `/api/reset/${sessionId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ "reset": types }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.error) {
|
||||
throw Error()
|
||||
}
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
switch (key) {
|
||||
case "rags":
|
||||
setRags(value as Tool[]);
|
||||
break;
|
||||
case "tools":
|
||||
setTools(value as Tool[]);
|
||||
break;
|
||||
case "system_prompt":
|
||||
setSystemPrompt((value as ServerTunables)["system_prompt"].trim());
|
||||
break;
|
||||
case "history":
|
||||
console.log('TODO: handle history reset');
|
||||
break;
|
||||
}
|
||||
}
|
||||
setSnack(message, "success");
|
||||
} else {
|
||||
throw Error(`${{ status: response.status, message: response.statusText }}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fetch error:', error);
|
||||
setSnack("Unable to restore defaults", "error");
|
||||
}
|
||||
};
|
||||
|
||||
// Get the system information
|
||||
useEffect(() => {
|
||||
if (systemInfo !== undefined || sessionId === undefined) {
|
||||
return;
|
||||
}
|
||||
fetch(connectionBase + `/api/system-info/${sessionId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
setSystemInfo(data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error obtaining system information:', error);
|
||||
setSnack("Unable to obtain system information.", "error");
|
||||
});
|
||||
}, [systemInfo, setSystemInfo, connectionBase, setSnack, sessionId])
|
||||
|
||||
useEffect(() => {
|
||||
setEditSystemPrompt(systemPrompt.trim());
|
||||
}, [systemPrompt, setEditSystemPrompt]);
|
||||
|
||||
const toggleRag = async (tool: Tool) => {
|
||||
tool.enabled = !tool.enabled
|
||||
try {
|
||||
const response = await fetch(connectionBase + `/api/tunables/${sessionId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ "rags": [{ "name": tool?.name, "enabled": tool.enabled }] }),
|
||||
});
|
||||
|
||||
const tunables: ServerTunables = await response.json();
|
||||
setRags(tunables.rags)
|
||||
setSnack(`${tool?.name} ${tool.enabled ? "enabled" : "disabled"}`);
|
||||
} catch (error) {
|
||||
console.error('Fetch error:', error);
|
||||
setSnack(`${tool?.name} ${tool.enabled ? "enabling" : "disabling"} failed.`, "error");
|
||||
tool.enabled = !tool.enabled
|
||||
}
|
||||
};
|
||||
|
||||
const toggleTool = async (tool: Tool) => {
|
||||
tool.enabled = !tool.enabled
|
||||
try {
|
||||
const response = await fetch(connectionBase + `/api/tunables/${sessionId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ "tools": [{ "name": tool.name, "enabled": tool.enabled }] }),
|
||||
});
|
||||
|
||||
const tunables: ServerTunables = await response.json();
|
||||
setTools(tunables.tools)
|
||||
setSnack(`${tool.name} ${tool.enabled ? "enabled" : "disabled"}`);
|
||||
} catch (error) {
|
||||
console.error('Fetch error:', error);
|
||||
setSnack(`${tool.name} ${tool.enabled ? "enabling" : "disabling"} failed.`, "error");
|
||||
tool.enabled = !tool.enabled
|
||||
}
|
||||
};
|
||||
|
||||
// If the systemPrompt has not been set, fetch it from the server
|
||||
useEffect(() => {
|
||||
if (serverTunables !== undefined || sessionId === undefined) {
|
||||
return;
|
||||
}
|
||||
const fetchTunables = async () => {
|
||||
// Make the fetch request with proper headers
|
||||
const response = await fetch(connectionBase + `/api/tunables/${sessionId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
});
|
||||
const data = await response.json();
|
||||
console.log("Server tunables: ", data);
|
||||
setServerTunables(data);
|
||||
setSystemPrompt(data["system_prompt"]);
|
||||
setMessageHistoryLength(data["message_history_length"]);
|
||||
setTools(data["tools"]);
|
||||
setRags(data["rags"]);
|
||||
}
|
||||
|
||||
fetchTunables();
|
||||
}, [sessionId, connectionBase, setServerTunables, setSystemPrompt, setMessageHistoryLength, serverTunables, setTools, setRags]);
|
||||
|
||||
const toggle = async (type: string, index: number) => {
|
||||
switch (type) {
|
||||
case "rag":
|
||||
if (rags === undefined) {
|
||||
return;
|
||||
}
|
||||
toggleRag(rags[index])
|
||||
break;
|
||||
case "tool":
|
||||
if (tools === undefined) {
|
||||
return;
|
||||
}
|
||||
toggleTool(tools[index]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyPress = (event: any) => {
|
||||
if (event.key === 'Enter' && event.ctrlKey) {
|
||||
setSystemPrompt(editSystemPrompt);
|
||||
}
|
||||
};
|
||||
|
||||
return (<div className="Controls">
|
||||
{/* <Typography component="span" sx={{ mb: 1 }}>
|
||||
You can change the information available to the LLM by adjusting the following settings:
|
||||
</Typography>
|
||||
<Accordion>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography component="span">System Prompt</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionActions style={{ display: "flex", flexDirection: "column" }}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
multiline
|
||||
slotProps={{
|
||||
htmlInput: { style: { fontSize: "0.85rem", lineHeight: "1.25rem" } }
|
||||
}}
|
||||
type="text"
|
||||
value={editSystemPrompt}
|
||||
onChange={(e) => setEditSystemPrompt(e.target.value)}
|
||||
onKeyDown={handleKeyPress}
|
||||
placeholder="Enter the new system prompt.."
|
||||
/>
|
||||
<Box sx={{ display: "flex", flexDirection: "row", gap: "8px", paddingTop: "8px" }}>
|
||||
<Button variant="contained" disabled={editSystemPrompt.trim() === systemPrompt.trim()} onClick={() => { setSystemPrompt(editSystemPrompt.trim()); }}>Set</Button>
|
||||
<Button variant="outlined" onClick={() => { reset(["system_prompt"], "System prompt reset."); }} color="error">Reset</Button>
|
||||
</Box>
|
||||
</AccordionActions>
|
||||
</Accordion>
|
||||
|
||||
<Accordion>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography component="span">Tunables</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionActions style={{ flexDirection: "column" }}>
|
||||
<TextField
|
||||
id="outlined-number"
|
||||
label="Message history"
|
||||
type="number"
|
||||
helperText="Only use this many messages as context. 0 = All. Keeping this low will reduce context growth and improve performance."
|
||||
value={messageHistoryLength}
|
||||
onChange={(e: any) => setMessageHistoryLength(e.target.value)}
|
||||
slotProps={{
|
||||
htmlInput: {
|
||||
min: 0
|
||||
},
|
||||
inputLabel: {
|
||||
shrink: true,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</AccordionActions>
|
||||
</Accordion>
|
||||
|
||||
<Accordion>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography component="span">Tools</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
These tools can be made available to the LLM for obtaining real-time information from the Internet. The description provided to the LLM is provided for reference.
|
||||
</AccordionDetails>
|
||||
<AccordionActions>
|
||||
<FormGroup sx={{ p: 1 }}>
|
||||
{
|
||||
(tools || []).map((tool, index) =>
|
||||
<Box key={index}>
|
||||
<Divider />
|
||||
<FormControlLabel control={<Switch checked={tool.enabled} />} onChange={() => toggle("tool", index)} label={tool.name} />
|
||||
<Typography sx={{ fontSize: "0.8rem", mb: 1 }}>{tool.description}</Typography>
|
||||
</Box>
|
||||
)
|
||||
}</FormGroup>
|
||||
</AccordionActions>
|
||||
</Accordion>
|
||||
|
||||
<Accordion>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography component="span">RAG</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
These RAG databases can be enabled / disabled for adding additional context based on the chat request.
|
||||
</AccordionDetails>
|
||||
<AccordionActions>
|
||||
<FormGroup sx={{ p: 1, flexGrow: 1, justifyContent: "flex-start" }}>
|
||||
{
|
||||
(rags || []).map((rag, index) =>
|
||||
<Box key={index} sx={{ display: "flex", flexGrow: 1, flexDirection: "column" }}>
|
||||
<Divider />
|
||||
<FormControlLabel
|
||||
control={<Switch checked={rag.enabled} />}
|
||||
onChange={() => toggle("rag", index)} label={rag.name}
|
||||
/>
|
||||
<Typography>{rag.description}</Typography>
|
||||
</Box>
|
||||
)
|
||||
}</FormGroup>
|
||||
</AccordionActions>
|
||||
</Accordion> */}
|
||||
|
||||
<Accordion>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Typography component="span">System Information</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
The server is running on the following hardware:
|
||||
</AccordionDetails>
|
||||
<AccordionActions>
|
||||
<SystemInfoComponent systemInfo={systemInfo} />
|
||||
</AccordionActions>
|
||||
</Accordion>
|
||||
|
||||
{/* <Button startIcon={<ResetIcon />} onClick={() => { reset(["history"], "History cleared."); }}>Delete Backstory History</Button>
|
||||
<Button onClick={() => { reset(["rags", "tools", "system_prompt", "message_history_length"], "Default settings restored.") }}>Reset system prompt, tunables, and RAG to defaults</Button> */}
|
||||
</div>);
|
||||
}
|
||||
|
||||
|
||||
export type {
|
||||
ControlsParams
|
||||
};
|
||||
|
||||
export {
|
||||
Controls
|
||||
};
|
@ -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} {...{ 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,21 @@
|
||||
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;
|
||||
|
||||
console.log(`${filepath} expanded: ${expanded}`);
|
||||
|
||||
const [document, setDocument] = useState<string>("");
|
||||
|
||||
@ -63,6 +63,7 @@ const Document = (props: DocumentProps) => {
|
||||
expanded,
|
||||
disableCopy,
|
||||
onExpand,
|
||||
sessionId,
|
||||
}} />
|
||||
);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState, useRef } from 'react';
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import Accordion from '@mui/material/Accordion';
|
||||
import AccordionSummary from '@mui/material/AccordionSummary';
|
||||
@ -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 } = props;
|
||||
const [metaExpanded, setMetaExpanded] = useState<boolean>(props.expanded || 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",
|
||||
@ -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"
|
||||
>
|
||||
@ -337,7 +333,6 @@ export type {
|
||||
MessageList,
|
||||
MessageData,
|
||||
MessageRoles,
|
||||
MessageSubmitQuery
|
||||
};
|
||||
|
||||
export {
|
||||
|
@ -1,6 +0,0 @@
|
||||
.ResumeBuilder .JsonViewScrollable {
|
||||
min-height: unset !important;
|
||||
max-height: 30rem !important;
|
||||
border: 1px solid orange;
|
||||
overflow-x: auto !important;
|
||||
}
|
@ -1,379 +0,0 @@
|
||||
import React, { useState, useCallback, useRef } from 'react';
|
||||
import {
|
||||
Tabs,
|
||||
Tab,
|
||||
Box,
|
||||
} 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 './ResumeBuilder.css';
|
||||
|
||||
interface ResumeBuilderProps {
|
||||
connectionBase: string,
|
||||
sessionId: string | undefined,
|
||||
setSnack: SetSnackType,
|
||||
sx?: SxProps<Theme>;
|
||||
};
|
||||
|
||||
/**
|
||||
* ResumeBuilder component
|
||||
*
|
||||
* 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> = ({
|
||||
sx,
|
||||
connectionBase,
|
||||
sessionId,
|
||||
setSnack
|
||||
|
||||
}) => {
|
||||
// State for editing job description
|
||||
const [hasJobDescription, setHasJobDescription] = useState<boolean>(false);
|
||||
const [hasResume, setHasResume] = useState<boolean>(false);
|
||||
const [hasFacts, setHasFacts] = useState<boolean>(false);
|
||||
const jobConversationRef = useRef<any>(null);
|
||||
const resumeConversationRef = useRef<any>(null);
|
||||
const factsConversationRef = useRef<any>(null);
|
||||
|
||||
const [activeTab, setActiveTab] = useState<number>(0);
|
||||
|
||||
/**
|
||||
* Handle tab change for mobile view
|
||||
*/
|
||||
const handleTabChange = (_event: React.SyntheticEvent, newValue: number): void => {
|
||||
setActiveTab(newValue);
|
||||
};
|
||||
|
||||
const handleJobQuery = (query: string) => {
|
||||
console.log(`handleJobQuery: ${query} -- `, jobConversationRef.current ? ' sending' : 'no handler');
|
||||
jobConversationRef.current?.submitQuery(query);
|
||||
};
|
||||
|
||||
const handleResumeQuery = (query: string) => {
|
||||
console.log(`handleResumeQuery: ${query} -- `, resumeConversationRef.current ? ' sending' : 'no handler');
|
||||
resumeConversationRef.current?.submitQuery(query);
|
||||
};
|
||||
|
||||
const handleFactsQuery = (query: string) => {
|
||||
console.log(`handleFactsQuery: ${query} -- `, factsConversationRef.current ? ' sending' : 'no handler');
|
||||
factsConversationRef.current?.submitQuery(query);
|
||||
};
|
||||
|
||||
const filterJobDescriptionMessages = useCallback((messages: MessageList): MessageList => {
|
||||
if (messages === undefined || messages.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (messages.length > 2) {
|
||||
setHasResume(true);
|
||||
setHasFacts(true);
|
||||
}
|
||||
|
||||
if (messages.length > 0) {
|
||||
messages[0].role = 'content';
|
||||
messages[0].title = 'Job Description';
|
||||
messages[0].disableCopy = false;
|
||||
messages[0].expandable = true;
|
||||
}
|
||||
|
||||
if (messages.length > 3) {
|
||||
// messages[2] is Show job requirements
|
||||
messages[3].role = 'job-requirements';
|
||||
messages[3].title = 'Job Requirements';
|
||||
messages[3].disableCopy = false;
|
||||
messages[3].expanded = true;
|
||||
messages[3].expandable = true;
|
||||
}
|
||||
|
||||
/* Filter out the 2nd and 3rd (0-based) */
|
||||
const filtered = messages.filter((m, i) => i !== 1 && i !== 2);
|
||||
|
||||
return filtered;
|
||||
}, [setHasResume, setHasFacts]);
|
||||
|
||||
const filterResumeMessages = useCallback((messages: MessageList): MessageList => {
|
||||
if (messages === undefined || messages.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (messages.length > 1) {
|
||||
// messages[0] is Show Qualifications
|
||||
messages[1].role = 'qualifications';
|
||||
messages[1].title = 'Candidate qualifications';
|
||||
messages[1].disableCopy = false;
|
||||
messages[1].expanded = false;
|
||||
messages[1].expandable = true;
|
||||
}
|
||||
|
||||
if (messages.length > 3) {
|
||||
// messages[2] is Show Resume
|
||||
messages[3].role = 'resume';
|
||||
messages[3].title = 'Generated Resume';
|
||||
messages[3].disableCopy = false;
|
||||
messages[3].expanded = true;
|
||||
messages[3].expandable = true;
|
||||
}
|
||||
|
||||
/* Filter out the 1st and 3rd messages (0-based) */
|
||||
const filtered = messages.filter((m, i) => i !== 0 && i !== 2);
|
||||
|
||||
return filtered;
|
||||
}, []);
|
||||
|
||||
const filterFactsMessages = useCallback((messages: MessageList): MessageList => {
|
||||
if (messages === undefined || messages.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (messages.length > 1) {
|
||||
// messages[0] is Show verification
|
||||
messages[1].role = 'fact-check';
|
||||
messages[1].title = 'Fact Check';
|
||||
messages[1].disableCopy = false;
|
||||
messages[1].expanded = false;
|
||||
messages[1].expandable = true;
|
||||
}
|
||||
|
||||
/* Filter out the 1st (0-based) */
|
||||
const filtered = messages.filter((m, i) => i !== 0);
|
||||
|
||||
return filtered;
|
||||
}, []);
|
||||
|
||||
const jobResponse = useCallback(async (message: MessageData) => {
|
||||
console.log('onJobResponse', message);
|
||||
if (message.actions && message.actions.includes("job_description")) {
|
||||
await jobConversationRef.current.fetchHistory();
|
||||
}
|
||||
if (message.actions && message.actions.includes("resume_generated")) {
|
||||
await resumeConversationRef.current.fetchHistory();
|
||||
setHasResume(true);
|
||||
setActiveTab(1); // Switch to Resume tab
|
||||
}
|
||||
if (message.actions && message.actions.includes("facts_checked")) {
|
||||
await factsConversationRef.current.fetchHistory();
|
||||
setHasFacts(true);
|
||||
}
|
||||
}, [setHasFacts, setHasResume, setActiveTab]);
|
||||
|
||||
const resumeResponse = useCallback((message: MessageData): void => {
|
||||
console.log('onResumeResponse', message);
|
||||
setHasFacts(true);
|
||||
}, [setHasFacts]);
|
||||
|
||||
const factsResponse = useCallback((message: MessageData): void => {
|
||||
console.log('onFactsResponse', message);
|
||||
}, []);
|
||||
|
||||
const resetJobDescription = useCallback(() => {
|
||||
setHasJobDescription(false);
|
||||
setHasResume(false);
|
||||
setHasFacts(false);
|
||||
}, [setHasJobDescription, setHasResume, setHasFacts]);
|
||||
|
||||
const resetResume = useCallback(() => {
|
||||
setHasResume(false);
|
||||
setHasFacts(false);
|
||||
}, [setHasResume, setHasFacts]);
|
||||
|
||||
const resetFacts = useCallback(() => {
|
||||
setHasFacts(false);
|
||||
}, [setHasFacts]);
|
||||
|
||||
const renderJobDescriptionView = useCallback((sx: SxProps) => {
|
||||
console.log('renderJobDescriptionView');
|
||||
const jobDescriptionQuestions = [
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
<ChatQuery prompt="What are the key skills necessary for this position?" tunables={{ enable_tools: false }} submitQuery={handleJobQuery} />
|
||||
<ChatQuery prompt="How much should this position pay (accounting for inflation)?" tunables={{ enable_tools: false }} submitQuery={handleJobQuery} />
|
||||
</Box>,
|
||||
];
|
||||
|
||||
const jobDescriptionPreamble: MessageList = [{
|
||||
role: 'info',
|
||||
content: `Once you paste a job description and press **Generate Resume**, the system will perform the following actions:
|
||||
|
||||
1. **RAG**: Collects information from the RAG database relavent to the job description
|
||||
2. **Isolated Analysis**: Three sub-stages
|
||||
1. **Job Analysis**: Extracts requirements from job description only
|
||||
2. **Candidate Analysis**: Catalogs qualifications from resume/context only
|
||||
3. **Mapping Analysis**: Identifies legitimate matches between requirements and qualifications
|
||||
3. **Resume Generation**: Uses mapping output to create a tailored resume with evidence-based content
|
||||
4. **Verification**: Performs fact-checking to catch any remaining fabrications
|
||||
1. **Re-generation**: If verification does not pass, a second attempt is made to correct any issues`
|
||||
}];
|
||||
|
||||
|
||||
if (!hasJobDescription) {
|
||||
return <Conversation
|
||||
ref={jobConversationRef}
|
||||
{...{
|
||||
type: "job_description",
|
||||
actionLabel: "Generate Resume",
|
||||
preamble: jobDescriptionPreamble,
|
||||
hidePreamble: true,
|
||||
prompt: "Paste a job description, then click Generate...",
|
||||
multiline: true,
|
||||
resetLabel: `job description${hasFacts ? ", resume, and fact check" : hasResume ? " and resume" : ""}`,
|
||||
messageFilter: filterJobDescriptionMessages,
|
||||
resetAction: resetJobDescription,
|
||||
onResponse: jobResponse,
|
||||
sessionId,
|
||||
connectionBase,
|
||||
setSnack,
|
||||
sx,
|
||||
}}
|
||||
/>
|
||||
|
||||
} else {
|
||||
return <Conversation
|
||||
ref={jobConversationRef}
|
||||
{...{
|
||||
type: "job_description",
|
||||
actionLabel: "Send",
|
||||
prompt: "Ask a question about this job description...",
|
||||
resetLabel: `job description${hasFacts ? ", resume, and fact check" : hasResume ? " and resume" : ""}`,
|
||||
messageFilter: filterJobDescriptionMessages,
|
||||
defaultPrompts: jobDescriptionQuestions,
|
||||
resetAction: resetJobDescription,
|
||||
onResponse: jobResponse,
|
||||
sessionId,
|
||||
connectionBase,
|
||||
setSnack,
|
||||
sx,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
}, [connectionBase, filterJobDescriptionMessages, hasJobDescription, sessionId, setSnack, jobResponse, resetJobDescription, hasFacts, hasResume]);
|
||||
|
||||
/**
|
||||
* Renders the resume view with loading indicator
|
||||
*/
|
||||
const renderResumeView = useCallback((sx: SxProps) => {
|
||||
const resumeQuestions = [
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
<ChatQuery prompt="Is this resume a good fit for the provided job description?" tunables={{ enable_tools: false }} submitQuery={handleResumeQuery} />
|
||||
<ChatQuery prompt="Provide a more concise resume." tunables={{ enable_tools: false }} submitQuery={handleResumeQuery} />
|
||||
</Box>,
|
||||
];
|
||||
|
||||
if (!hasFacts) {
|
||||
return <Conversation
|
||||
ref={resumeConversationRef}
|
||||
{...{
|
||||
type: "resume",
|
||||
actionLabel: "Fact Check",
|
||||
defaultQuery: "Fact check the resume.",
|
||||
resetLabel: `job description${hasFacts ? ", resume, and fact check" : hasResume ? " and resume" : ""}`,
|
||||
messageFilter: filterResumeMessages,
|
||||
onResponse: resumeResponse,
|
||||
resetAction: resetResume,
|
||||
sessionId,
|
||||
connectionBase,
|
||||
setSnack,
|
||||
sx,
|
||||
}}
|
||||
/>
|
||||
} else {
|
||||
return <Conversation
|
||||
ref={resumeConversationRef}
|
||||
{...{
|
||||
type: "resume",
|
||||
actionLabel: "Send",
|
||||
prompt: "Ask a question about this job resume...",
|
||||
resetLabel: `job description${hasFacts ? ", resume, and fact check" : hasResume ? " and resume" : ""}`,
|
||||
messageFilter: filterResumeMessages,
|
||||
onResponse: resumeResponse,
|
||||
resetAction: resetResume,
|
||||
sessionId,
|
||||
connectionBase,
|
||||
setSnack,
|
||||
defaultPrompts: resumeQuestions,
|
||||
sx,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
}, [connectionBase, filterResumeMessages, hasFacts, sessionId, setSnack, resumeResponse, resetResume, hasResume]);
|
||||
|
||||
/**
|
||||
* Renders the fact check view
|
||||
*/
|
||||
const renderFactCheckView = useCallback((sx: SxProps) => {
|
||||
const factsQuestions = [
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
<ChatQuery prompt="Rewrite the resume to address any discrepancies." tunables={{ enable_tools: false }} submitQuery={handleFactsQuery} />
|
||||
</Box>,
|
||||
];
|
||||
|
||||
return <Conversation
|
||||
ref={factsConversationRef}
|
||||
{...{
|
||||
type: "fact_check",
|
||||
actionLabel: "Send",
|
||||
prompt: "Ask a question about any discrepencies...",
|
||||
resetLabel: `job description${hasFacts ? ", resume, and fact check" : hasResume ? " and resume" : ""}`,
|
||||
messageFilter: filterFactsMessages,
|
||||
defaultPrompts: factsQuestions,
|
||||
resetAction: resetFacts,
|
||||
onResponse: factsResponse,
|
||||
sessionId,
|
||||
connectionBase,
|
||||
setSnack,
|
||||
sx,
|
||||
}}
|
||||
/>
|
||||
}, [connectionBase, sessionId, setSnack, factsResponse, filterFactsMessages, resetFacts, hasResume, hasFacts]);
|
||||
|
||||
return (
|
||||
<Box className="ResumeBuilder"
|
||||
sx={{
|
||||
p: 0,
|
||||
m: 0,
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
margin: "0 auto",
|
||||
overflow: "hidden",
|
||||
backgroundColor: "#F5F5F5",
|
||||
flexDirection: "column",
|
||||
maxWidth: "1024px",
|
||||
}}
|
||||
>
|
||||
{/* Tabs */}
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onChange={handleTabChange}
|
||||
variant="fullWidth"
|
||||
sx={{ bgcolor: 'background.paper' }}
|
||||
>
|
||||
<Tab value={0} label="Job Description" />
|
||||
{hasResume && <Tab value={1} label="Resume" />}
|
||||
{hasFacts && <Tab value={2} label="Fact Check" />}
|
||||
</Tabs>
|
||||
|
||||
{/* Document display area */}
|
||||
<Box sx={{
|
||||
display: 'flex', flexDirection: 'column', flexGrow: 1, p: 0, width: "100%", ...sx,
|
||||
overflow: "hidden"
|
||||
}}>
|
||||
<Box sx={{ display: activeTab === 0 ? "flex" : "none" }}>{renderJobDescriptionView({ height: "calc(100vh - 72px - 48px)" })}</Box>
|
||||
<Box sx={{ display: activeTab === 1 ? "flex" : "none" }}>{renderResumeView({ height: "calc(100vh - 72px - 48px)" })}</Box>
|
||||
<Box sx={{ display: activeTab === 2 ? "flex" : "none" }}>{renderFactCheckView({ height: "calc(100vh - 72px - 48px)" })}</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export type {
|
||||
ResumeBuilderProps
|
||||
};
|
||||
|
||||
export {
|
||||
ResumeBuilder
|
||||
};
|
||||
|
@ -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={{
|
||||
|
@ -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 } = 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;
|
||||
@ -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