Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

staging to prod #83

Merged
merged 37 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
5039d43
feat: reduce spacing in history section (#52)
aeschi Aug 23, 2024
5f4c861
Feat:single prompt mail (#53)
aeschi Aug 23, 2024
0454407
feat: make toggle responsive (#54)
aeschi Aug 23, 2024
4f0afd3
feat: add selector for model in chat function (#50)
aeschi Aug 26, 2024
ce592c1
feat: add varying model description
aeschi Aug 27, 2024
f4e8247
settings intro text
Esshahn Aug 27, 2024
4787f04
prettier check
Esshahn Aug 27, 2024
9e67d24
add default text to model description
Esshahn Aug 27, 2024
dc9533a
Merge pull request #56 from technologiestiftung/feat/settings-text
Esshahn Aug 27, 2024
74edfac
Merge pull request #57 from technologiestiftung/feat/add-model-descri…
Esshahn Aug 27, 2024
de3a4db
feat: switch order of navigation (#59)
aeschi Aug 27, 2024
c394f95
Feat: email prompt improvement (#55)
aeschi Aug 27, 2024
be351ad
feat: add text summary feature (#58)
aeschi Aug 29, 2024
b7f02e4
fix: add correct copied icon again (#62)
aeschi Aug 29, 2024
d3fc4c8
Feat/update file upload design (#60)
aeschi Aug 29, 2024
6cc4604
Fix/small design errors (#63)
aeschi Aug 30, 2024
7814439
fix: use requestSubmit instead of dispatchEvent with virtual event
raphael-arce Aug 29, 2024
75591fb
feat: use model details from api
raphael-arce Aug 29, 2024
59c8acc
feat: add help button/dialog and refactor the QA component as a gener…
raphael-arce Aug 29, 2024
e206718
feat: adjust text size spacing and color (#68)
aeschi Aug 30, 2024
058c1c7
Feat/adjust textarea height (#61)
aeschi Aug 30, 2024
1c314ab
feat: remove email-chat toggle, make email-chat default in sidebar,
raphael-arce Aug 30, 2024
fdc0733
Feat/force word break (#67)
aeschi Aug 30, 2024
ea4ad78
feat: update help dialog text
raphael-arce Aug 30, 2024
6b152ab
feat: add backdrop blur to splash screen (#72)
aeschi Aug 30, 2024
449233a
feat: add backdrop blur to navigation (#73)
aeschi Aug 30, 2024
17e11a4
Feat/text edit prompt polish (#71)
aeschi Aug 30, 2024
716fdea
feat: text changes (#74)
aeschi Aug 30, 2024
9b992a0
fix: persist settings (#76)
Jaszkowic Sep 23, 2024
86e8c20
chore: merged main
Jaszkowic Sep 23, 2024
f77f10e
feat: error handling for llm health (#78)
Jaszkowic Sep 25, 2024
7f911b7
feat: count token and show warning (#79)
Jaszkowic Sep 26, 2024
6b7408a
feat: update faq (#80)
aeschi Sep 30, 2024
bc704cd
fix: user scrolling only recognized upwards (#81)
aeschi Sep 30, 2024
a03b144
chore: merged main
Jaszkowic Oct 1, 2024
b232f43
fix: imports
Jaszkowic Oct 1, 2024
e740a73
chore: rename to selectedLlm
Jaszkowic Oct 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
},
"dependencies": {
"date-fns": "3.6.0",
"js-tiktoken": "1.0.14",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-markdown": "9.0.1",
Expand Down
20 changes: 20 additions & 0 deletions src/components/capacity-error-hint.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from "react";

interface CapacityErrorProps {
tokenCapacity: number;
}
export const CapacityError: React.FC<CapacityErrorProps> = ({
tokenCapacity,
}) => {
return (
<div className="md:w-[640px] lg:w-[768px] w-full flex flex-row gap-4 rounded-sm border-4 border-ber-orange bg-white px-6 py-3 text-sm text-ber-darker-grey shadow-md mb-2 mx-5 items-center">
<div className="w-full flex justify-center">
Die maximale Länge dieser Konversation ist {tokenCapacity < 1 && "bald"}{" "}
erreicht.
<a href="/" className="mx-1 underline">
Bitte starten Sie einen neuen Chat.
</a>
</div>
</div>
);
};
12 changes: 9 additions & 3 deletions src/components/chat/chat-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,13 @@ async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
setIsLoading(false);
}

export const ChatForm: React.FC = () => {
interface ChatFormProps {
isDisabledDueToExceededTokenCapacity: boolean;
}

export const ChatForm: React.FC<ChatFormProps> = ({
isDisabledDueToExceededTokenCapacity,
}) => {
const { isLoading: isGPTResponseLoading } = useIsLoadingStore();
const [isSendDisabled, setIsSendDisabled] = useState(true);

Expand Down Expand Up @@ -71,9 +77,9 @@ export const ChatForm: React.FC = () => {

<button
type="submit"
disabled={isLoading}
disabled={isLoading || isDisabledDueToExceededTokenCapacity}
className={`text-ber-darker-grey hover:text-ber-dark-grey disabled:text-ber-light-grey
${isSendDisabled && files.length === 0 && "text-ber-light-grey hover:text-ber-light-grey"}`}
${isDisabledDueToExceededTokenCapacity || (isSendDisabled && files.length === 0 && "text-ber-light-grey hover:text-ber-light-grey")}`}
>
<SendIcon className="w-8 h-8" />
</button>
Expand Down
46 changes: 24 additions & 22 deletions src/components/chat/chat-messages.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useRef } from "react";
import React, { useEffect, useRef, useState } from "react";
import { useChatHistoryStore } from "../../store/chat-history-store";
import { useCurrentChatIdStore } from "../../store/current-chat-id-store";
import { FileMessage } from "../messages/file-message";
Expand All @@ -11,6 +11,12 @@ export const ChatMessages: React.FC = () => {
const { currentChatId } = useCurrentChatIdStore();
const { setHasUserScrolled } = useHasUserScrolledStore();

const isSafari =
navigator.userAgent.indexOf("Safari") !== -1 &&
navigator.userAgent.indexOf("Chrome") === -1;

const [previousScrollTop, setPreviousScrollTop] = useState(0);

const messages: Message[] = getChat(currentChatId)?.messages || [];

const messageContainerRef = useRef<null | HTMLDivElement>(null);
Expand All @@ -22,10 +28,8 @@ export const ChatMessages: React.FC = () => {
messagesContainer &&
!useHasUserScrolledStore.getState().hasUserScrolled
) {
messagesContainer.scrollTo({
top: messagesContainer.scrollHeight,
behavior: "smooth",
});
messagesContainer.scrollTop =
messagesContainer.scrollHeight - messagesContainer.clientHeight;
}
};

Expand All @@ -37,30 +41,26 @@ export const ChatMessages: React.FC = () => {
}

const isScrollPositionCloseToEnd =
messagesContainer.scrollTop + messagesContainer.clientHeight >=
messagesContainer.scrollHeight - 10;
messagesContainer.scrollHeight - messagesContainer.clientHeight <=
messagesContainer.scrollTop + 1;

if (isScrollPositionCloseToEnd) {
setHasUserScrolled(false);
return;
}

setHasUserScrolled(true);
};

useEffect(() => {
const messagesContainer = messageContainerRef.current;
const currentScrollTop = messagesContainer.scrollTop;

if (!messagesContainer) {
return () => {};
/**
* Only stop auto scrolling to the new message, when user is scrolling towards top.
* When the user is scrolling down, there is no need to stop auto scrolling.
*/
const hasUserScrolledTowardsTop = previousScrollTop > currentScrollTop;
if (hasUserScrolledTowardsTop) {
setHasUserScrolled(true);
}

messagesContainer.addEventListener("scroll", handleScroll);

return () => {
messagesContainer.removeEventListener("scroll", handleScroll);
};
}, []);
setPreviousScrollTop(currentScrollTop);
};

useEffect(() => {
scrollToBottom();
Expand All @@ -69,7 +69,9 @@ export const ChatMessages: React.FC = () => {
return (
<div
ref={messageContainerRef}
className="flex w-full justify-center overflow-y-auto overflow-x-hidden scroll-smooth mb-2"
onScroll={handleScroll}
className={`flex w-full justify-center overflow-y-auto overflow-x-hidden mb-2
${isSafari ? "scroll-auto" : "scroll-smooth"}`}
>
<div className="md:w-[640px] lg:w-[768px] w-full h-full flex flex-col gap-y-4 px-5 md:pr-0 md:pl-2 ">
{messages.map((message) => (
Expand Down
24 changes: 18 additions & 6 deletions src/components/chat/chat.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import React from "react";
import { GetStarted } from "./get-started/get-started";
import React, { useMemo } from "react";
import { useChatHistoryStore } from "../../store/chat-history-store";
import { useCurrentChatIdStore } from "../../store/current-chat-id-store";
import { CapacityError } from "../capacity-error-hint";
import { Help } from "../help/help";
import { UploadedFiles } from "../uploaded-files";
import { ChatForm } from "./chat-form";
import { ChatMessages } from "./chat-messages";
import { Help } from "../help/help";
import { GetStarted } from "./get-started/get-started";

export const Chat: React.FC = () => {
const { getChat } = useChatHistoryStore();
const { getChat, getFreeChatTokenCapacity, tokenCapacityWarningThreshold } =
useChatHistoryStore();
const { currentChatId } = useCurrentChatIdStore();
const messages = getChat(currentChatId)?.messages || [];

const freeChatTokenCapacity = useMemo(() => {
return getFreeChatTokenCapacity();
}, [currentChatId]);

return (
<div className="flex h-full flex-col items-center justify-between pt-14 py-5 relative">
<Help />
Expand All @@ -20,11 +26,17 @@ export const Chat: React.FC = () => {

<ChatMessages />

<div className="px-5 w-full flex justify-center">
<div className="px-5 w-full flex flex-col justify-center items-center">
{freeChatTokenCapacity > tokenCapacityWarningThreshold && (
<CapacityError tokenCapacity={freeChatTokenCapacity}></CapacityError>
)}

<div className="md:w-[640px] lg:w-[768px] w-full z-10 flex flex-col bg-ber-lighter-grey">
<UploadedFiles />

<ChatForm />
<ChatForm
isDisabledDueToExceededTokenCapacity={freeChatTokenCapacity >= 1}
/>
</div>
</div>
</div>
Expand Down
61 changes: 57 additions & 4 deletions src/components/dialogs/faq-dialog/qas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,40 @@ export const qas = [
{
question:
"Was muss ich bei der Nutzung in Sachen Datenschutz und Datensicherheit beachten?",
answer:
"BärGPT nutzt aktuell eine europäische Instanz des Sprachmodells GPT 4o-mini des Anbieters OpenAI. Das Modell wird in einem schwedischen Rechenzentrum betrieben und liegt damit im DSGVO-Raum. Eingegebene Daten werden nicht gespeichert und nicht zu Trainingszwecken verwendet. Damit bietet das Modell ein höheres Datenschutzniveau als vergleichbare Angebote aus den USA. Es ist jedoch zu berücksichtigen, dass das zugrundeliegende KI-Modell nicht im sicheren Berliner Landesnetz betrieben wird. Personenbezogene oder anderweitig sensible Daten, sowie Daten, die ausschließlich für die Nutzung im Berliner Landesnetz vorgesehen sind, dürfen nicht eingegeben werden.",
answer: (
<div className="flex flex-col gap-y-3">
<p>
BärGPT bietet Nutzer:innen verschiedene Large Language Modelle zur
Auswahl, die sich bezüglich des Datenschutzes unterscheiden.
</p>
<p>
Das Modell <b>azure-gpt-4o-mini</b> wird von Microsoft in einem
schwedischen Rechenzentrum betrieben und liegt damit im DSGVO-Raum.
Eingegebene Daten werden weder gespeichert noch zu Trainingszwecken
verwendet. Damit bietet das Modell ein höheres Datenschutzniveau als
vergleichbare Angebote aus den USA.
</p>
<p>
Das Modell <b>openai-gpt-4o-mini</b> bietet die gleiche
Funktionalität, wird jedoch von OpenAI selbst betrieben. Der
Serverstandort ist hier in den Vereinigten Staaten. Dieses Modell
sollte lediglich zu Vergleichszwecken genutzt werden und bietet
ansonsten keine Vorteile gegenüber dem Modell von Microsoft.
</p>
<p>
Das Modell <b>citylab-macstudio-llama-3.1</b> wird datenschutzkonform
vom CityLAB Berlin gehostet. Es handelt sich um ein Open
Source-Modell, welches in der Qualität oft mit den kommerziellen
Angeboten mithalten kann.
</p>
<p>
Es ist zu berücksichtigen, dass keines der verfügbaren KI-Modelle im
Berliner Landesnetz betrieben wird. Personenbezogene oder anderweitig
sensible Daten, sowie Daten, die ausschließlich für die Nutzung im
Berliner Landesnetz vorgesehen sind, dürfen nicht eingegeben werden.
</p>
</div>
),
},
{
question:
Expand All @@ -23,7 +55,28 @@ export const qas = [
},
{
question: "Kann BärGPT auch für andere Anwendungsfälle genutzt werden?",
answer:
"BärGPT ist eine flexible KI-Infrastruktur und kann prinzipiell für verschiedene Anwendungsfälle und Kontexte angepasst und weiterentwickelt werden. Sie haben eine Idee für einen speziellen Anwendungsfall, bei dem BärGPT unterstützen kann? Dann freuen wir uns über eine Nachricht.",
answer: (
<div className="flex flex-col gap-y-3">
<p>
BärGPT ist eine flexible KI-Infrastruktur und kann prinzipiell für
verschiedene Anwendungsfälle und Kontexte angepasst und
weiterentwickelt werden. Sie haben eine Idee für einen speziellen
Anwendungsfall, bei dem BärGPT unterstützen kann? Dann freuen wir uns
über eine Nachricht.
</p>
<p>
Allgemeine Hinweise und Empfehlungen zum Umgang mit generativen
KI-Anwendungen in der Berliner Verwaltung finden Sie in der{" "}
<a
target="_blank"
rel="noreferrer"
className="text-ber-mid-grey underline hover:text-ber-darker-grey"
href="https://www.berlin.de/politik-und-verwaltung/rundschreiben/index.php?category=RBmSKzl&issue_no=2&issue_year=2024&send=1"
>
Rundschreibendatenbank des Landes Berlin.
</a>
</p>
</div>
),
},
];
29 changes: 27 additions & 2 deletions src/components/dialogs/settings-dialog/settings-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ import React from "react";
import { DefaultDialog } from "../default-dialog";
import { useCurrentLLMStore } from "../../../store/current-llm-store";
import { SettingsOption } from "./settings-option";
import { ErrorIcon } from "../../icons/error-icon";
import { RefreshIcon } from "../../icons/refresh-icon";

export const settingsDialogId = "settings-dialog";

export const SettingsDialog: React.FC = () => {
const { availableLLMs } = useCurrentLLMStore();
const {
availableLLMs,
selectedLLM: currentLLM,
getAvailableLLMs,
} = useCurrentLLMStore();

return (
<DefaultDialog
Expand All @@ -16,6 +22,7 @@ export const SettingsDialog: React.FC = () => {
<div className="w-full flex justify-center">
<div className="flex flex-col max-w-[600px] gap-y-5 md:gap-y-5">
<div className="text-2xl font-bold">Einstellungen</div>

<div className="text-lg flex flex-col gap-3">
<div className="font-bold">Modellauswahl</div>
<p>
Expand All @@ -26,12 +33,30 @@ export const SettingsDialog: React.FC = () => {
entspricht.
</p>
</div>

<div className="flex flex-col gap-x-2">
{availableLLMs.map((option) => (
<SettingsOption key={option.identifier} option={option} />
))}
</div>
{currentLLM && !currentLLM.status.healthy && (
<div className="text-sm md:text-lg">
<div className="flex w-full flex-row">
<div className="w-full flex flex-row items-center justify-between gap-4 rounded-sm border-4 border-ber-orange bg-white px-6 py-4 text-[16px] font-semibold text-ber-darker-grey shadow-md">
<ErrorIcon />
Das derzeit gewählte Modell ist temporär nicht verfügbar,
bitte wählen Sie ein alternatives Modell.
<button
className="pl-8"
onClick={async () => {
await getAvailableLLMs();
}}
>
<RefreshIcon />
</button>
</div>
</div>
</div>
)}
</div>
</div>
</DefaultDialog>
Expand Down
Loading