All UI seems to work

This commit is contained in:
James Ketr 2025-05-10 15:52:33 -07:00
parent c97d24be68
commit 97425a6aad
26 changed files with 292 additions and 1394 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,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"
]
}

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,
}} />
);
};

View File

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

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

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

View File

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

View File

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

View File

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

View File

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