From af621278ad260417ad0849415ffdec8be123a01c Mon Sep 17 00:00:00 2001 From: Karim Tabet Date: Fri, 22 Mar 2024 11:13:31 +0000 Subject: [PATCH 01/22] Use GunicornInternalPrometheusMetrics and example Gauge metric --- server/Dockerfile | 2 +- server/Makefile | 3 ++- server/app.py | 4 ++-- server/docker-entrypoint.sh | 2 +- server/gunicorn_config.py | 6 ++++++ server/routes.py | 11 +++++++++++ 6 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 server/gunicorn_config.py diff --git a/server/Dockerfile b/server/Dockerfile index 7fa0d5d..8317cb4 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -6,7 +6,7 @@ RUN pip install --upgrade pip RUN pip install pipenv RUN pipenv requirements > requirements.txt RUN pip install --no-cache-dir --upgrade -r requirements.txt -COPY init.py app.py models.py routes.py utils.py docker-entrypoint.sh ./ +COPY init.py app.py models.py routes.py utils.py docker-entrypoint.sh gunicorn_config.py ./ COPY default_documents ./default_documents COPY default_settings ./default_settings COPY system_settings ./system_settings diff --git a/server/Makefile b/server/Makefile index 35fde50..e59ad8d 100644 --- a/server/Makefile +++ b/server/Makefile @@ -13,7 +13,8 @@ run-dev-locally: init pipenv run python run.py run-prod-locally: init - pipenv run gunicorn -w 4 -b 0.0.0.0:5003 app:app + pipenv run python init.py + gunicorn -c gunicorn_config.py --worker-class gevent -b --timeout 120 --workers 4 --threads 4 0.0.0.0:5000 app:app stop-container: docker stop sidekick-server; \ diff --git a/server/app.py b/server/app.py index 775c2ca..8f2dd2f 100644 --- a/server/app.py +++ b/server/app.py @@ -8,7 +8,7 @@ from flask_oidc import OpenIDConnect from sqlalchemy.engine.url import make_url import uuid -from prometheus_flask_exporter import PrometheusMetrics +from prometheus_flask_exporter.multiprocess import GunicornInternalPrometheusMetrics VERSION = "0.2.3" server_instance_id = str(uuid.uuid4()) @@ -54,7 +54,7 @@ CORS(app) migrate = Migrate(app, db) -metrics = PrometheusMetrics(app) +metrics = GunicornInternalPrometheusMetrics(app) # static information as metric metrics.info('app_info', 'Application info', version=VERSION) diff --git a/server/docker-entrypoint.sh b/server/docker-entrypoint.sh index ce230b4..60cb105 100755 --- a/server/docker-entrypoint.sh +++ b/server/docker-entrypoint.sh @@ -1,2 +1,2 @@ python init.py -gunicorn --worker-class gevent -b 0.0.0.0:5000 app:app --timeout 120 --workers 4 --threads 4 \ No newline at end of file +gunicorn -c gunicorn_config.py --worker-class gevent -b --timeout 120 --workers 4 --threads 4 0.0.0.0:5000 app:app \ No newline at end of file diff --git a/server/gunicorn_config.py b/server/gunicorn_config.py new file mode 100644 index 0000000..0af732c --- /dev/null +++ b/server/gunicorn_config.py @@ -0,0 +1,6 @@ +import os + +from prometheus_flask_exporter.multiprocess import GunicornInternalPrometheusMetrics + +def child_exit(server, worker): + GunicornInternalPrometheusMetrics.mark_process_dead_on_child_exit(worker.pid) \ No newline at end of file diff --git a/server/routes.py b/server/routes.py index 9c320e7..631fe71 100644 --- a/server/routes.py +++ b/server/routes.py @@ -15,12 +15,20 @@ from flask_jwt_extended import get_jwt_identity, jwt_required, \ create_access_token, unset_jwt_cookies +from prometheus_client import Counter, Gauge + from sqlalchemy.exc import NoResultFound from sqlalchemy.exc import IntegrityError from app import app, oidc from app import VERSION, server_instance_id +prompt_characters = Gauge( + 'prompt_characters', + 'Number of characters in the prompt', + ['route', 'user'] +) + class OrderedEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, OrderedDict): @@ -347,6 +355,9 @@ def construct_name_topic_request(request): message_usage["prompt_characters"] = promptCharacters increment_server_stat(category="usage", stat_name="promptCharacters", increment=promptCharacters) increment_server_stat(category="usage", stat_name="totalCharacters", increment=promptCharacters) + + prompt_characters.labels('name-topic', acting_user_id).set(promptCharacters) + proxy_url = app.config["OPENAI_PROXY"] proxies = {"http": proxy_url, "https": proxy_url} if proxy_url else None From 63f4b204c93b7c66b9a78c19a4a75e1436f88b93 Mon Sep 17 00:00:00 2001 From: Karim Tabet Date: Thu, 25 Apr 2024 12:25:27 +0100 Subject: [PATCH 02/22] Revert gunicorn prometheus work --- Makefile | 39 ++++++++++++++----------------------- server/Dockerfile | 2 +- server/Makefile | 5 ++--- server/app.py | 4 ++-- server/docker-entrypoint.sh | 2 +- server/gunicorn_config.py | 6 ------ server/routes.py | 11 ----------- 7 files changed, 21 insertions(+), 48 deletions(-) delete mode 100644 server/gunicorn_config.py diff --git a/Makefile b/Makefile index 1e48550..9c3d192 100644 --- a/Makefile +++ b/Makefile @@ -1,36 +1,27 @@ -# Makefile for the Sidekick project -# Make web_ui and server_api docker images - -.PHONY: init local run-dev-locally run-prod-locally all test web_ui server_api test-locally - -all: local build +.PHONY: init test build-docker run-dev-locally run-prod-locally run-container stop-container init: - $(MAKE) -C web_ui init - $(MAKE) -C server init + pipenv install test: init - $(MAKE) -C web_ui test - $(MAKE) -C server test + pipenv run python -m pytest + +build-docker: + docker build --tag sidekick-server . run-dev-locally: init - $(MAKE) -C server run-dev-locally & - $(MAKE) -C web_ui run-dev-locally & + pipenv run python run.py run-prod-locally: init - $(MAKE) -C server run-prod-locally & - $(MAKE) -C web_ui run-prod-locally & - -web_ui: - $(MAKE) -C web_ui + pipenv run gunicorn -w 4 -b 0.0.0.0:5003 app:app -server_api: - $(MAKE) -C server +stop-container: + docker stop sidekick-server; \ + docker ps test-locally: - $(MAKE) -C web_ui test-locally - $(MAKE) -C server test-locally + $(MAKE) init + $(MAKE) test -build-docker: - docker build --tag sidekick-server server/ - docker build --tag sidekick-web-ui web_ui/ +db-upgrade: + pipenv run flask db upgrade \ No newline at end of file diff --git a/server/Dockerfile b/server/Dockerfile index 8317cb4..7fa0d5d 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -6,7 +6,7 @@ RUN pip install --upgrade pip RUN pip install pipenv RUN pipenv requirements > requirements.txt RUN pip install --no-cache-dir --upgrade -r requirements.txt -COPY init.py app.py models.py routes.py utils.py docker-entrypoint.sh gunicorn_config.py ./ +COPY init.py app.py models.py routes.py utils.py docker-entrypoint.sh ./ COPY default_documents ./default_documents COPY default_settings ./default_settings COPY system_settings ./system_settings diff --git a/server/Makefile b/server/Makefile index e59ad8d..9c3d192 100644 --- a/server/Makefile +++ b/server/Makefile @@ -13,8 +13,7 @@ run-dev-locally: init pipenv run python run.py run-prod-locally: init - pipenv run python init.py - gunicorn -c gunicorn_config.py --worker-class gevent -b --timeout 120 --workers 4 --threads 4 0.0.0.0:5000 app:app + pipenv run gunicorn -w 4 -b 0.0.0.0:5003 app:app stop-container: docker stop sidekick-server; \ @@ -25,4 +24,4 @@ test-locally: $(MAKE) test db-upgrade: - pipenv run flask db upgrade + pipenv run flask db upgrade \ No newline at end of file diff --git a/server/app.py b/server/app.py index 64b3e3a..21ee742 100644 --- a/server/app.py +++ b/server/app.py @@ -8,7 +8,7 @@ from flask_oidc import OpenIDConnect from sqlalchemy.engine.url import make_url import uuid -from prometheus_flask_exporter.multiprocess import GunicornInternalPrometheusMetrics +from prometheus_flask_exporter import PrometheusMetrics VERSION = "0.3" server_instance_id = str(uuid.uuid4()) @@ -54,7 +54,7 @@ CORS(app) migrate = Migrate(app, db) -metrics = GunicornInternalPrometheusMetrics(app) +metrics = PrometheusMetrics(app) # static information as metric metrics.info('app_info', 'Application info', version=VERSION) diff --git a/server/docker-entrypoint.sh b/server/docker-entrypoint.sh index 60cb105..ce230b4 100755 --- a/server/docker-entrypoint.sh +++ b/server/docker-entrypoint.sh @@ -1,2 +1,2 @@ python init.py -gunicorn -c gunicorn_config.py --worker-class gevent -b --timeout 120 --workers 4 --threads 4 0.0.0.0:5000 app:app \ No newline at end of file +gunicorn --worker-class gevent -b 0.0.0.0:5000 app:app --timeout 120 --workers 4 --threads 4 \ No newline at end of file diff --git a/server/gunicorn_config.py b/server/gunicorn_config.py deleted file mode 100644 index 0af732c..0000000 --- a/server/gunicorn_config.py +++ /dev/null @@ -1,6 +0,0 @@ -import os - -from prometheus_flask_exporter.multiprocess import GunicornInternalPrometheusMetrics - -def child_exit(server, worker): - GunicornInternalPrometheusMetrics.mark_process_dead_on_child_exit(worker.pid) \ No newline at end of file diff --git a/server/routes.py b/server/routes.py index 631fe71..9c320e7 100644 --- a/server/routes.py +++ b/server/routes.py @@ -15,20 +15,12 @@ from flask_jwt_extended import get_jwt_identity, jwt_required, \ create_access_token, unset_jwt_cookies -from prometheus_client import Counter, Gauge - from sqlalchemy.exc import NoResultFound from sqlalchemy.exc import IntegrityError from app import app, oidc from app import VERSION, server_instance_id -prompt_characters = Gauge( - 'prompt_characters', - 'Number of characters in the prompt', - ['route', 'user'] -) - class OrderedEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, OrderedDict): @@ -355,9 +347,6 @@ def construct_name_topic_request(request): message_usage["prompt_characters"] = promptCharacters increment_server_stat(category="usage", stat_name="promptCharacters", increment=promptCharacters) increment_server_stat(category="usage", stat_name="totalCharacters", increment=promptCharacters) - - prompt_characters.labels('name-topic', acting_user_id).set(promptCharacters) - proxy_url = app.config["OPENAI_PROXY"] proxies = {"http": proxy_url, "https": proxy_url} if proxy_url else None From f30cee736ca4c8635f1906dd0e6490426e79774c Mon Sep 17 00:00:00 2001 From: Karim Tabet Date: Thu, 25 Apr 2024 12:26:15 +0100 Subject: [PATCH 03/22] Undo changes to Makefile --- Makefile | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/Makefile b/Makefile index 9c3d192..a48c611 100644 --- a/Makefile +++ b/Makefile @@ -1,27 +1,36 @@ -.PHONY: init test build-docker run-dev-locally run-prod-locally run-container stop-container +# Makefile for the Sidekick project +# Make web_ui and server_api docker images + +.PHONY: init local run-dev-locally run-prod-locally all test web_ui server_api test-locally + +all: local build init: - pipenv install + $(MAKE) -C web_ui init + $(MAKE) -C server init test: init - pipenv run python -m pytest - -build-docker: - docker build --tag sidekick-server . + $(MAKE) -C web_ui test + $(MAKE) -C server test run-dev-locally: init - pipenv run python run.py + $(MAKE) -C server run-dev-locally & + $(MAKE) -C web_ui run-dev-locally & run-prod-locally: init - pipenv run gunicorn -w 4 -b 0.0.0.0:5003 app:app + $(MAKE) -C server run-prod-locally & + $(MAKE) -C web_ui run-prod-locally & + +web_ui: + $(MAKE) -C web_ui -stop-container: - docker stop sidekick-server; \ - docker ps +server_api: + $(MAKE) -C server test-locally: - $(MAKE) init - $(MAKE) test + $(MAKE) -C web_ui test-locally + $(MAKE) -C server test-locally -db-upgrade: - pipenv run flask db upgrade \ No newline at end of file +build-docker: + docker build --tag sidekick-server server/ + docker build --tag sidekick-web-ui web_ui/ \ No newline at end of file From 50fd9ffb8f83ffb45a4a2c95cd150b5146acefb7 Mon Sep 17 00:00:00 2001 From: Karim Tabet Date: Thu, 25 Apr 2024 12:26:42 +0100 Subject: [PATCH 04/22] Newline --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a48c611..1e48550 100644 --- a/Makefile +++ b/Makefile @@ -33,4 +33,4 @@ test-locally: build-docker: docker build --tag sidekick-server server/ - docker build --tag sidekick-web-ui web_ui/ \ No newline at end of file + docker build --tag sidekick-web-ui web_ui/ From ccb298839ef781cdcea33e371c5c25d2958b1d03 Mon Sep 17 00:00:00 2001 From: Karim Tabet Date: Thu, 25 Apr 2024 12:26:55 +0100 Subject: [PATCH 05/22] Newline --- server/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/Makefile b/server/Makefile index 9c3d192..35fde50 100644 --- a/server/Makefile +++ b/server/Makefile @@ -24,4 +24,4 @@ test-locally: $(MAKE) test db-upgrade: - pipenv run flask db upgrade \ No newline at end of file + pipenv run flask db upgrade From 4d3fcea82c044040746177e80efea980b2f2c549 Mon Sep 17 00:00:00 2001 From: embernet Date: Tue, 7 May 2024 11:54:46 +0100 Subject: [PATCH 06/22] mermaid diagrams centre themselves --- web_ui/src/MermaidDiagram.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/web_ui/src/MermaidDiagram.js b/web_ui/src/MermaidDiagram.js index 2db72ed..c30a222 100644 --- a/web_ui/src/MermaidDiagram.js +++ b/web_ui/src/MermaidDiagram.js @@ -35,7 +35,11 @@ const MermaidDiagram = memo(({ markdown }) => { }, [markdown]); return ( -
+
); }); From f2c39d18c43939ae8ff607b836e313ea3033b14f Mon Sep 17 00:00:00 2001 From: embernet Date: Tue, 7 May 2024 23:22:56 +0100 Subject: [PATCH 07/22] MermaidDiagram now detects markdown errors and replaces mermaid error bomb with better UI --- web_ui/src/MermaidDiagram.js | 55 ++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/web_ui/src/MermaidDiagram.js b/web_ui/src/MermaidDiagram.js index c30a222..cb52b16 100644 --- a/web_ui/src/MermaidDiagram.js +++ b/web_ui/src/MermaidDiagram.js @@ -1,4 +1,5 @@ import React, { useEffect, useRef, useState } from "react"; +import { Box, Collapse, Button, Typography } from "@mui/material"; import mermaid from "mermaid"; import { v4 as uuidv4 } from 'uuid'; import { memo } from 'react'; @@ -7,12 +8,15 @@ const MermaidDiagram = memo(({ markdown }) => { const mermaidRef = useRef(null); const mermaidId = `mermaid-diagram-${uuidv4()}`; const [svg, setSvg] = useState(null); + const [error, setError] = useState(null); + const [showError, setShowError] = useState(false); useEffect(() => { mermaid.initialize({ startOnLoad: true, theme: 'default', securityLevel: 'loose', + suppressErrorRendering: true, }); }, []); @@ -25,22 +29,49 @@ const MermaidDiagram = memo(({ markdown }) => { useEffect(() => { if (mermaidRef.current && markdown !== "") { mermaid.render(mermaidId, markdown) - .then(({svg}) => { - setSvg(svg); - }) - .catch((error) => { - console.error(error); - }); + .then(({svg}) => { + setSvg(svg); + }) + .catch((error) => { + console.error("Error rendering mermaid diagram", mermaidId, error); + setError({ message: 'Error rendering mermaid diagram.', error: error}); + const mermaidInjectedErrorElement = document.getElementById(mermaidId); + if (mermaidInjectedErrorElement) { + // Hide the mermaid 'Syntax error in text' bomb message + mermaidInjectedErrorElement.style.display = 'none'; + } + }); } }, [markdown]); return ( -
-
+ + { + !error + ? +
+
+ : + + + + + {error.error.toString()} + + + + Here's the markdown the generated the error: + {markdown} + + + } +
); }); From f2116ebe545cc0c4f00507fdd7cedf9e6a29a5b3 Mon Sep 17 00:00:00 2001 From: embernet Date: Wed, 8 May 2024 09:02:38 +0100 Subject: [PATCH 08/22] Markdown copy button now includes language designator --- web_ui/src/SidekickMarkdown.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web_ui/src/SidekickMarkdown.js b/web_ui/src/SidekickMarkdown.js index d6a46cb..fab2a66 100644 --- a/web_ui/src/SidekickMarkdown.js +++ b/web_ui/src/SidekickMarkdown.js @@ -24,6 +24,7 @@ const SidekickMarkdown = memo(({ markdown }) => { let language = match[1]; if (language === "" || !language) { language = "code"; } // provide a default if ``` used wuthout specifying a language const code = match[2]; + const codeMarkdown = `\`\`\`${language}\n${code.trim()}\n\`\`\`\n`; const startIndex = match.index; const endIndex = codeRegex.lastIndex; const before = markdown.slice(lastIndex, startIndex); @@ -35,7 +36,7 @@ const SidekickMarkdown = memo(({ markdown }) => { {language} { navigator.clipboard.writeText(code); event.stopPropagation(); }}> + onClick={(event) => { navigator.clipboard.writeText(codeMarkdown); event.stopPropagation(); }}> From 521d5dba538ea7a473378c05ad62fff3f3d098f5 Mon Sep 17 00:00:00 2001 From: embernet Date: Wed, 8 May 2024 09:03:06 +0100 Subject: [PATCH 09/22] fixed race condition in MermaidDiagram render --- web_ui/src/MermaidDiagram.js | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/web_ui/src/MermaidDiagram.js b/web_ui/src/MermaidDiagram.js index cb52b16..68e31c3 100644 --- a/web_ui/src/MermaidDiagram.js +++ b/web_ui/src/MermaidDiagram.js @@ -27,21 +27,33 @@ const MermaidDiagram = memo(({ markdown }) => { }, [svg]); useEffect(() => { + let isCancelled = false; + if (mermaidRef.current && markdown !== "") { mermaid.render(mermaidId, markdown) .then(({svg}) => { - setSvg(svg); + if (!isCancelled) { + setSvg(svg); + } }) .catch((error) => { - console.error("Error rendering mermaid diagram", mermaidId, error); - setError({ message: 'Error rendering mermaid diagram.', error: error}); - const mermaidInjectedErrorElement = document.getElementById(mermaidId); - if (mermaidInjectedErrorElement) { - // Hide the mermaid 'Syntax error in text' bomb message - mermaidInjectedErrorElement.style.display = 'none'; + if (!isCancelled) { + setError({ message: 'Error rendering mermaid diagram.', error: error}); + const mermaidInjectedErrorElement = document.getElementById(mermaidId); + if (mermaidInjectedErrorElement) { + // Hide the mermaid 'Syntax error in text' bomb message + mermaidInjectedErrorElement.style.display = 'none'; + } } }); } + + // This cleanup function is called when the component unmounts or when the markdown prop changes. + // It sets isCancelled to true, to prevent the .then and .catch callbacks from updating the state + // to avoid race conditions due to the asynchronous nature of the mermaid.render() function. + return () => { + isCancelled = true; + }; }, [markdown]); return ( @@ -58,7 +70,7 @@ const MermaidDiagram = memo(({ markdown }) => { : @@ -66,7 +78,7 @@ const MermaidDiagram = memo(({ markdown }) => { - Here's the markdown the generated the error: + Here's the markdown that generated the error: {markdown} From 8fbdb701b267c6c3c2793f0f7eecc0857bc6b768 Mon Sep 17 00:00:00 2001 From: embernet Date: Wed, 8 May 2024 21:37:39 +0100 Subject: [PATCH 10/22] added Diagrams sub-menu to Chat Prompt menu --- node_modules/.package-lock.json | 6 +++ package-lock.json | 6 +++ package.json | 1 + web_ui/src/Chat.js | 94 ++++++++++++++++++++++++++++----- 4 files changed, 93 insertions(+), 14 deletions(-) create mode 100644 node_modules/.package-lock.json create mode 100644 package-lock.json create mode 100644 package.json diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json new file mode 100644 index 0000000..85fc23e --- /dev/null +++ b/node_modules/.package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "sidekick", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..85fc23e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "sidekick", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/package.json @@ -0,0 +1 @@ +{} diff --git a/web_ui/src/Chat.js b/web_ui/src/Chat.js index e7cc7e4..064409e 100644 --- a/web_ui/src/Chat.js +++ b/web_ui/src/Chat.js @@ -43,6 +43,7 @@ import AddOutlinedIcon from '@mui/icons-material/AddOutlined'; import HighlightOffIcon from '@mui/icons-material/HighlightOff'; import SpeakerNotesOffIcon from '@mui/icons-material/SpeakerNotesOff'; import LibraryBooksIcon from '@mui/icons-material/LibraryBooks'; +import SchemaIcon from '@mui/icons-material/Schema'; import { SystemContext } from './SystemContext'; import ContentFormatter from './ContentFormatter'; @@ -306,6 +307,7 @@ const Chat = ({ const [systemPrompt, setSystemPrompt] = useState(""); const [promptPlaceholder, setPromptPlaceholder] = useState(userPromptReady.current); const [menuPromptsAnchorEl, setMenuPromptsAnchorEl] = useState(null); + const [menuDiagramsAnchorEl, setMenuDiagramsAnchorEl] = useState(null); const [menuPanelAnchorEl, setMenuPanelAnchorEl] = useState(null); const [menuPromptEditorAnchorEl, setMenuPromptEditorAnchorEl] = useState(null); const [menuMessageContext, setMenuMessageContext] = useState(null); @@ -1186,6 +1188,7 @@ const Chat = ({ handleMenuMessageContextClose(); handleMenuPromptEditorClose(); handleMenuPromptsClose(); + handleMenuDiagramsClose(); } const runMenuAction = (functionToRun, thenFocusOnPrompt=true) => { @@ -1204,6 +1207,14 @@ const Chat = ({ setMenuPromptsAnchorEl(null); }; + const handleMenuDiagramsOpen = (event) => { + setMenuDiagramsAnchorEl(event.currentTarget); + }; + + const handleMenuDiagramsClose = () => { + setMenuDiagramsAnchorEl(null); + }; + const handleMenuCommandsOpen = (event) => { setMenuCommandsAnchorEl(event.currentTarget); }; @@ -1471,11 +1482,8 @@ const Chat = ({ onKeyDown={ (event) => { if (event.key === 'ArrowRight') { - setChatPrompt(prompt); onClick && onClick(); - if (chatPromptRef.current) { - chatPromptRef.current.focus(); - } + runMenuAction(()=>{setChatPrompt(prompt)}); } } } @@ -1505,6 +1513,7 @@ const Chat = ({ } const promptSelectionInstructions = "Click on a prompt to run it, ALT+Click (or Right-Arrow when using via slash command) to place in prompt editor so you can edit it"; + const diagramSelectionInstructions = "Click on a diagram (or press enter) to generate it based on context, ALT+Click (or Right-Arrow when using via slash command) to place in prompt editor so you can edit it and describe what you want"; const toolbar = + { + if (event.key === 'ArrowRight') { + handleMenuDiagramsOpen(event); + } + } + } + > + + Diagrams + + + + + {runMenuAction(togglePromptEngineerOpen, false);}}> + + Prompt Engineer + {runMenuAction(handleReload);}}> Reload last prompt for editing @@ -1675,25 +1704,22 @@ const Chat = ({ Delete last prompt/response - {runMenuAction(togglePromptEngineerOpen);}}> - - Prompt Engineer - {runMenuAction(handleNewChat);}}> New Chat - {runMenuAction(); handleToggleMarkdownRendering();}}> - { markdownRenderingOn ? : } - { markdownRenderingOn ? "Turn off markdown rendering" : "Turn on markdown rendering" } + {runMenuAction(handleToggleMarkdownRendering);}}> + { markdownRenderingOn ? : } + { markdownRenderingOn ? "Turn off markdown rendering" : "Turn on markdown rendering" } + { isMobile ? null : - {runMenuAction(); handleToggleWindowMaximise();}}> + {runMenuAction(handleToggleWindowMaximise);}}> { windowMaximized ? : } { windowMaximized ? "Shrink window" : "Expand window" } } - {runMenuAction(); handleClose();}}> + {runMenuAction(handleClose, false);}}> Close Window @@ -1775,7 +1801,7 @@ const Chat = ({ vertical: 'top', horizontal: 'left', }} - > + > @@ -1867,6 +1893,46 @@ const Chat = ({ + { + if (event.key === 'ArrowLeft') { + handleMenuDiagramsClose(event); + } + } + } + anchorOrigin={{ + vertical: 'top', + horizontal: 'right', + }} + transformOrigin={{ + vertical: 'top', + horizontal: 'left', + }} + > + + + + Diagrams + + + + + + + + + + + + + + + Date: Thu, 9 May 2024 08:18:38 +0100 Subject: [PATCH 11/22] added use case diagrams added chat context menu items to delete from or upto a message prompts from the menu now automatically go into the prompt editor rather than running when there are no messages --- web_ui/src/Chat.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/web_ui/src/Chat.js b/web_ui/src/Chat.js index 064409e..b66d471 100644 --- a/web_ui/src/Chat.js +++ b/web_ui/src/Chat.js @@ -1373,6 +1373,20 @@ const Chat = ({ setMenuMessageContext(null); }; + const handleDeleteAllMessagesUpToHere = () => { + const updatedMessages = messages.slice(menuMessageContext.index + 1); + setMessages(updatedMessages); + setMenuMessageContext(null); + }; + + const handleDeleteAllMessagesFromHere = () => { + if (menuMessageContext.index > 0) { + const updatedMessages = messages.slice(0, menuMessageContext.index - 1); + setMessages(updatedMessages); + setMenuMessageContext(null); + } + }; + const handleUseAsChatInput = () => { setChatPrompt(menuMessageContext.message.content); setPromptFocus(); @@ -1472,7 +1486,7 @@ const Chat = ({ (event) => { onClick && onClick(); - if (event.altKey) { + if (event.altKey || messages.length === 0) { runMenuAction(()=>{setChatPrompt(prompt)}); } else { runMenuAction(()=>{sendPrompt(prompt)}); @@ -1780,6 +1794,8 @@ const Chat = ({ Delete this message Delete this and previous message Delete all messages + Delete this and all previous messages + Delete this and all subseqeunt messages + From be3ce0bc4b3adff695078f0d14cf5edd656e0cb4 Mon Sep 17 00:00:00 2001 From: embernet Date: Thu, 9 May 2024 08:26:22 +0100 Subject: [PATCH 12/22] fixed bug in chat context menu to delete this and subsequent messages --- web_ui/src/Chat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_ui/src/Chat.js b/web_ui/src/Chat.js index b66d471..1c0c00c 100644 --- a/web_ui/src/Chat.js +++ b/web_ui/src/Chat.js @@ -1381,7 +1381,7 @@ const Chat = ({ const handleDeleteAllMessagesFromHere = () => { if (menuMessageContext.index > 0) { - const updatedMessages = messages.slice(0, menuMessageContext.index - 1); + const updatedMessages = messages.slice(0, menuMessageContext.index); setMessages(updatedMessages); setMenuMessageContext(null); } From 55f98c205c1baf7e610b030b9823fdfe118a5793 Mon Sep 17 00:00:00 2001 From: embernet Date: Thu, 9 May 2024 17:34:04 +0100 Subject: [PATCH 13/22] Moved delete all messages menu item to bottom --- web_ui/src/Chat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_ui/src/Chat.js b/web_ui/src/Chat.js index 1c0c00c..54e4910 100644 --- a/web_ui/src/Chat.js +++ b/web_ui/src/Chat.js @@ -1793,9 +1793,9 @@ const Chat = ({ Delete this message Delete this and previous message - Delete all messages Delete this and all previous messages Delete this and all subseqeunt messages + Delete all messages Date: Mon, 13 May 2024 13:50:10 +0100 Subject: [PATCH 14/22] Run server in container as sidekick user --- server/Dockerfile | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/server/Dockerfile b/server/Dockerfile index 7fa0d5d..c4af3a0 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,15 +1,27 @@ FROM python:3.10-alpine -RUN apk add gcc musl-dev libffi-dev + +RUN apk add --no-cache gcc musl-dev libffi-dev + +RUN adduser -D -u 1000 sidekick + WORKDIR /sidekick_server + COPY Pipfile.lock ./ -RUN pip install --upgrade pip -RUN pip install pipenv -RUN pipenv requirements > requirements.txt -RUN pip install --no-cache-dir --upgrade -r requirements.txt +RUN pip install --upgrade pip && \ + pip install pipenv && \ + pipenv requirements > requirements.txt && \ + pip install --no-cache-dir --upgrade -r requirements.txt + COPY init.py app.py models.py routes.py utils.py docker-entrypoint.sh ./ COPY default_documents ./default_documents COPY default_settings ./default_settings COPY system_settings ./system_settings COPY migrations ./migrations + RUN chmod +x docker-entrypoint.sh -ENTRYPOINT ["/bin/sh", "docker-entrypoint.sh"] + +RUN chown -R sidekick:sidekick /sidekick_server + +USER sidekick + +ENTRYPOINT ["/bin/sh", "docker-entrypoint.sh"] \ No newline at end of file From 6afa1a303b61cba3ccb742c80bcbaa4d6948f154 Mon Sep 17 00:00:00 2001 From: Karim Tabet Date: Mon, 13 May 2024 13:50:22 +0100 Subject: [PATCH 15/22] Optionally set open ai base URL as env var --- server/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/app.py b/server/app.py index f388883..1e4fb3d 100644 --- a/server/app.py +++ b/server/app.py @@ -20,6 +20,7 @@ app.config["JWT_ACCESS_TOKEN_EXPIRES"] = False app.config["SQLALCHEMY_DATABASE_URI"] = os.environ["SQLALCHEMY_DATABASE_URI"] app.config["OPENAI_API_KEY"] = os.environ["OPENAI_API_KEY"] +app.config["OPENAI_BASE_URL"] = os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1") app.config["OPENAI_PROXY"] = os.environ.get("OPENAI_PROXY") # Optionallay count chat tokens if specified in the env var From 5be0818da1a41f9306ab50c03c4a1a6ff91f041a Mon Sep 17 00:00:00 2001 From: Karim Tabet Date: Mon, 13 May 2024 13:50:34 +0100 Subject: [PATCH 16/22] Use open ai base url in routes --- server/routes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/routes.py b/server/routes.py index 9c320e7..371795a 100644 --- a/server/routes.py +++ b/server/routes.py @@ -101,7 +101,7 @@ def test_ai(): with RequestLogger(request) as rl: increment_server_stat(category="requests", stat_name="healthAi") try: - url = 'https://api.openai.com/v1/chat/completions' + url = f"{app.config['OPENAI_BASE_URL']}/chat/completions" headers = { 'content-type': 'application/json; charset=utf-8', 'Authorization': f"Bearer {app.config['OPENAI_API_KEY']}" @@ -337,7 +337,7 @@ def construct_name_topic_request(request): return ai_request try: - url = 'https://api.openai.com/v1/chat/completions' + url = f"{app.config['OPENAI_BASE_URL']}/chat/completions" headers = { 'content-type': 'application/json; charset=utf-8', 'Authorization': f"Bearer {app.config['OPENAI_API_KEY']}" @@ -416,7 +416,7 @@ def construct_query_ai_request(request): promptCharacters = num_characters_from_messages(ai_request["messages"]) increment_server_stat(category="usage", stat_name="promptCharacters", increment=promptCharacters) increment_server_stat(category="usage", stat_name="totalCharacters", increment=promptCharacters) - url = 'https://api.openai.com/v1/chat/completions' + url = f"{app.config['OPENAI_BASE_URL']}/chat/completions" headers = { 'content-type': 'application/json; charset=utf-8', 'Authorization': f"Bearer {app.config['OPENAI_API_KEY']}" @@ -473,7 +473,7 @@ def chat_v2(): increment_server_stat(category="requests", stat_name="chatV2") def generate(): - url = 'https://api.openai.com/v1/chat/completions' + url = f"{app.config['OPENAI_BASE_URL']}/chat/completions" headers = { 'content-type': 'application/json; charset=utf-8', 'Authorization': f"Bearer {app.config['OPENAI_API_KEY']}" From 13fe63ece0c0b68c991ce0aca86a641e0559515a Mon Sep 17 00:00:00 2001 From: embernet Date: Tue, 14 May 2024 07:27:37 +0100 Subject: [PATCH 17/22] Note add to AI library tooltip expanded --- web_ui/src/Note.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web_ui/src/Note.js b/web_ui/src/Note.js index 49cac7e..c6f5dc6 100644 --- a/web_ui/src/Note.js +++ b/web_ui/src/Note.js @@ -214,7 +214,7 @@ Don't repeat the CONTEXT_TEXT or the REQUEST in your response. Create a response useEffect(()=>{ if(pageLoaded && appendNoteContent.content !== "" && noteContentRef?.current) { - setNoteOpen({id: id, timestamp: Date.now()}); + setNoteOpen({id: id, timestamp: Date.now()}); // to scroll the note into view if its off the screen let newNotePart = appendNoteContent.content.trim(); if(typeof newNotePart === "string") { let newNote = noteContentRef.current.innerText; @@ -721,7 +721,7 @@ Don't repeat the CONTEXT_TEXT or the REQUEST in your response. Create a response { markdownRenderingOn ? : } - + { inAILibrary ? : } From b5fa9b39cf0df9bdca46e7028b7b051f2f1b47a0 Mon Sep 17 00:00:00 2001 From: embernet Date: Tue, 14 May 2024 07:56:53 +0100 Subject: [PATCH 18/22] Gave AI Note Library a toolbar with a close button --- web_ui/src/Chat.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/web_ui/src/Chat.js b/web_ui/src/Chat.js index 54e4910..d18717a 100644 --- a/web_ui/src/Chat.js +++ b/web_ui/src/Chat.js @@ -2462,8 +2462,10 @@ const Chat = ({ - - + { Object.keys(selectedAiLibraryNotes).length === 0 ? : } @@ -2552,11 +2554,17 @@ const Chat = ({ { aiLibraryOpen ? - - Loaded knowledge: { Object.keys(selectedAiLibraryNotes).length === 0 ? "None" : ""} + + AI Library + Loaded notes: {Object.keys(selectedAiLibraryNotes).length} - + + {setAiLibraryOpen(false)}}> + + + + {Object.values(selectedAiLibraryNotes).map(note =>( Date: Tue, 14 May 2024 16:27:32 +0100 Subject: [PATCH 19/22] added support for OpenAI gpt-4o model --- server/default_settings/model_settings.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/default_settings/model_settings.json b/server/default_settings/model_settings.json index 2a90f11..d9a0e88 100644 --- a/server/default_settings/model_settings.json +++ b/server/default_settings/model_settings.json @@ -4,6 +4,15 @@ "providers": { "OpenAI": { "models": { + "gpt-4o": { + "temperature": 0.7, + "topP": 1, + "frequencyPenalty": 0, + "presencePenalty": 0, + "contextTokenSize": 128000, + "systemMessage": "You are a helpful advisor.", + "notes": "Points to the latest version of the gpt-4o (Omni) model. Cheaper and faster than gpt-4-turbo." + }, "gpt-4-turbo-preview": { "temperature": 0.7, "topP": 1, From feecedec81c9e2a3c92776f2dca5dfd4c326178c Mon Sep 17 00:00:00 2001 From: Karim Tabet Date: Tue, 14 May 2024 16:56:45 +0100 Subject: [PATCH 20/22] Get openai api key from custom util --- server/Dockerfile | 1 + server/custom_utils/get_openai_token.py | 4 ++++ server/routes.py | 10 +++++----- 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 server/custom_utils/get_openai_token.py diff --git a/server/Dockerfile b/server/Dockerfile index c4af3a0..28873d0 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -13,6 +13,7 @@ RUN pip install --upgrade pip && \ pip install --no-cache-dir --upgrade -r requirements.txt COPY init.py app.py models.py routes.py utils.py docker-entrypoint.sh ./ +COPY custom_utils ./custom_utils COPY default_documents ./default_documents COPY default_settings ./default_settings COPY system_settings ./system_settings diff --git a/server/custom_utils/get_openai_token.py b/server/custom_utils/get_openai_token.py new file mode 100644 index 0000000..6289ed0 --- /dev/null +++ b/server/custom_utils/get_openai_token.py @@ -0,0 +1,4 @@ +from app import app + +def get_openai_token(): + return app.config['OPENAI_API_KEY'] diff --git a/server/routes.py b/server/routes.py index 371795a..a99eb1e 100644 --- a/server/routes.py +++ b/server/routes.py @@ -2,7 +2,6 @@ import json import requests import socket -import uuid import sseclient from collections import OrderedDict @@ -10,6 +9,7 @@ from utils import DBUtils, construct_ai_request, RequestLogger,\ server_stats, increment_server_stat, openai_num_tokens_from_messages, \ get_random_string, num_characters_from_messages, update_default_settings +from custom_utils.get_openai_token import get_openai_token from flask import request, jsonify, Response, stream_with_context, redirect, session, url_for from flask_jwt_extended import get_jwt_identity, jwt_required, \ @@ -104,7 +104,7 @@ def test_ai(): url = f"{app.config['OPENAI_BASE_URL']}/chat/completions" headers = { 'content-type': 'application/json; charset=utf-8', - 'Authorization': f"Bearer {app.config['OPENAI_API_KEY']}" + 'Authorization': f"Bearer {get_openai_token()}" } ai_request = { "model": "gpt-3.5-turbo", @@ -340,7 +340,7 @@ def construct_name_topic_request(request): url = f"{app.config['OPENAI_BASE_URL']}/chat/completions" headers = { 'content-type': 'application/json; charset=utf-8', - 'Authorization': f"Bearer {app.config['OPENAI_API_KEY']}" + 'Authorization': f"Bearer {get_openai_token()}" } ai_request = construct_name_topic_request(request) promptCharacters = num_characters_from_messages(ai_request["messages"]) @@ -419,7 +419,7 @@ def construct_query_ai_request(request): url = f"{app.config['OPENAI_BASE_URL']}/chat/completions" headers = { 'content-type': 'application/json; charset=utf-8', - 'Authorization': f"Bearer {app.config['OPENAI_API_KEY']}" + 'Authorization': f"Bearer {get_openai_token()}" } message_usage["prompt_characters"] = num_characters_from_messages( ai_request["messages"]) @@ -476,7 +476,7 @@ def generate(): url = f"{app.config['OPENAI_BASE_URL']}/chat/completions" headers = { 'content-type': 'application/json; charset=utf-8', - 'Authorization': f"Bearer {app.config['OPENAI_API_KEY']}" + 'Authorization': f"Bearer {get_openai_token()}" } ai_request = construct_ai_request(request) ai_request["stream"] = True From 1b3419019397d648623bb38047b393a26e1a846f Mon Sep 17 00:00:00 2001 From: embernet Date: Wed, 15 May 2024 11:00:54 +0100 Subject: [PATCH 21/22] system warnings now pop up a modal dialogue --- web_ui/src/SystemContext.js | 1 + 1 file changed, 1 insertion(+) diff --git a/web_ui/src/SystemContext.js b/web_ui/src/SystemContext.js index 679af84..f59a0c9 100644 --- a/web_ui/src/SystemContext.js +++ b/web_ui/src/SystemContext.js @@ -66,6 +66,7 @@ export const SystemProvider = ({ serverUrl, setStatusUpdates, setModalDialogInfo console.error(message, context ? context: context, error ? error : ""); }, warning: (message, context="") => { + setModalDialogInfo({ title: "Warning", message: message }); setStatusUpdates(prevStatusUpdates => [...prevStatusUpdates, { message: message, type: 'warning', timestamp: _dateTimeString() }]); console.log(message, context); }, From b429cbada5fa1f1b658462676b7e88c13ad66a9a Mon Sep 17 00:00:00 2001 From: embernet Date: Wed, 15 May 2024 12:09:45 +0100 Subject: [PATCH 22/22] fixed bug so note not corrupted in renderMarkdown mode --- web_ui/src/Note.js | 14 ++++++++++---- web_ui/src/SidekickMarkdown.js | 20 +++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/web_ui/src/Note.js b/web_ui/src/Note.js index c6f5dc6..775a066 100644 --- a/web_ui/src/Note.js +++ b/web_ui/src/Note.js @@ -43,12 +43,14 @@ const Note = ({noteOpen, setNoteOpen, appendNoteContent, loadNote, createNote, d setNewPromptPart, setNewPrompt, setChatRequest, onChange, setOpenNoteId, modelSettings, persona, serverUrl, token, setToken, maxWidth, isMobile}) => { + const panelWindowRef = useRef(null); + const [notePanelKey, setNotePanelKey] = useState(Date.now()); // used to force re-renders + const StyledToolbar = styled(Toolbar)(({ theme }) => ({ backgroundColor: darkMode ? green[900] : green[300], marginRight: theme.spacing(2), })); - const panelWindowRef = useRef(null); const newNoteName = "New Note"; const systemPrompt = `You are DocumentGPT. You take CONTEXT_TEXT from a document along with a REQUEST to generate more text to include in the document. @@ -143,6 +145,7 @@ You always do your best to generate text in the same style as the context text p const noteInstantiated = useRef(false); const [renameInProcess, setRenameInProcess] = useState(false); const [timeToSave, setTimeToSave] = useState(false); + const [noteContentBuffer, setNoteContentBuffer] = useState(""); useEffect(() => { @@ -155,6 +158,7 @@ You always do your best to generate text in the same style as the context text p const setContent = (text) => { if (noteContentRef.current) { noteContentRef.current.innerText = text; + setNoteContentBuffer(text); noteInstantiated.current = true; if (saveStatus.current === "saved") { saveStatus.current = "changed"; @@ -213,7 +217,9 @@ Don't repeat the CONTEXT_TEXT or the REQUEST in your response. Create a response }; useEffect(()=>{ - if(pageLoaded && appendNoteContent.content !== "" && noteContentRef?.current) { + if (markdownRenderingOn) { + system.warning("Note is now in markdown rendering mode. To enable edit, turn this off by clicking the markdown icon in the toolbar."); + } else if (pageLoaded && appendNoteContent.content !== "" && noteContentRef?.current) { setNoteOpen({id: id, timestamp: Date.now()}); // to scroll the note into view if its off the screen let newNotePart = appendNoteContent.content.trim(); if(typeof newNotePart === "string") { @@ -814,7 +820,7 @@ Don't repeat the CONTEXT_TEXT or the REQUEST in your response. Create a response ); - const render = { markdownRenderingOn && - + }
{ const system = useContext(SystemContext); + const [myMarkdown, setMyMarkdown] = useState(null); + const [myRenderedMarkdown, setMyRenderedMarkdown] = useState(null); + + useEffect(() => { + setMyMarkdown(markdown); + }, [markdown]); + + useEffect(() => { + if (myMarkdown) { + setMyRenderedMarkdown(renderMarkdown(myMarkdown)); + } + }, [myMarkdown]); const renderMarkdown = (markdown) => { try { @@ -64,10 +77,7 @@ const SidekickMarkdown = memo(({ markdown }) => { return {markdown}; } }; - if (!markdown) { - return null; - } - const result = renderMarkdown(markdown); + const result = myRenderedMarkdown ? myRenderedMarkdown : null; return (result); });