Render html optional (#4478)

* allow user to render HTML in chat responses
resolves #4476

* add file

* Add translation for all non-en
This commit is contained in:
Timothy Carambat 2025-10-02 13:52:07 -07:00 committed by GitHub
parent 8cdadd8cb3
commit 87c666466f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 153 additions and 3 deletions

View File

@ -804,6 +804,10 @@ const TRANSLATIONS = {
icon: null,
link: null,
},
"render-html": {
title: null,
description: null,
},
},
},
"main-page": {

View File

@ -843,6 +843,10 @@ const TRANSLATIONS = {
icon: null,
link: null,
},
"render-html": {
title: null,
description: null,
},
},
},
"main-page": {

View File

@ -546,6 +546,10 @@ const TRANSLATIONS = {
icon: "Icon",
link: "Link",
},
"render-html": {
title: null,
description: null,
},
},
},
api: {

View File

@ -564,6 +564,11 @@ const TRANSLATIONS = {
icon: "Icon",
link: "Link",
},
"render-html": {
title: "Render HTML in chat",
description:
"Render HTML responses in assistant responses.\nThis can result in a much higher fidelity of response quality, but can also lead to potential security risks.",
},
},
},

View File

@ -557,6 +557,10 @@ const TRANSLATIONS = {
icon: "Icono",
link: "Enlace",
},
"render-html": {
title: null,
description: null,
},
},
},
api: {

View File

@ -523,6 +523,10 @@ const TRANSLATIONS = {
icon: "Ikoon",
link: "Link",
},
"render-html": {
title: null,
description: null,
},
},
},
api: {

View File

@ -796,6 +796,10 @@ const TRANSLATIONS = {
icon: null,
link: null,
},
"render-html": {
title: null,
description: null,
},
},
},
"main-page": {

View File

@ -804,6 +804,10 @@ const TRANSLATIONS = {
icon: null,
link: null,
},
"render-html": {
title: null,
description: null,
},
},
},
"main-page": {

View File

@ -529,6 +529,10 @@ const TRANSLATIONS = {
icon: "סמל",
link: "קישור",
},
"render-html": {
title: null,
description: null,
},
},
},
api: {

View File

@ -802,6 +802,10 @@ const TRANSLATIONS = {
icon: null,
link: null,
},
"render-html": {
title: null,
description: null,
},
},
},
"main-page": {

View File

@ -835,6 +835,10 @@ const TRANSLATIONS = {
icon: null,
link: null,
},
"render-html": {
title: null,
description: null,
},
},
},
"main-page": {

View File

@ -532,6 +532,10 @@ const TRANSLATIONS = {
icon: "아이콘",
link: "링크",
},
"render-html": {
title: null,
description: null,
},
},
},
api: {

View File

@ -541,6 +541,10 @@ const TRANSLATIONS = {
icon: "Ikona",
link: "Saite",
},
"render-html": {
title: null,
description: null,
},
},
},
api: {

View File

@ -799,6 +799,10 @@ const TRANSLATIONS = {
icon: null,
link: null,
},
"render-html": {
title: null,
description: null,
},
},
},
"main-page": {

View File

@ -546,6 +546,10 @@ const TRANSLATIONS = {
icon: "Ikona",
link: "Link",
},
"render-html": {
title: null,
description: null,
},
},
},
api: {

View File

@ -531,6 +531,10 @@ const TRANSLATIONS = {
icon: "Ícone",
link: "Link",
},
"render-html": {
title: null,
description: null,
},
},
},
api: {

View File

@ -998,6 +998,10 @@ const TRANSLATIONS = {
icon: "Iconiță",
link: "Link",
},
"render-html": {
title: null,
description: null,
},
},
},
api: {

View File

@ -844,6 +844,10 @@ const TRANSLATIONS = {
icon: null,
link: null,
},
"render-html": {
title: null,
description: null,
},
},
},
"main-page": {

View File

@ -799,6 +799,10 @@ const TRANSLATIONS = {
icon: null,
link: null,
},
"render-html": {
title: null,
description: null,
},
},
},
"main-page": {

View File

@ -798,6 +798,10 @@ const TRANSLATIONS = {
icon: null,
link: null,
},
"render-html": {
title: null,
description: null,
},
},
},
"main-page": {

View File

@ -509,6 +509,10 @@ const TRANSLATIONS = {
icon: "图标",
link: "链接",
},
"render-html": {
title: null,
description: null,
},
},
},
api: {

View File

@ -806,6 +806,10 @@ const TRANSLATIONS = {
icon: "圖示",
link: "連結",
},
"render-html": {
title: null,
description: null,
},
},
},
"main-page": {

View File

@ -4,7 +4,8 @@ import { APPEARANCE_SETTINGS } from "@/utils/constants";
* @typedef { 'showScrollbar' |
* 'autoSubmitSttInput' |
* 'autoPlayAssistantTtsResponse' |
* 'enableSpellCheck'
* 'enableSpellCheck' |
* 'renderHTML'
* } AvailableSettings - The supported settings for the appearance model.
*/
@ -14,11 +15,12 @@ const Appearance = {
autoSubmitSttInput: true,
autoPlayAssistantTtsResponse: false,
enableSpellCheck: true,
renderHTML: false,
},
/**
* Fetches any locally storage settings for the user
* @returns {{showScrollbar: boolean}}
* @returns {{showScrollbar: boolean, autoSubmitSttInput: boolean, autoPlayAssistantTtsResponse: boolean, enableSpellCheck: boolean, renderHTML: boolean}}
*/
getSettings: () => {
try {

View File

@ -5,6 +5,7 @@ import AutoSubmit from "../components/AutoSubmit";
import AutoSpeak from "../components/AutoSpeak";
import SpellCheck from "../components/SpellCheck";
import ShowScrollbar from "../components/ShowScrollbar";
import ChatRenderHTML from "../components/ChatRenderHTML";
export default function ChatSettings() {
const { t } = useTranslation();
@ -31,6 +32,7 @@ export default function ChatSettings() {
<AutoSpeak />
<SpellCheck />
<ShowScrollbar />
<ChatRenderHTML />
</div>
</div>
</div>

View File

@ -0,0 +1,56 @@
import React, { useState, useEffect } from "react";
import Appearance from "@/models/appearance";
import { useTranslation } from "react-i18next";
export default function ChatRenderHTML() {
const { t } = useTranslation();
const [saving, setSaving] = useState(false);
const [renderHTML, setRenderHTML] = useState(false);
const handleChange = async (e) => {
const newValue = e.target.checked;
setRenderHTML(newValue);
setSaving(true);
try {
Appearance.updateSettings({ renderHTML: newValue });
} catch (error) {
console.error("Failed to update appearance settings:", error);
setRenderHTML(!newValue);
}
setSaving(false);
};
useEffect(() => {
function fetchSettings() {
const settings = Appearance.getSettings();
setRenderHTML(settings.renderHTML);
}
fetchSettings();
}, []);
return (
<div className="flex flex-col gap-y-0.5 my-4">
<p className="text-sm leading-6 font-semibold text-white">
{t("customization.items.render-html.title")}
</p>
<p className="text-xs text-white/60 w-1/2 whitespace-pre-line">
{t("customization.items.render-html.description")}
</p>
<div className="flex items-center gap-x-4 pt-1">
<label className="relative inline-flex cursor-pointer items-center">
<input
id="render_html"
type="checkbox"
name="render_html"
value="yes"
checked={renderHTML}
onChange={handleChange}
disabled={saving}
className="peer sr-only"
/>
<div className="pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
</label>
</div>
</div>
);
}

View File

@ -1,13 +1,14 @@
import { encode as HTMLEncode } from "he";
import markdownIt from "markdown-it";
import markdownItKatexPlugin from "./plugins/markdown-katex";
import Appearance from "@/models/appearance";
import hljs from "highlight.js";
import "./themes/github-dark.css";
import "./themes/github.css";
import { v4 } from "uuid";
const markdown = markdownIt({
html: false,
html: Appearance.get("renderHTML") ?? false,
typographer: true,
highlight: function (code, lang) {
const uuid = v4();