Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 12 additions & 1 deletion front/src/Redux/reducers/interfaceSettingsSlice.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const interfaceSettingsSlice = createSlice({
warn_clear_history: true,
// warn_clear_memory: true,
warn_share_settings: true,
warn_regenerate: true,
warn_fork: true,
show_tour: true,
count_hallucination: 0,
count_announcement: 0,
Expand Down Expand Up @@ -55,6 +57,12 @@ const interfaceSettingsSlice = createSlice({
setShowUsageInSidebar: (state, action) => {
state.show_usage_in_sidebar = Boolean(action.payload);
},
// Enable or disable one of the warn_* confirmation dialogs, e.g. when the
// user dismisses it with "don't show this again".
setWarning: (state, action) => {
const { key, value } = action.payload;
state[key] = Boolean(value);
},
},
});

Expand All @@ -67,10 +75,13 @@ export const selectAgreeWebSearch = (state) => state.interface_settings.agree_we
export const selectShowTour = (state) => state.interface_settings.show_tour;
export const selectShowUsageInSidebar = (state) =>
state.interface_settings.show_usage_in_sidebar ?? true;
// Warnings default to shown, so anything unknown counts as enabled.
export const selectWarning = (key) => (state) =>
state.interface_settings[key] ?? true;
export const {
toggleTheme, setDarkMode, setLightMode,
closeSettings, toggleSettings, toggleSidebar, closeSidebar, openSidebar,
closeAnnouncement, closeHallucination, agreeWebSearch, closeTour,
setShowUsageInSidebar
setShowUsageInSidebar, setWarning
} = interfaceSettingsSlice.actions;
export default interfaceSettingsSlice.reducer;
2 changes: 2 additions & 0 deletions front/src/Redux/store/store.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ const getDefaultState = () => {
warn_clear_history: true,
warn_clear_memory: true,
warn_clear_settings: true,
warn_regenerate: true,
warn_fork: true,
count_hallucination: 0,
count_announcement: 0,
show_usage_in_sidebar: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Workflow } from "lucide-react";
import { GitFork } from "lucide-react";
import { useTranslation } from "react-i18next";

export default function ForkButton({ handleForkConversation }) {
const { t } = useTranslation();
return (
<button onClick={handleForkConversation} title="Continue in new chat" className=" h-[22px] w-[22px] cursor-pointer disabled:opacity-40">
<button onClick={handleForkConversation} title={t("common.fork")} className=" h-[22px] w-[22px] cursor-pointer disabled:opacity-40">
{" "}
<Workflow
<GitFork
className="opacity-20 group-hover:opacity-100 transition-opacity duration-300 h-[22px] w-[22px] cursor-pointer text-[#009EE0]"
alt="fork_icon"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import EditButton from "./EditButton";
import { RotateCw, GitFork } from "lucide-react";
import { useSendMessage } from "../../../hooks/useSendMessage";
import { useForkConversation } from "../../../hooks/useForkConversation";
import { useModal } from "../../../modals/ModalContext";
import MetaBox from "./MetaBox";
import FeedbackButtons from "./FeedbackButtons";
import ForkButton from "./ForkButton";
Expand All @@ -30,6 +31,7 @@ export default React.memo(({ localState, setLocalState, message_index }) => {

const sendMessage = useSendMessage();
const { forkConversation } = useForkConversation(localState);
const { confirmAction } = useModal();
const feedbackModule = import.meta.env.VITE_MODULE_FEEDBACK === "true";

//Functions
Expand Down Expand Up @@ -151,8 +153,16 @@ export default React.memo(({ localState, setLocalState, message_index }) => {
};

const handleForkConversation = useCallback(
() => forkConversation(message_index),
[forkConversation, message_index]
() =>
confirmAction(
{
warningKey: "warn_fork",
messageKey: "alert.fork_confirm",
confirmKey: "alert.fork_yes",
},
() => forkConversation(message_index)
),
[confirmAction, forkConversation, message_index]
);

const content = message?.content?.[0]?.text ?? "";
Expand Down
34 changes: 34 additions & 0 deletions front/src/components/Conversation/MessageUser/ForkButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { GitFork } from "lucide-react";
import { useTranslation } from "react-i18next";
import { useForkConversation } from "../../../hooks/useForkConversation";
import { useModal } from "../../../modals/ModalContext";
import Tooltip from "../../Others/Tooltip";

export default function ForkButton({ localState, message_index }) {
const { t } = useTranslation();
const { forkConversation, forking } = useForkConversation(localState);
const { confirmAction } = useModal();

// Forking keeps this conversation untouched, but it is not obvious what it
// does, so explain it once.
const handleFork = () =>
confirmAction(
{
warningKey: "warn_fork",
messageKey: "alert.fork_confirm",
confirmKey: "alert.fork_yes",
},
() => forkConversation(message_index)
);

return (
<Tooltip text={t("common.fork")}>
<button onClick={handleFork} disabled={forking} className="flex">
<GitFork
className="h-[22px] w-[22px] cursor-pointer text-[#009EE0]"
alt="icon_fork"
/>
</button>
</Tooltip>
);
}
6 changes: 6 additions & 0 deletions front/src/components/Conversation/MessageUser/MessageUser.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from "react";
import { useState, useRef, useEffect } from "react";
import RetryButton from "./RetryButton";
import ForkButton from "./ForkButton";
import EditButton from "./EditButton";
import EditBox from "./EditBox";
import MessageTextContainer from "./MessageTextContainer";
Expand Down Expand Up @@ -52,6 +53,11 @@ export default React.memo(({
setLocalState={setLocalState}
message_index={message_index}
/>
{/* Fork button */}
<ForkButton
localState={localState}
message_index={message_index}
/>
{/* Edit button */}
<EditButton
setEditMode={setEditMode}
Expand Down
43 changes: 22 additions & 21 deletions front/src/components/Conversation/MessageUser/RetryButton.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { RotateCw } from "lucide-react";
import { useTranslation } from "react-i18next";
import { useSendMessage } from "../../../hooks/useSendMessage";
import { useForkConversation } from "../../../hooks/useForkConversation";
import { useModal } from "../../../modals/ModalContext";
import Tooltip from "../../Others/Tooltip";

export default function RetryButton({
localState,
setLocalState,
message_index,
}) {
const { t } = useTranslation();
const sendMessage = useSendMessage();
const { forkConversation } = useForkConversation(localState);
const { openModal } = useModal();
const { confirmAction } = useModal();

// Drop the current response (and everything after this message), then
// resend using the conversation's *current* settings.
Expand All @@ -24,25 +25,25 @@ export default function RetryButton({

// Regenerating is destructive (subsequent messages are lost), so confirm
// first unless the user opted out.
const handleRetry = () => {
if (localState?.dontShow?.regenerate) {
regenerate();
} else {
openModal("regenerateConfirm", {
localState,
setLocalState,
regenerate,
forkConversation: () => forkConversation(message_index),
});
}
};
const handleRetry = () =>
confirmAction(
{
warningKey: "warn_regenerate",
messageKey: "alert.regenerate_confirm",
confirmKey: "alert.regenerate_yes",
danger: true,
},
regenerate
);

return (
<button onClick={handleRetry}>
<RotateCw
className="h-[22px] w-[22px] cursor-pointer text-[#009EE0]"
alt="icon_retry"
/>
</button>
<Tooltip text={t("common.regenerate")}>
<button onClick={handleRetry} className="flex">
<RotateCw
className="h-[22px] w-[22px] cursor-pointer text-[#009EE0]"
alt="icon_retry"
/>
</button>
</Tooltip>
);
}
10 changes: 6 additions & 4 deletions front/src/hooks/useForkConversation.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useCallback, useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router";
import { createConversation, newId, saveFile, loadFile } from "../db";
import { useToast } from "./useToast";
Expand Down Expand Up @@ -43,6 +44,7 @@ async function cloneMessageContent(content, targetConversationId) {
// `message_index` into a brand-new conversation, then navigate to it.
export function useForkConversation(localState) {
const navigate = useNavigate();
const { t } = useTranslation();
const { notifySuccess, notifyError } = useToast();
const [forking, setForking] = useState(false);

Expand All @@ -54,7 +56,7 @@ export function useForkConversation(localState) {
const sourceMessages =
localState?.messages?.slice(0, message_index + 1) || [];
if (sourceMessages.length === 0) {
notifyError("Keine Nachrichten zum Forken gefunden.");
notifyError(t("conversation.fork_empty"));
return;
}

Expand Down Expand Up @@ -101,16 +103,16 @@ export function useForkConversation(localState) {
folderId: localState?.folderId ?? null,
});

notifySuccess("Neue Konversation erstellt.");
notifySuccess(t("conversation.fork_created"));
navigate(`/chat/${newConversationId}`);
} catch (error) {
console.error("Failed to fork conversation", error);
notifyError("Konversation konnte nicht geforkt werden.");
notifyError(t("conversation.fork_failed"));
} finally {
setForking(false);
}
},
[forking, localState, navigate, notifyError, notifySuccess]
[forking, localState, navigate, notifyError, notifySuccess, t]
);

return { forkConversation, forking };
Expand Down
11 changes: 9 additions & 2 deletions front/src/i18n/de.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export default {
record_start: "Aufnehmen (klicken/halten)",
record_stop: "Stopp (klicken/loslassen)",
dont_show_again: "Nicht mehr anzeigen",
regenerate: "Antwort neu generieren",
fork: "In neuem Chat fortfahren",
backup_data: "Daten sichern",
skip_backup: "Backup überspringen",
upgrade_chat_ai: "Chat AI aktualisieren",
Expand Down Expand Up @@ -136,6 +138,9 @@ export default {
empty_message:
"Ihre Gespräche werden niemals auf unseren Servern gespeichert.",
untitled: "Unbenannte Unterhaltung",
fork_empty: "Keine Nachrichten zum Kopieren gefunden.",
fork_created: "Neue Unterhaltung erstellt.",
fork_failed: "Unterhaltung konnte nicht kopiert werden.",
},
// Footer
footer: {
Expand Down Expand Up @@ -411,9 +416,11 @@ export default {
clear_messages:
"Sind Sie sicher, dass alle Nachrichten gelöscht werden sollen?",
regenerate_confirm:
"Beim Regenerieren werden diese Antwort und alle nachfolgenden Nachrichten gelöscht und die aktuellen Einstellungen angewendet. Um diese Unterhaltung zu behalten, können Sie in einer Kopie dieses Chats fortfahren.",
"Beim Regenerieren werden diese Antwort und alle nachfolgenden Nachrichten gelöscht und die aktuellen Einstellungen angewendet. Dies kann nicht rückgängig gemacht werden.",
regenerate_yes: "Regenerieren",
regenerate_fork: "Kopie",
fork_confirm:
"Die Unterhaltung wird bis zu dieser Nachricht in einen neuen Chat kopiert. Die aktuelle Unterhaltung bleibt unverändert, Sie können also eine andere Antwort oder andere Einstellungen ausprobieren, ohne etwas zu verlieren.",
fork_yes: "Kopie erstellen",
summarize_replace:
"Dies ersetzt den aktuellen Chatverlauf durch eine Zusammenfassung. Die bisherigen Nachrichten werden aus dieser Unterhaltung entfernt. Fortfahren?",
summarize_in_progress:
Expand Down
11 changes: 9 additions & 2 deletions front/src/i18n/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export default {
record_start: "Record (click/hold)",
record_stop: "Stop (click/release)",
dont_show_again: "Don't show this again",
regenerate: "Regenerate response",
fork: "Continue in a new chat",
backup_data: "Backup Data",
skip_backup: "Skip Backup",
upgrade_chat_ai: "Upgrade Chat AI",
Expand Down Expand Up @@ -135,6 +137,9 @@ export default {
references: "References",
empty_message: "Your conversations are never stored on our servers",
untitled: "Untitled Conversation",
fork_empty: "No messages found to copy.",
fork_created: "New conversation created.",
fork_failed: "Conversation could not be copied.",
},
// Footer
footer: {
Expand Down Expand Up @@ -407,9 +412,11 @@ export default {
clear_messages:
"Are you sure you want to erase all messages in this conversation?",
regenerate_confirm:
"Regenerating will delete this response and every message after it, and will apply your current settings. To keep this conversation intact, fork it into a new chat instead.",
"Regenerating will delete this response and every message after it, and will apply your current settings. This cannot be undone.",
regenerate_yes: "Regenerate",
regenerate_fork: "Fork",
fork_confirm:
"This copies the conversation up to this message into a new chat. The current conversation stays as it is, so you can try a different answer or different settings without losing anything.",
fork_yes: "Create copy",
summarize_replace:
"This will replace your current chat history with a summary. Your existing messages will be removed from this conversation. Continue?",
summarize_in_progress:
Expand Down
Loading