diff --git a/application/api/answer/routes/base.py b/application/api/answer/routes/base.py index 8eb5aafc..5162ab77 100644 --- a/application/api/answer/routes/base.py +++ b/application/api/answer/routes/base.py @@ -193,6 +193,43 @@ class BaseAnswerResource: data = json.dumps({"type": "end"}) yield f"data: {data}\n\n" + except GeneratorExit: + # Client aborted the connection + logger.info( + f"Stream aborted by client for question: {question[:50]}... " + ) + # Save partial response to database before exiting + if should_save_conversation and response_full: + try: + if isNoneDoc: + for doc in source_log_docs: + doc["source"] = "None" + llm = LLMCreator.create_llm( + settings.LLM_PROVIDER, + api_key=settings.API_KEY, + user_api_key=user_api_key, + decoded_token=decoded_token, + ) + self.conversation_service.save_conversation( + conversation_id, + question, + response_full, + thought, + source_log_docs, + tool_calls, + llm, + self.gpt_model, + decoded_token, + index=index, + api_key=user_api_key, + agent_id=agent_id, + is_shared_usage=is_shared_usage, + shared_token=shared_token, + attachment_ids=attachment_ids, + ) + except Exception as e: + logger.error(f"Error saving partial response: {str(e)}", exc_info=True) + raise except Exception as e: logger.error(f"Error in stream: {str(e)}", exc_info=True) data = json.dumps( diff --git a/application/llm/anthropic.py b/application/llm/anthropic.py index 1fa3b5b2..b55dd855 100644 --- a/application/llm/anthropic.py +++ b/application/llm/anthropic.py @@ -46,5 +46,9 @@ class AnthropicLLM(BaseLLM): stream=True, ) - for completion in stream_response: - yield completion.completion + try: + for completion in stream_response: + yield completion.completion + finally: + if hasattr(stream_response, 'close'): + stream_response.close() diff --git a/application/llm/docsgpt_provider.py b/application/llm/docsgpt_provider.py index 001035c4..dbbcbfd2 100644 --- a/application/llm/docsgpt_provider.py +++ b/application/llm/docsgpt_provider.py @@ -121,11 +121,19 @@ class DocsGPTAPILLM(BaseLLM): model="docsgpt", messages=messages, stream=stream, **kwargs ) - for line in response: - if len(line.choices) > 0 and line.choices[0].delta.content is not None and len(line.choices[0].delta.content) > 0: - yield line.choices[0].delta.content - elif len(line.choices) > 0: - yield line.choices[0] + try: + for line in response: + if ( + len(line.choices) > 0 + and line.choices[0].delta.content is not None + and len(line.choices[0].delta.content) > 0 + ): + yield line.choices[0].delta.content + elif len(line.choices) > 0: + yield line.choices[0] + finally: + if hasattr(response, 'close'): + response.close() def _supports_tools(self): return True \ No newline at end of file diff --git a/application/llm/google_ai.py b/application/llm/google_ai.py index b88e1d9f..9263c873 100644 --- a/application/llm/google_ai.py +++ b/application/llm/google_ai.py @@ -373,17 +373,21 @@ class GoogleLLM(BaseLLM): config=config, ) - for chunk in response: - if hasattr(chunk, "candidates") and chunk.candidates: - for candidate in chunk.candidates: - if candidate.content and candidate.content.parts: - for part in candidate.content.parts: - if part.function_call: - yield part - elif part.text: - yield part.text - elif hasattr(chunk, "text"): - yield chunk.text + try: + for chunk in response: + if hasattr(chunk, "candidates") and chunk.candidates: + for candidate in chunk.candidates: + if candidate.content and candidate.content.parts: + for part in candidate.content.parts: + if part.function_call: + yield part + elif part.text: + yield part.text + elif hasattr(chunk, "text"): + yield chunk.text + finally: + if hasattr(response, 'close'): + response.close() def _supports_tools(self): """Return whether this LLM supports function calling.""" diff --git a/application/llm/openai.py b/application/llm/openai.py index 618aa238..cd69cea9 100644 --- a/application/llm/openai.py +++ b/application/llm/openai.py @@ -170,15 +170,19 @@ class OpenAILLM(BaseLLM): response = self.client.chat.completions.create(**request_params) - for line in response: - if ( - len(line.choices) > 0 - and line.choices[0].delta.content is not None - and len(line.choices[0].delta.content) > 0 - ): - yield line.choices[0].delta.content - elif len(line.choices) > 0: - yield line.choices[0] + try: + for line in response: + if ( + len(line.choices) > 0 + and line.choices[0].delta.content is not None + and len(line.choices[0].delta.content) > 0 + ): + yield line.choices[0].delta.content + elif len(line.choices) > 0: + yield line.choices[0] + finally: + if hasattr(response, 'close'): + response.close() def _supports_tools(self): return True diff --git a/frontend/.env.development b/frontend/.env.development index 4083d677..7b6f3434 100644 --- a/frontend/.env.development +++ b/frontend/.env.development @@ -1,4 +1,6 @@ # Please put appropriate value VITE_BASE_URL=http://localhost:5173 VITE_API_HOST=http://127.0.0.1:7091 -VITE_API_STREAMING=true \ No newline at end of file +VITE_API_STREAMING=true +VITE_NOTIFICATION_TEXT="What's new in 0.14.0 — Changelog" +VITE_NOTIFICATION_LINK="#" \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index fcb9a939..cf5a4fd0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,7 +15,7 @@ "i18next": "^25.5.3", "i18next-browser-languagedetector": "^8.0.2", "lodash": "^4.17.21", - "mermaid": "^11.6.0", + "mermaid": "^11.12.0", "prop-types": "^15.8.1", "react": "^19.1.0", "react-chartjs-2": "^5.3.0", @@ -90,22 +90,22 @@ } }, "node_modules/@antfu/install-pkg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.0.0.tgz", - "integrity": "sha512-xvX6P/lo1B3ej0OsaErAjqgFYzYVcJpamjLAFLYh9vRJngBrMoUG7aVnrGTeqM7yxbyTD5p3F2+0/QUEh8Vzhw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", "license": "MIT", "dependencies": { - "package-manager-detector": "^0.2.8", - "tinyexec": "^0.3.2" + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" }, "funding": { "url": "https://github.com/sponsors/antfu" } }, "node_modules/@antfu/utils": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-8.1.1.tgz", - "integrity": "sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==", + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-9.3.0.tgz", + "integrity": "sha512-9hFT4RauhcUzqOE4f1+frMKLZrgNog5b06I7VmZQV1BkvwvqrbC8EBZf3L1eEL2AKb6rNKjER0sEvJiSP1FXEA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" @@ -957,18 +957,18 @@ "license": "MIT" }, "node_modules/@iconify/utils": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.3.0.tgz", - "integrity": "sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.0.2.tgz", + "integrity": "sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ==", "license": "MIT", "dependencies": { - "@antfu/install-pkg": "^1.0.0", - "@antfu/utils": "^8.1.0", + "@antfu/install-pkg": "^1.1.0", + "@antfu/utils": "^9.2.0", "@iconify/types": "^2.0.0", - "debug": "^4.4.0", - "globals": "^15.14.0", + "debug": "^4.4.1", + "globals": "^15.15.0", "kolorist": "^1.8.0", - "local-pkg": "^1.0.0", + "local-pkg": "^1.1.1", "mlly": "^1.7.4" } }, @@ -1053,9 +1053,9 @@ "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" }, "node_modules/@mermaid-js/parser": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.4.0.tgz", - "integrity": "sha512-wla8XOWvQAwuqy+gxiZqY+c7FokraOTHRWMsbB4AgRx9Sy7zKslNyejy7E+a77qHfey5GXw/ik3IXv/NHMJgaA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz", + "integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==", "license": "MIT", "dependencies": { "langium": "3.3.1" @@ -2856,9 +2856,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -4141,15 +4141,16 @@ } }, "node_modules/dayjs": { - "version": "1.11.13", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", "license": "MIT" }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -5326,9 +5327,9 @@ } }, "node_modules/exsolve": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.4.tgz", - "integrity": "sha512-xsZH6PXaER4XoV+NiT7JHp1bJodJVT+cxeSH1G0f0tlT0lJqYuHUP3bUx2HtfTDvOagMINYp8rsqusxud3RXhw==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", "license": "MIT" }, "node_modules/extend": { @@ -6909,13 +6910,14 @@ } }, "node_modules/katex": { - "version": "0.16.21", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.21.tgz", - "integrity": "sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==", + "version": "0.16.23", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.23.tgz", + "integrity": "sha512-7VlC1hsEEolL9xNO05v9VjrvWZePkCVBJqj8ruICxYjZfHaHbaU53AlP+PODyFIXEnaEIEWi3wJy7FPZ95JAVg==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" ], + "license": "MIT", "dependencies": { "commander": "^8.3.0" }, @@ -7280,14 +7282,14 @@ } }, "node_modules/local-pkg": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.1.tgz", - "integrity": "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", "license": "MIT", "dependencies": { "mlly": "^1.7.4", - "pkg-types": "^2.0.1", - "quansync": "^0.2.8" + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" }, "engines": { "node": ">=14" @@ -7483,15 +7485,15 @@ } }, "node_modules/marked": { - "version": "15.0.7", - "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.7.tgz", - "integrity": "sha512-dgLIeKGLx5FwziAnsk4ONoGwHwGPJzselimvlVskE9XLN4Orv9u2VA3GWw/lYUqjfA0rUT/6fqKwfZJapP9BEg==", + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.0.tgz", + "integrity": "sha512-CTPAcRBq57cn3R8n3hwc2REddc28hjR7RzDXQ+lXLmMJYqn20BaI2cGw6QjgZGIgVfp2Wdfw4aMzgNteQ6qJgQ==", "license": "MIT", "bin": { "marked": "bin/marked.js" }, "engines": { - "node": ">= 18" + "node": ">= 20" } }, "node_modules/math-intrinsics": { @@ -7927,14 +7929,14 @@ } }, "node_modules/mermaid": { - "version": "11.6.0", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.6.0.tgz", - "integrity": "sha512-PE8hGUy1LDlWIHWBP05SFdqUHGmRcCcK4IzpOKPE35eOw+G9zZgcnMpyunJVUEOgb//KBORPjysKndw8bFLuRg==", + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.0.tgz", + "integrity": "sha512-ZudVx73BwrMJfCFmSSJT84y6u5brEoV8DOItdHomNLz32uBjNrelm7mg95X7g+C6UoQH/W6mBLGDEDv73JdxBg==", "license": "MIT", "dependencies": { - "@braintree/sanitize-url": "^7.0.4", - "@iconify/utils": "^2.1.33", - "@mermaid-js/parser": "^0.4.0", + "@braintree/sanitize-url": "^7.1.1", + "@iconify/utils": "^3.0.1", + "@mermaid-js/parser": "^0.6.2", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", "cytoscape-cose-bilkent": "^4.1.0", @@ -7942,12 +7944,12 @@ "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.11", - "dayjs": "^1.11.13", - "dompurify": "^3.2.4", - "katex": "^0.16.9", + "dayjs": "^1.11.18", + "dompurify": "^3.2.5", + "katex": "^0.16.22", "khroma": "^2.1.0", "lodash-es": "^4.17.21", - "marked": "^15.0.7", + "marked": "^16.2.1", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", @@ -8606,15 +8608,15 @@ } }, "node_modules/mlly": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", - "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", "license": "MIT", "dependencies": { - "acorn": "^8.14.0", - "pathe": "^2.0.1", - "pkg-types": "^1.3.0", - "ufo": "^1.5.4" + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" } }, "node_modules/mlly/node_modules/confbox": { @@ -8923,13 +8925,10 @@ } }, "node_modules/package-manager-detector": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.11.tgz", - "integrity": "sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==", - "license": "MIT", - "dependencies": { - "quansync": "^0.2.7" - } + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.4.0.tgz", + "integrity": "sha512-rRZ+pR1Usc+ND9M2NkmCvE/LYJS+8ORVV9X0KuNSY/gFsp7RBHJM/ADh9LYq4Vvfq6QkKrW6/weuh8SMEtN5gw==", + "license": "MIT" }, "node_modules/parent-module": { "version": "1.0.1", @@ -9084,13 +9083,13 @@ } }, "node_modules/pkg-types": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.1.0.tgz", - "integrity": "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", "license": "MIT", "dependencies": { - "confbox": "^0.2.1", - "exsolve": "^1.0.1", + "confbox": "^0.2.2", + "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, @@ -9302,9 +9301,9 @@ } }, "node_modules/quansync": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.10.tgz", - "integrity": "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==", + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", "funding": [ { "type": "individual", @@ -10539,9 +10538,9 @@ "dev": true }, "node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", "license": "MIT" }, "node_modules/tinyglobby": { @@ -10791,9 +10790,9 @@ } }, "node_modules/ufo": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz", - "integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", "license": "MIT" }, "node_modules/unbox-primitive": { diff --git a/frontend/package.json b/frontend/package.json index e6292774..c689baaf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -26,7 +26,7 @@ "i18next": "^25.5.3", "i18next-browser-languagedetector": "^8.0.2", "lodash": "^4.17.21", - "mermaid": "^11.6.0", + "mermaid": "^11.12.0", "prop-types": "^15.8.1", "react": "^19.1.0", "react-chartjs-2": "^5.3.0", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0a8c22f1..0c3384d1 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -15,6 +15,7 @@ import useTokenAuth from './hooks/useTokenAuth'; import Navigation from './Navigation'; import PageNotFound from './PageNotFound'; import Setting from './settings'; +import Notification from './components/Notification'; function AuthWrapper({ children }: { children: React.ReactNode }) { const { isAuthLoading } = useTokenAuth(); @@ -52,11 +53,27 @@ function MainLayout() { } export default function App() { const [, , componentMounted] = useDarkTheme(); + const [showNotification, setShowNotification] = useState(() => { + const saved = localStorage.getItem('showNotification'); + return saved ? JSON.parse(saved) : true; + }); + const notificationText = import.meta.env.VITE_NOTIFICATION_TEXT; + const notificationLink = import.meta.env.VITE_NOTIFICATION_LINK; if (!componentMounted) { return
; } return (
+ {notificationLink && notificationText && showNotification && ( + { + setShowNotification(false); + localStorage.setItem('showNotification', 'false'); + }} + /> + )} Loading conversations diff --git a/frontend/src/agents/SharedAgentCard.tsx b/frontend/src/agents/SharedAgentCard.tsx index 46988979..a2b02c87 100644 --- a/frontend/src/agents/SharedAgentCard.tsx +++ b/frontend/src/agents/SharedAgentCard.tsx @@ -2,6 +2,12 @@ import AgentImage from '../components/AgentImage'; import { Agent } from './types'; export default function SharedAgentCard({ agent }: { agent: Agent }) { + // Check if shared metadata exists and has properties (type is 'any' so we validate it's a non-empty object) + const hasSharedMetadata = + agent.shared_metadata && + typeof agent.shared_metadata === 'object' && + agent.shared_metadata !== null && + Object.keys(agent.shared_metadata).length > 0; return (
@@ -20,7 +26,7 @@ export default function SharedAgentCard({ agent }: { agent: Agent }) {

- {agent.shared_metadata && ( + {hasSharedMetadata && (
{agent.shared_metadata?.shared_by && (

diff --git a/frontend/src/assets/arrow-full-right.svg b/frontend/src/assets/arrow-full-right.svg new file mode 100644 index 00000000..d6004b4a --- /dev/null +++ b/frontend/src/assets/arrow-full-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/assets/notification-bg.jpg b/frontend/src/assets/notification-bg.jpg new file mode 100644 index 00000000..6e0a5d0f Binary files /dev/null and b/frontend/src/assets/notification-bg.jpg differ diff --git a/frontend/src/components/MessageInput.tsx b/frontend/src/components/MessageInput.tsx index 1052843d..3b5bdd3c 100644 --- a/frontend/src/components/MessageInput.tsx +++ b/frontend/src/components/MessageInput.tsx @@ -7,11 +7,9 @@ import userService from '../api/services/userService'; import AlertIcon from '../assets/alert.svg'; import ClipIcon from '../assets/clip.svg'; import ExitIcon from '../assets/exit.svg'; -import PaperPlane from '../assets/paper_plane.svg'; +import SendArrowIcon from './SendArrowIcon'; import SourceIcon from '../assets/source.svg'; import DocumentationDark from '../assets/documentation-dark.svg'; -import SpinnerDark from '../assets/spinner-dark.svg'; -import Spinner from '../assets/spinner.svg'; import ToolIcon from '../assets/tool.svg'; import { addAttachment, @@ -19,7 +17,7 @@ import { selectAttachments, updateAttachment, } from '../upload/uploadSlice'; -import { useDarkTheme } from '../hooks'; + import { ActiveState } from '../models/misc'; import { selectSelectedDocs, @@ -29,6 +27,7 @@ import Upload from '../upload/Upload'; import { getOS, isTouchDevice } from '../utils/browserUtils'; import SourcesPopup from './SourcesPopup'; import ToolsPopup from './ToolsPopup'; +import { handleAbort } from '../conversation/conversationSlice'; type MessageInputProps = { onSubmit: (text: string) => void; @@ -46,7 +45,6 @@ export default function MessageInput({ autoFocus = true, }: MessageInputProps) { const { t } = useTranslation(); - const [isDarkTheme] = useDarkTheme(); const [value, setValue] = useState(''); const inputRef = useRef(null); const sourceButtonRef = useRef(null); @@ -256,6 +254,11 @@ export default function MessageInput({ setValue(''); } }; + + const handleCancel = () => { + handleAbort(); + }; + return (

@@ -427,26 +430,33 @@ export default function MessageInput({ {/* Additional badges can be added here in the future */}
- + ) : ( + + + )}
diff --git a/frontend/src/components/Notification.tsx b/frontend/src/components/Notification.tsx new file mode 100644 index 00000000..7bd48158 --- /dev/null +++ b/frontend/src/components/Notification.tsx @@ -0,0 +1,45 @@ +import close from '../assets/cross.svg'; +import rightArrow from '../assets/arrow-full-right.svg'; +import bg from '../assets/notification-bg.jpg'; + +interface NotificationProps { + notificationText: string; + notificationLink: string; + handleCloseNotification: () => void; +} + +export default function Notification({ + notificationText, + notificationLink, + handleCloseNotification, +}: NotificationProps) { + return ( + +

+ {notificationText} +

+ + + + + +
+ ); +} diff --git a/frontend/src/components/SendArrowIcon.tsx b/frontend/src/components/SendArrowIcon.tsx new file mode 100644 index 00000000..f88dde3d --- /dev/null +++ b/frontend/src/components/SendArrowIcon.tsx @@ -0,0 +1,17 @@ +import { SVGProps } from 'react'; +const SendArrowIcon = (props: SVGProps) => ( + + + +); +export default SendArrowIcon; diff --git a/frontend/src/conversation/Conversation.tsx b/frontend/src/conversation/Conversation.tsx index d7b59a4e..312b92c9 100644 --- a/frontend/src/conversation/Conversation.tsx +++ b/frontend/src/conversation/Conversation.tsx @@ -202,6 +202,7 @@ export default function Conversation() { queries[queries.length - 1].response && setLastQueryReturnedErr(false); } }, [queries[queries.length - 1]]); + return (