Delete unused components, add chat frontend

This commit is contained in:
Tobias Wasner 2023-01-16 22:51:48 +01:00
parent 1bc80ed7d2
commit f5e98edaa8
14 changed files with 99 additions and 762 deletions

View File

@ -23,6 +23,11 @@ app.route("/chat").post((req, res) => {
return;
}
if (msg.length === 0) {
res.status(200).end(true);
return;
}
getResponse(history, msg, res);
});
@ -63,7 +68,7 @@ async function getResponse(history, msg, res) {
history = history + '\nHuman: ' + msg;
const prompt = staticPrefix + '\n' + history;
console.log("sending to openai: " + prompt);
// console.log("sending to openai: " + prompt);
try {
const response = await openai.createCompletion({
@ -77,7 +82,7 @@ async function getResponse(history, msg, res) {
stop: [" Human:", " AI:"],
});
const stringResponse = JSON.stringify(response.data);
console.log("got from openai: " + stringResponse);
// console.log("got from openai: " + stringResponse);
res.status(200).end(history + response.data.choices[0].text);
} catch(error) {
console.error(error.response.status, error.response.data);

View File

@ -36,6 +36,7 @@
"postcss-jit-props": "^1.0.9",
"sass": "^1.57.1",
"vite": "^4.0.0",
"autoprefixer": "10.4.13"
"autoprefixer": "10.4.13",
"jquery": "3.6.3"
}
}

View File

@ -12,6 +12,7 @@ specifiers:
eslint: ^8.28.0
eslint-config-prettier: ^8.5.0
eslint-plugin-svelte3: ^4.0.0
jquery: 3.6.3
open-props: ^1.5.3
postcss: ^8.4.21
postcss-jit-props: ^1.0.9
@ -28,6 +29,7 @@ dependencies:
'@poppanator/sveltekit-svg': 2.0.2_svelte@3.55.0+vite@4.0.4
'@sveltejs/adapter-node': 1.1.0_@sveltejs+kit@1.0.7
autoprefixer: 10.4.13_postcss@8.4.21
jquery: 3.6.3
open-props: 1.5.3
postcss: 8.4.21
postcss-jit-props: 1.0.9_postcss@8.4.21
@ -1351,6 +1353,10 @@ packages:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
dev: true
/jquery/3.6.3:
resolution: {integrity: sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg==}
dev: false
/js-sdsl/4.2.0:
resolution: {integrity: sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==}
dev: true

View File

@ -1,127 +0,0 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import AutocompleteItem from './AutocompleteItem.svelte';
export let options: string[] = ['test-1', 'test-2', 'test-3'];
export let placeholder: string | undefined = undefined;
const dispatch = createEventDispatcher<{ select: string | undefined }>();
/* FILTERING countres DATA BASED ON INPUT */
let filteredCountries: string[] = [];
// $: console.log(filteredCountries)
const filterNace = () => {
let storageArr: string[] = [];
if (inputValue) {
options.forEach((option) => {
const matchIndex = option.toLowerCase().indexOf(inputValue.toLowerCase());
if (matchIndex !== -1) {
storageArr = [...storageArr, makeMatchBold(option, matchIndex)];
}
});
}
filteredCountries = storageArr;
};
/* HANDLING THE INPUT */
let searchInput: HTMLInputElement; // use with bind:this to focus element
let inputValue = '';
$: if (!inputValue) {
filteredCountries = [];
hiLiteIndex = null;
}
const clearInput = () => {
inputValue = '';
searchInput.focus();
};
const setInputVal = (categoryName: string) => {
inputValue = removeBold(categoryName);
filteredCountries = [];
hiLiteIndex = null;
document.querySelector('#nace-input').focus();
dispatch('select', inputValue);
};
const makeMatchBold = (str: string, start: number) => {
// replace part of (country name === inputValue) with strong tags
let matched = str.substring(start, start + inputValue.length);
let makeBold = `<strong>${matched}</strong>`;
let boldedMatch = str.replace(matched, makeBold);
return boldedMatch;
};
const removeBold = (str: string) => {
//replace < and > all characters between
return str.replace(/<(.)*?>/g, '');
// return str.replace(/<(strong)>/g, "").replace(/<\/(strong)>/g, "");
};
/* NAVIGATING OVER THE LIST OF COUNTRIES W HIGHLIGHTING */
let hiLiteIndex: null | number = null;
//$: console.log(hiLiteIndex);
$: hiLitedCountry = filteredCountries[hiLiteIndex];
const navigateList = (e) => {
if (e.key === 'ArrowDown' && hiLiteIndex <= filteredCountries.length - 1) {
hiLiteIndex === null ? (hiLiteIndex = 0) : (hiLiteIndex += 1);
} else if (e.key === 'ArrowUp' && hiLiteIndex !== null) {
hiLiteIndex === 0 ? (hiLiteIndex = filteredCountries.length - 1) : (hiLiteIndex -= 1);
} else if (e.key === 'Enter') {
setInputVal(filteredCountries[hiLiteIndex]);
} else {
return;
}
};
</script>
<svelte:window on:keydown={navigateList} />
<div class="autocomplete">
<input
id="nace-input"
type="text"
autocomplete=""
{placeholder}
bind:this={searchInput}
bind:value={inputValue}
on:input={filterNace}
/>
</div>
<!-- FILTERED LIST OF COUNTRIES -->
{#if filteredCountries.length > 0}
<ul id="autocomplete-items-list">
{#each filteredCountries as country, i}
<AutocompleteItem
itemLabel={country}
highlighted={i === hiLiteIndex}
on:click={() => setInputVal(country)}
/>
{/each}
</ul>
{/if}
<style>
div.autocomplete {
display: block;
width: 100%;
}
#autocomplete-items-list {
position: relative;
margin: 0;
padding: 0;
top: -11px;
border: 2px solid rgba(var(--color-foreground-light-raw), 0.2);
background-color: var(--color-background);
border-bottom-left-radius: var(--size-3);
border-bottom-right-radius: var(--size-3);
overflow: hidden;
box-shadow: var(--shadow-5);
}
</style>

View File

@ -1,45 +0,0 @@
<script>
export let itemLabel;
export let highlighted;
</script>
<li class="autocomplete-items" class:autocomplete-active={highlighted} on:click>
{@html itemLabel}
</li>
<style lang="scss">
li.autocomplete-items {
display: block;
list-style: none;
border-bottom: 1px solid rgba(var(--color-foreground-raw), 0.2);
z-index: 99;
/*position the autocomplete items to be the same width as the container:*/
width: 100%;
padding: var(--size-2) var(--size-4);
cursor: pointer;
background-color: transparent;
max-inline-size: unset;
&:last-child {
border-bottom: 0;
}
}
li.autocomplete-items:hover {
/*when hovering an item:*/
background-color: var(--color-primary);
color: white;
}
li.autocomplete-items:active {
/*when navigating through the items using the arrow keys:*/
background-color: var(--color-primary) !important;
color: #ffffff;
}
.autocomplete-active {
/*when navigating through the items using the arrow keys:*/
background-color: var(--color-primary) !important;
color: #ffffff;
}
</style>

View File

@ -1,127 +0,0 @@
<script lang="ts">
import Card from '../Card.svelte';
import type { RadioOption } from './types';
// based on suggestions from:
// Sami Keijonen https://webdesign.tutsplus.com/tutorials/how-to-make-custom-accessible-checkboxes-and-radio-buttons--cms-32074
// and Inclusive Components by Heydon Pickering https://inclusive-components.design/toggle-button/
export let options: RadioOption[] = [];
export let legend: string | undefined = undefined;
export let userSelected: string | undefined = undefined;
export let fontSize = 16;
export let flexDirection = 'column';
const uniqueID = Math.floor(Math.random() * 100);
const slugify = (str = '') => str.toLowerCase().replace(/ /g, '-').replace(/\./g, '');
</script>
<div
role="radiogroup"
class="group-container"
aria-labelledby={`label-${uniqueID}`}
id={`group-${uniqueID}`}
>
{#each options as { value, label, icon }}
<label for={slugify(label)}>
<Card class={'radio-wrap' + (value === userSelected ? ' selected' : '')}>
<span class="icon">{icon}</span>
<input class="sr-only" type="radio" id={slugify(label)} bind:group={userSelected} {value} />
{label}
</Card>
</label>
{/each}
</div>
<style lang="scss">
@use 'sass:math';
@import '$lib/style/app.scss';
$dot-size: 1.5rem;
:global(.radio-wrap) {
display: flex;
flex-direction: column;
align-items: center;
font-size: 1.5rem;
border: 5px solid var(--color-background) !important;
.icon {
font-size: 4rem;
}
}
:global(.card.selected) {
border: 5px solid var(--color-primary) !important;
}
.group-container {
border-radius: 2px;
display: flex;
flex-direction: column;
@media (min-width: $breakpoint-small) {
flex-direction: row;
}
align-items: center;
justify-content: center;
gap: var(--size-4);
}
.legend {
font-weight: bold;
}
label {
width: 100%;
display: block;
cursor: pointer;
user-select: none;
display: flex;
align-items: center;
justify-content: flex-start;
margin-bottom: var(--size-4);
:global(.card) {
width: 100%;
}
}
.sr-only {
position: absolute;
clip: rect(1px, 1px, 1px, 1px);
padding: 0;
border: 0;
height: 1px;
width: 1px;
overflow: hidden;
}
input[type='radio'] {
position: relative;
&:checked {
& + label {
font-weight: bold;
}
& + label::before {
border-color: var(--color-primary);
}
& + label::after {
transform: scale(1);
}
}
}
input[type='radio']:disabled + label {
color: rgba(var(--color-foreground-raw), 0.2);
}
input[type='radio']:disabled + label::before {
background: rgba(var(--color-foreground-raw), 0.2);
}
</style>

View File

@ -1 +0,0 @@
export type RadioOption = { value: string; label: string; icon: string };

View File

@ -1,304 +0,0 @@
<script>
import { createEventDispatcher } from 'svelte';
import { fly, fade } from 'svelte/transition';
// Props
export let min = 1;
export let max = 10000;
export let initialValue = 0;
export let id = null;
export let value = typeof initialValue === 'string' ? parseInt(initialValue) : initialValue;
// Node Bindings
let container = null;
let thumb = null;
let progressBar = null;
let element = null;
// Internal State
let elementX = null;
let currentThumb = null;
let holding = false;
let thumbHover = false;
let keydownAcceleration = 0;
let accelerationTimer = null;
// Dispatch 'change' events
const dispatch = createEventDispatcher();
// Mouse shield used onMouseDown to prevent any mouse events penetrating other elements,
// ie. hover events on other elements while dragging. Especially for Safari
const mouseEventShield = document.createElement('div');
mouseEventShield.setAttribute('class', 'mouse-over-shield');
mouseEventShield.addEventListener('mouseover', (e) => {
e.preventDefault();
e.stopPropagation();
});
function resizeWindow() {
elementX = element.getBoundingClientRect().left;
}
// Allows both bind:value and on:change for parent value retrieval
function setValue(val) {
value = val;
dispatch('change', { value });
}
function onTrackEvent(e) {
// Update value immediately before beginning drag
updateValueOnEvent(e);
onDragStart(e);
}
function onHover(e) {
thumbHover = thumbHover ? false : true;
}
function onDragStart(e) {
// If mouse event add a pointer events shield
if (e.type === 'mousedown') document.body.append(mouseEventShield);
currentThumb = thumb;
}
function onDragEnd(e) {
// If using mouse - remove pointer event shield
if (e.type === 'mouseup') {
if (document.body.contains(mouseEventShield)) document.body.removeChild(mouseEventShield);
// Needed to check whether thumb and mouse overlap after shield removed
if (isMouseInElement(e, thumb)) thumbHover = true;
}
currentThumb = null;
}
// Check if mouse event cords overlay with an element's area
function isMouseInElement(event, element) {
let rect = element.getBoundingClientRect();
let { clientX: x, clientY: y } = event;
if (x < rect.left || x >= rect.right) return false;
if (y < rect.top || y >= rect.bottom) return false;
return true;
}
// Accessible keypress handling
function onKeyPress(e) {
// Max out at +/- 10 to value per event (50 events / 5)
// 100 below is to increase the amount of events required to reach max velocity
if (keydownAcceleration < 50) keydownAcceleration++;
let throttled = Math.ceil(keydownAcceleration / 5);
if (e.key === 'ArrowUp' || e.key === 'ArrowRight') {
if (value + throttled > max || value >= max) {
setValue(max);
} else {
setValue(value + throttled);
}
}
if (e.key === 'ArrowDown' || e.key === 'ArrowLeft') {
if (value - throttled < min || value <= min) {
setValue(min);
} else {
setValue(value - throttled);
}
}
// Reset acceleration after 100ms of no events
clearTimeout(accelerationTimer);
accelerationTimer = setTimeout(() => (keydownAcceleration = 1), 100);
}
function calculateNewValue(clientX) {
// Find distance between cursor and element's left cord (20px / 2 = 10px) - Center of thumb
let delta = clientX - (elementX + 10);
// Use width of the container minus (5px * 2 sides) offset for percent calc
let percent = (delta * 100) / (container.clientWidth - 10);
// Limit percent 0 -> 100
percent = percent < 0 ? 0 : percent > 100 ? 100 : percent;
// Limit value min -> max
setValue(parseInt((percent * (max - min)) / 100) + min);
}
// Handles both dragging of touch/mouse as well as simple one-off click/touches
function updateValueOnEvent(e) {
// touchstart && mousedown are one-off updates, otherwise expect a currentPointer node
if (!currentThumb && e.type !== 'touchstart' && e.type !== 'mousedown') return false;
if (e.stopPropagation) e.stopPropagation();
if (e.preventDefault) e.preventDefault();
// Get client's x cord either touch or mouse
const clientX =
e.type === 'touchmove' || e.type === 'touchstart' ? e.touches[0].clientX : e.clientX;
calculateNewValue(clientX);
}
// React to left position of element relative to window
$: if (element) elementX = element.getBoundingClientRect().left;
// Set a class based on if dragging
$: holding = Boolean(currentThumb);
// Update progressbar and thumb styles to represent value
$: if (progressBar && thumb) {
// Limit value min -> max
value = value > min ? value : min;
value = value < max ? value : max;
let percent = ((value - min) * 100) / (max - min);
let offsetLeft = (container.clientWidth - 10) * (percent / 100) + 5;
// Update thumb position + active range track width
thumb.style.left = `${offsetLeft}px`;
progressBar.style.width = `${offsetLeft}px`;
}
</script>
<svelte:window
on:touchmove|nonpassive={updateValueOnEvent}
on:touchcancel={onDragEnd}
on:touchend={onDragEnd}
on:mousemove={updateValueOnEvent}
on:mouseup={onDragEnd}
on:resize={resizeWindow}
/>
<div class="range">
<div
class="range__wrapper"
tabindex="0"
on:keydown={onKeyPress}
bind:this={element}
role="slider"
aria-valuemin={min}
aria-valuemax={max}
aria-valuenow={value}
{id}
on:mousedown={onTrackEvent}
on:touchstart={onTrackEvent}
>
<div class="range__track" bind:this={container}>
<div class="range__track--highlighted" bind:this={progressBar} />
<div
class="range__thumb"
class:range__thumb--holding={holding}
bind:this={thumb}
on:touchstart={onDragStart}
on:mousedown={onDragStart}
on:mouseover={() => (thumbHover = true)}
on:mouseout={() => (thumbHover = false)}
>
{#if holding || thumbHover}
<div class="range__tooltip" in:fly={{ y: 7, duration: 200 }} out:fade={{ duration: 100 }}>
{value}
</div>
{/if}
</div>
</div>
</div>
</div>
<svelte:head>
<style>
.mouse-over-shield {
position: fixed;
top: 0px;
left: 0px;
height: 100%;
width: 100%;
background-color: rgba(255, 0, 0, 0);
z-index: 10000;
cursor: grabbing;
}
</style>
</svelte:head>
<style>
.range {
position: relative;
flex: 1;
}
.range__wrapper {
min-width: 100%;
position: relative;
padding: 0.5rem;
box-sizing: border-box;
outline: none;
}
.range__wrapper:focus-visible > .range__track {
box-shadow: 0 0 0 2px white, 0 0 0 3px var(--color-primary);
}
.range__track {
height: 6px;
background-color: rgba(var(--color-foreground-raw), 0.1);
border-radius: 999px;
}
.range__track--highlighted {
background-color: var(--color-primary);
width: 0;
height: 6px;
position: absolute;
border-radius: 999px;
}
.range__thumb {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
width: 20px;
height: 20px;
background-color: var(--color-primary);
cursor: pointer;
border-radius: 999px;
margin-top: -8px;
transition: box-shadow 100ms;
user-select: none;
box-shadow: var(
--thumb-boxshadow,
0 1px 1px 0 rgba(0, 0, 0, 0.14),
0 0px 2px 1px rgba(0, 0, 0, 0.2)
);
}
.range__thumb--holding {
box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 1px 2px 1px rgba(0, 0, 0, 0.2),
0 0 0 6px var(--thumb-holding-outline, rgba(113, 119, 250, 0.3));
}
.range__tooltip {
pointer-events: none;
position: absolute;
top: -50px;
color: var(--tooltip-text, white);
width: 120px;
padding: 4px 0;
border-radius: 4px;
text-align: center;
background-color: var(--color-primary);
background: var(
--tooltip-bg,
linear-gradient(45deg, var(--color-primary), var(--color-primary))
);
}
.range__tooltip::after {
content: '';
display: block;
position: absolute;
height: 7px;
width: 7px;
background-color: var(--color-primary);
bottom: -3px;
left: calc(50% - 3px);
clip-path: polygon(0% 0%, 100% 100%, 0% 100%);
transform: rotate(-45deg);
border-radius: 0 0 0 3px;
}
</style>

View File

@ -1,86 +0,0 @@
<script lang="ts">
export let stepCount: number = 4;
export let currentStep: number = 4;
export let enabledStep: number = 4;
const onStepClick = (idx: number) => {
if (idx >= enabledStep) {
return;
}
currentStep = idx;
};
</script>
<div class="stepper">
{#each Array(stepCount) as _, i}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
on:click={() => onStepClick(i + 1)}
class="step"
class:completed={i < currentStep}
class:active={currentStep === i + 1}
>
{i + 1}
</div>
{#if i < stepCount - 1}
<div class="dash" class:completed={i < currentStep} />
{/if}
{/each}
</div>
<style lang="scss">
@import '$lib/style/app.scss';
.stepper {
display: flex;
align-items: center;
justify-content: center;
user-select: none;
gap: var(--size-2);
@media (min-width: $breakpoint-small) {
gap: var(--size-4);
}
}
.step {
cursor: pointer;
display: flex;
width: 50px;
height: 50px;
border-radius: 50%;
align-items: center;
justify-content: center;
font-weight: bold;
flex-shrink: 0;
border: 2px solid rgba(var(--color-foreground-raw), 0.2);
color: rgba(var(--color-foreground-raw), 0.2);
transition: all 0.2s var(--easing-default);
&.completed {
border: 2px solid var(--color-primary);
color: var(--color-primary);
}
&.active {
background-color: var(--color-primary);
color: #fff;
}
&:hover {
transform: scale(1.1);
}
}
.dash {
display: block;
width: 50px;
height: 6px;
content: '';
border-radius: 3px;
background-color: rgba(var(--color-foreground-raw), 0.1);
&.completed {
background-color: rgba(var(--color-primary-raw), 0.4);
}
}
</style>

View File

@ -1,62 +0,0 @@
<script lang="ts">
import { onMount } from 'svelte';
import AutocompleteInput from '../Autocomplete.svelte/AutocompleteInput.svelte';
import type { NaceEntry } from './types';
export let selectedNace: NaceEntry | undefined = undefined;
let options: string[] = [];
let naceOptions: NaceEntry[] = [];
onMount(async () => {
const naceEntries: NaceEntry[] = await fetch('/nace.json').then((r) => r.json());
if (!naceEntries) {
return;
}
naceOptions = naceEntries;
options = naceEntries.map((entry) => `${entry.code} - ${entry.name}`);
});
const onNaceSelected = (evt: CustomEvent<string | undefined>) => {
const nace = evt.detail;
if (!nace) {
selectedNace = undefined;
}
let code = nace!.split(' ')[0];
// Find matching entry
selectedNace = naceOptions.find((nace) => nace.code === code);
};
</script>
<form>
<section>
<h2>Company Classification</h2>
<label>
<span>Select NACE code</span>
<AutocompleteInput on:select={onNaceSelected} {options} placeholder="NACE Code" />
</label>
<slot />
<br /><br />
<br />
<div>
<h3>What are NACE codes?</h3>
<p>
NACE codes are used to classify economic activities in the European Union (EU). They are
based on the NACE (Nomenclature of Economic Activities) classification system, which was
developed by the EU to provide a common framework for the collection and analysis of
statistical data related to economic activities. NACE codes are used to identify the type of
business or industry in which a company operates, and are typically used for statistical and
analytical purposes.
</p>
</div>
</section>
</form>
<style lang="scss">
label > span {
padding-left: var(--size-3);
}
</style>

View File

@ -1,5 +0,0 @@
export type NaceEntry = {
section: string;
code: string;
name: string;
};

View File

@ -0,0 +1,70 @@
<script lang="ts">
import { goto } from '$app/navigation';
import Button from '$lib/components/Button.svelte';
import Card from '$lib/components/Card.svelte';
import Container from '$lib/components/Container.svelte';
import { onMount } from 'svelte';
import Input from '$lib/components/Input.svelte';
import Liquid from '$lib/components/Liquid.svelte';
import Notification from '$lib/components/Notification.svelte';
import jQuery from 'jquery';
let token: string = '';
let msg: string | undefined = undefined;
let history: string | undefined = undefined;
onMount(() => {
token = localStorage.getItem('token') ?? '';
});
function sendRequest() {
if (!msg || msg.length == 0) {
return;
}
// TODO: Loading animation!
jQuery.post("https://" + window.location.hostname + "/api", {
history: history,
msg: msg,
token: token
}).done(function (data: any) {
history = data;
msg = "";
});
}
function keyUp (e: { key: string; keyCode: number; }) {
if (e.key === 'Enter' || e.keyCode === 13) {
sendRequest();
}
};
</script>
<Container centered>
<p>
<Card background={false}>
<form>
<section>
<h2>Chatbot</h2>
<textarea bind:value={history} disabled />
<input id="chatbot-input" type="text" placeholder="Type your message here..." bind:value={msg} on:keyup={keyUp} />
<Button on:click={sendRequest}>Send</Button>
</section>
</form>
</Card>
</p>
</Container>
<style lang="scss">
@import '$lib/style/app.scss';
section {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: var(--size-5);
}
textarea {
width: 100%;
height: 50vh;
}
</style>

View File

@ -6,6 +6,18 @@
import Input from '$lib/components/Input.svelte';
import Liquid from '$lib/components/Liquid.svelte';
import Notification from '$lib/components/Notification.svelte';
let token: string | undefined = undefined;
const onLogin = (evt: MouseEvent) => {
if (!token) {
evt.preventDefault();
evt.stopPropagation();
return;
}
localStorage.setItem('token', token ?? '');
};
</script>
<Container centered>
@ -18,8 +30,8 @@
<form>
<section>
<h2>Login</h2>
<input placeholder="Access token" type="password" />
<Button primary>Login</Button>
<input placeholder="Access token" type="password" bind:value={token} />
<a href="/chat" on:click={onLogin}><Button>Login</Button></a>
</section>
</form>
</Card>