+
+
+
+
+ {t("projectTree.workspaceTitle")}
+
+
+
+
+ {filterMenuOpen && (
+
+
+
+
+
+
+
+
+
+
+
+ )}
- )}
-
-
- );
- };
-
- const renderProjectHeader = (mode: "classic" | "workbench") => (
-
-
-
- {t("projectTree.workspaceTitle")}
-
-
- {mode === "workbench" ? (
- <>
- {renderTimeFilterControl("workbench")}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- ) : (
- <>
- {renderTimeFilterControl("classic")}
-
+
+
+
+
+
+
+
+
+
+
+ {visibleTree.length === 0 ? (
+ query.trim() ? (
+
{t("projectTree.emptyNoMatch")}
+ ) : timeFilter !== "all" ? (
+
{t("projectTree.emptyNoTimeFilterMatch")}
-
-
+
+ ) : (
+
+
{t("projectTree.emptyNoProjects")}
-
- >
+
+ )
+ ) : (
+ visibleTree.map((node) => renderNode(node, 0))
)}
-
-
- );
-
- const renderEmptyState = () => {
- if (query.trim()) return
{t("projectTree.emptyNoMatch")}
;
- if (timeFilter !== "all") {
- return (
-
{t("projectTree.emptyNoTimeFilterMatch")}
-
-
- );
- }
- return (
-
-
{t("projectTree.emptyNoProjects")}
-
- );
- };
-
- const hasWorkbenchRows = workbenchTreeSections.pinned.length > 0 || workbenchTreeSections.projects.length > 0;
-
- return (
-
-
- {compactTopics ? (
-
- {!hasWorkbenchRows ? (
- renderEmptyState()
- ) : (
- <>
- {workbenchTreeSections.pinned.length > 0 && (
-
-
{t("projectTree.pinnedTitle")}
- {workbenchTreeSections.pinned.map((node) => renderNode(node, 0, "pinned"))}
-
- )}
-
- {renderProjectHeader("workbench")}
- {workbenchTreeSections.projects.map((node) => renderNode(node, 0, "projects"))}
-
- >
- )}
-
- ) : (
- <>
- {renderProjectHeader("classic")}
-
- {visibleTree.length === 0 ? renderEmptyState() : visibleTree.map((node) => renderNode(node, 0))}
-
- >
- )}
);
}
diff --git a/desktop/frontend/src/components/SettingsPanel.tsx b/desktop/frontend/src/components/SettingsPanel.tsx
index a8f343d4f..447c38a1d 100644
--- a/desktop/frontend/src/components/SettingsPanel.tsx
+++ b/desktop/frontend/src/components/SettingsPanel.tsx
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useId, useMemo, useRef, useState, type MouseEvent as ReactMouseEvent, type PointerEvent, type ReactNode } from "react";
import { QRCodeSVG } from "qrcode.react";
-import { Check, CheckCircle2, ChevronDown, ChevronUp, Clipboard, GripVertical, KeyRound, Loader2, Play, QrCode, RefreshCw, Send } from "lucide-react";
+import { Check, CheckCircle2, ChevronDown, ChevronUp, GripVertical, Loader2, Play, QrCode, RefreshCw } from "lucide-react";
import { asArray } from "../lib/array";
import { useDeferredClose } from "../lib/useMountTransition";
import { app } from "../lib/bridge";
@@ -18,22 +18,10 @@ import {
type ThemeStyle,
} from "../lib/theme";
import { TEXT_SIZES, applyTextSize, getTextSize, type TextSize } from "../lib/textSize";
-import {
- applyFontFamily,
- applyMonoFontFamily,
- getFontFamily,
- getMonoFontFamily,
- getCustomFontName,
- getCustomMonoFontName,
- setCustomFontName,
- setCustomMonoFontName,
- type FontFamily,
- type MonoFontFamily,
-} from "../lib/fontFamily";
-import { getAvailableFontFamilies, getAvailableMonoFontFamilies } from "../lib/fontAvailability";
+import { FONT_FAMILIES, applyFontFamily, getFontFamily, getCustomFontName, setCustomFontName, type FontFamily } from "../lib/fontFamily";
import { getDisplayMode, onDisplayModeChange, setDisplayMode as setLocalDisplayMode } from "../lib/displayMode";
import { DEFAULT_STATUS_BAR_ITEMS, normalizeStatusBarItems, type StatusBarItemId } from "../lib/statusBarItems";
-import type { BotAllowlistView, BotConnectionDiagnostic, BotConnectionView, BotInstallStartResult, BotSettingsView, HookConfigView, HooksSettingsView, NetworkView, ProviderView, SettingsTab, SettingsView } from "../lib/types";
+import type { BotAllowlistView, BotConnectionView, BotInstallStartResult, BotSettingsView, HookConfigView, HooksSettingsView, NetworkView, PersonalitySettingsView, ProviderView, SettingsTab, SettingsView } from "../lib/types";
import { InlineConfirmButton } from "./InlineConfirmButton";
import { Tooltip } from "./Tooltip";
import { AnchoredPopover } from "./AnchoredPopover";
@@ -44,7 +32,7 @@ import { SoundSelect } from "./SoundSelect";
import { getSuccessPreference, setSuccessPreference, getAttentionPreference, setAttentionPreference, playSuccessChime, playAttentionChime, type SoundWavPref } from "../lib/sound";
import { ModalCloseButton } from "./ModalCloseButton";
-const SETTINGS_TABS: SettingsTab[] = ["general", "models", "bots", "mcp", "skills", "memory", "hooks", "permissions", "sandbox", "network", "appearance", "updates"];
+const SETTINGS_TABS: SettingsTab[] = ["general", "models", "bots", "mcp", "skills", "memory", "hooks", "permissions", "sandbox", "network", "appearance", "personality", "updates"];
export type SettingsInitialFocus = { target: "bot-allowlist"; connectionId?: string };
// SettingsPanel is the desktop settings centre — a centred modal with left
@@ -71,9 +59,7 @@ export function SettingsPanel({
const [themeStyle, setThemeStyleState] = useState
(() => getThemeStyle(getTheme()));
const [textSize, setTextSizeState] = useState(getTextSize());
const [fontFamily, setFontFamilyState] = useState(getFontFamily());
- const [monoFontFamily, setMonoFontFamilyState] = useState(getMonoFontFamily());
const [customFontName, setCustomFontNameState] = useState(getCustomFontName());
- const [customMonoFontName, setCustomMonoFontNameState] = useState(getCustomMonoFontName());
const [tab, setTab] = useState(initialTab === "providers" ? "models" : initialTab ?? "general");
// Play the modal exit animation, then let the parent unmount us.
const { status, requestClose } = useDeferredClose(onClose, 240);
@@ -174,9 +160,7 @@ export function SettingsPanel({
themeStyle={themeStyle}
textSize={textSize}
fontFamily={fontFamily}
- monoFontFamily={monoFontFamily}
customFontName={customFontName}
- customMonoFontName={customMonoFontName}
onTheme={(nextTheme) => {
applyTheme(nextTheme, themeStyle, { persist: false });
setThemeState(nextTheme);
@@ -195,23 +179,15 @@ export function SettingsPanel({
applyFontFamily(font);
setFontFamilyState(font);
}}
- onMonoFontFamily={(font) => {
- applyMonoFontFamily(font);
- setMonoFontFamilyState(font);
- }}
onCustomFontNameChange={(name) => {
setCustomFontNameState(name);
setCustomFontName(name);
applyFontFamily("custom");
}}
- onCustomMonoFontNameChange={(name) => {
- setCustomMonoFontNameState(name);
- setCustomMonoFontName(name);
- applyMonoFontFamily("custom");
- }}
/>
)}
+ {tab === "personality" && }
{tab === "updates" && s && (
Promise) => Promise;
};
+// --- Personality section ---
+
+const PERSONALITY_FILE_NAMES = ["IDENTITY.md", "SOUL.md", "USER.md"];
+
+const PERSONALITY_FILE_LABELS: Record = {
+ "IDENTITY.md": "IDENTITY.md — Who you are",
+ "SOUL.md": "SOUL.md — Your behavioural traits",
+ "USER.md": "USER.md — About the user",
+};
+
+const PERSONALITY_FILE_HINTS: Record = {
+ "IDENTITY.md": "Define the agent's core identity, beliefs, and values. This replaces the default 'You are Reasonix...' framing.",
+ "SOUL.md": "Describe behavioural traits, communication style, emotional tone, and quirks. This shapes how the agent expresses itself.",
+ "USER.md": "Describe the user — their role, preferences, goals, and context. The agent uses this to personalise responses.",
+};
+
+function PersonalitySection() {
+ const t = useT();
+ const [settings, setSettings] = useState(null);
+ const [busy, setBusy] = useState(false);
+ const [err, setErr] = useState(null);
+ const [activeFile, setActiveFile] = useState(PERSONALITY_FILE_NAMES[0]);
+ const [editContent, setEditContent] = useState("");
+ const [dirty, setDirty] = useState(false);
+
+ const load = async () => {
+ try {
+ const s = await app.GetPersonalitySettings();
+ setSettings(s);
+ const file = s.files.find((f) => f.name === activeFile);
+ if (file && !dirty) {
+ setEditContent(file.content);
+ }
+ } catch (e) {
+ setErr(String(e));
+ }
+ };
+
+ useEffect(() => { void load(); }, []);
+
+ const selectFile = (name: string) => {
+ setActiveFile(name);
+ setDirty(false);
+ const file = settings?.files.find((f) => f.name === name);
+ setEditContent(file?.content ?? "");
+ };
+
+ const save = async () => {
+ setBusy(true);
+ setErr(null);
+ try {
+ await app.SavePersonalityFile(activeFile, editContent);
+ setDirty(false);
+ await load();
+ } catch (e) {
+ setErr(String(e));
+ } finally {
+ setBusy(false);
+ }
+ };
+
+ const remove = async (name: string) => {
+ setBusy(true);
+ setErr(null);
+ try {
+ await app.DeletePersonalityFile(name);
+ if (activeFile === name) {
+ const next = PERSONALITY_FILE_NAMES.find((n) => n !== name) ?? PERSONALITY_FILE_NAMES[0];
+ setActiveFile(next);
+ setEditContent("");
+ }
+ await load();
+ } catch (e) {
+ setErr(String(e));
+ } finally {
+ setBusy(false);
+ }
+ };
+
+ const toggleEnabled = async (enabled: boolean) => {
+ setBusy(true);
+ setErr(null);
+ try {
+ await app.SetPersonalityEnabled(enabled);
+ await load();
+ } catch (e) {
+ setErr(String(e));
+ } finally {
+ setBusy(false);
+ }
+ };
+
+ return (
+
+ {err && {err}
}
+
+
+
+
+
+
+
+
+ {!settings ? (
+ {t("common.loading")}
+ ) : (
+ <>
+
+
+ {PERSONALITY_FILE_NAMES.map((name) => {
+ const file = settings.files.find((f) => f.name === name);
+ return (
+
+ );
+ })}
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+ );
+}
+
function settingsTabLabel(id: SettingsTab, t: ReturnType): string {
switch (id) {
case "general":
@@ -377,8 +536,12 @@ function settingsTabLabel(id: SettingsTab, t: ReturnType): string {
return t("settings.tab.sandbox");
case "appearance":
return t("settings.tab.appearance");
+ case "personality":
+ return t("settings.tab.personality");
case "updates":
return t("settings.tab.updates");
+ default:
+ return "";
}
}
@@ -387,7 +550,7 @@ function settingsTabMeta(id: SettingsTab, s: SettingsView, t: ReturnType): string
function botSettingsMeta(bot: BotSettingsView, t: ReturnType): string {
const normalized = normalizeBotSettings(bot);
- const connections = normalized.connections.length + (qqBotAdded(normalized.qq) ? 1 : 0);
+ const connections = normalized.connections.length;
if (connections === 0) return t("settings.botNoConnections");
if (!normalized.enabled) return t("settings.botDisabledWithConnections", { n: connections });
return t("settings.botConnectionCount", { n: connections });
@@ -515,7 +682,7 @@ function defaultBotSettings(): BotSettingsView {
feishuGroups: [],
weixinGroups: [],
},
- qq: { enabled: false, appId: "", appSecretEnv: "QQ_BOT_APP_SECRET", secretSet: false, sandbox: false },
+ qq: { enabled: false, appId: "", appSecretEnv: "QQ_BOT_APP_SECRET", secretSet: false },
feishu: {
enabled: false,
domain: "feishu",
@@ -588,10 +755,6 @@ function normalizeBotConnection(raw: any) {
sessionMappings: asArray(raw?.sessionMappings).map((item: any) => ({
remoteId: String(item?.remoteId ?? "").trim(),
sessionId: String(item?.sessionId ?? "").trim(),
- sessionSource: String(item?.sessionSource ?? "").trim(),
- chatType: String(item?.chatType ?? "").trim(),
- userId: String(item?.userId ?? "").trim(),
- threadId: String(item?.threadId ?? "").trim(),
scope: normalizeBotMappingScope(item?.scope, item?.workspaceRoot ?? workspaceRoot),
workspaceRoot: normalizeBotMappingScope(item?.scope, item?.workspaceRoot ?? workspaceRoot) === "project"
? String(item?.workspaceRoot ?? workspaceRoot).trim()
@@ -664,7 +827,6 @@ function normalizeSettingsView(view: SettingsView | null | undefined): SettingsV
autoApproveTools: Boolean(view.autoApproveTools ?? view.bypass),
bypass: Boolean(view.autoApproveTools ?? view.bypass),
desktopLanguage: normalizeLangPref(view.desktopLanguage),
- desktopLayoutStyle: normalizeDesktopLayoutStyle(view.desktopLayoutStyle),
desktopTheme: normalizeThemePreference(view.desktopTheme),
desktopThemeStyle: normalizeThemeStyleForTheme(view.desktopThemeStyle, normalizeThemePreference(view.desktopTheme)),
closeBehavior: normalizeCloseBehavior(view.closeBehavior),
@@ -687,16 +849,6 @@ function normalizeDisplayMode(mode: string | undefined): DisplayMode {
return mode === "standard" || mode === "compact" ? mode : "standard";
}
-type DesktopLayoutStyle = "classic" | "workbench";
-
-function normalizeDesktopLayoutStyle(style: string | undefined): DesktopLayoutStyle {
- return style === "workbench" ? "workbench" : "classic";
-}
-
-function desktopLayoutStyleLabel(style: DesktopLayoutStyle, t: ReturnType): string {
- return t(`settings.desktopLayoutStyle.${style}`);
-}
-
type StatusBarStyle = "icon" | "text";
type StatusBarDropPlacement = "before" | "after";
type StatusBarDragTarget = {
@@ -783,7 +935,6 @@ function GeneralSection({ s, busy, apply, agentRunning }: SectionProps & { agent
useEffect(() => () => mouseDragCleanupRef.current?.(), []);
const autoPlan = normalizeAutoPlan(s.autoPlan);
const languagePref = normalizeLangPref(s.desktopLanguage);
- const desktopLayoutStyle = normalizeDesktopLayoutStyle(s.desktopLayoutStyle);
const [genMusicPreset, setGenMusicPreset] = useState(getGenerativePreset());
const [soundPref, setSoundPref] = useState(getSuccessPreference());
const [attentionPref, setAttentionPref] = useState(getAttentionPreference());
@@ -954,20 +1105,6 @@ function GeneralSection({ s, busy, apply, agentRunning }: SectionProps & { agent
))}