Skip to content
Merged
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
16 changes: 13 additions & 3 deletions chat2db-community-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,12 @@
"test:sql-execution-request-tracker": "tsx src/service/sqlExecutionRequestTracker.test.ts",
"test:sql-execution-stream": "tsx src/service/sqlExecutionStream.test.ts",
"test:local-file-tab-title": "tsx src/pages/main/workspace/utils/localTextFile.test.ts",
"test:local-file-tab-refresh": "tsx src/store/workspace/utils/localFileWorkspaceTab.test.ts",
"test:sql-completion-context": "tsx src/components/SQLEditor/core/sqlCompletionContext.test.ts",
"test:sql-in-clipboard": "tsx src/utils/sqlInClipboard.test.ts && tsx src/components/SQLEditor/helper/sqlInsertValueDefaults.test.ts",
"test:tree-title-highlight": "tsx src/blocks/NewTree/components/TitleRender/highlightSearchText.test.ts",
"test:terminal": "tsx src/constants/terminal.test.ts && tsx src/pages/main/workspace/components/WorkspaceExtend/WorkspaceExtendNav/quickTerminal.test.ts && tsx src/pages/main/workspace/components/WorkspaceTabs/terminalTabPlacement.test.ts",
"test:workspace-tab-drag": "tsx src/pages/main/workspace/components/WorkspaceTabs/workspaceTabDrop.test.ts",
"test:verification-code-countdown": "tsx src/utils/verificationCodeCountdown.test.ts && tsx src/utils/latestRequest.test.ts",
"modify:package:name": "node ./scripts/modify-package-name.js",
"start": "APP_VERSION=${npm_config_app_version} UMI_DEV_SERVER_COMPRESS=none umi dev",
Expand All @@ -80,6 +84,9 @@
"@visactor/vtable-editors": "^1.10.1",
"@visactor/vtable-search": "^1.13.0",
"@xyflow/react": "^12.4.2",
"@xterm/addon-fit": "^0.11.0",
"@xterm/addon-serialize": "^0.14.0",
"@xterm/xterm": "^6.0.0",
"ahooks": "^3.7.8",
"ali-oss": "^6.20.0",
"ali-react-table": "^2.6.1",
Expand All @@ -99,10 +106,12 @@
"html2canvas": "^1.4.1",
"idb-keyval": "^6.2.1",
"is-online": "^11.0.0",
"katex": "^0.16.21",
"localforage": "^1.10.0",
"lodash": "^4.17.21",
"lottie-react": "^2.4.0",
"lucide-react": "^0.356.0",
"mermaid": "^11.12.0",
"monaco-editor": "^0.52.0",
"node-schedule": "^2.1.1",
"pako": "^2.1.0",
Expand All @@ -112,16 +121,17 @@
"react-infinite-scroll-component": "^6.1.0",
"react-mentions": "^4.4.10",
"react-resizable": "^3.0.5",
"react-markdown": "^9.0.3",
"react-sortablejs": "^6.1.4",
"react-split-pane": "^0.1.92",
"rehype-katex": "^7.0.1",
"remark-gfm": "^4.0.1",
"remark-math": "^6.0.0",
"umi": "^4.0.87",
"umi-request": "^1.4.0",
"url-parse": "^1.5.10",
"utility-types": "^3.11.0",
"uuid": "^9.0.0",
"xterm": "^5.3.0",
"xterm-addon-fit": "^0.8.0",
"xterm-addon-web-links": "^0.9.0",
"zod": "^3.24.2",
"zustand": "^4.4.4"
},
Expand Down
8 changes: 4 additions & 4 deletions chat2db-community-client/scripts/i18n-source-hashes.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@
"plugin.ts": "01f15b6342cc5b29899ee4144d67b04bb4bc02c6bb10779806be322dc5e9a2bc",
"price.ts": "26e35cb96368585f83a29adbb14e300da9b7f5278a4320063e81a1d1dd0adee9",
"redis.ts": "a73e4fca6d783031370060e20bb1bbba13f663a0773cd031e1744be1cb4e93af",
"setting.ts": "b4a74c0ac5970c355247155f2d61eb609f6c473038e8eced4ceac9124e5e7688",
"setting.ts": "9728c7f38cd3a20fda257a304366cc9493e490c6e941fe455f1b3d11522ef3ca",
"spaceSetting.ts": "defdf3138e5e1782f0282760bcef4ce1b8550cc80b03052fea468d97165cb980",
"sqlEditor.ts": "5c41e5f24d5ba78909795bcc681cabb8460cca075233b665dc4940afe82ce0c9",
"stream.ts": "8418dd25b0fd54b2ef2b15a2573edf0a4876efb4e69936ff82a157ea5cf42be4",
"team.ts": "eb1fb03bdfeb93169ef1d2e347deb5b95071c10c8c93c35cfe2cb5b6606779ea",
"userGuide.ts": "0103ba35af95a3a0ff7a414130b68a7dd5f5f8196e59cd951631ac2090cb04fe",
"workspace.ts": "df144283a0a2d8426f8c465d90a27ecd4b6194e01cee74a427a1e9aa983cc7ca"
"workspace.ts": "fd9c276d878e08b73fce53188b0a190c01b2367bac43fd43812a5085554a545b"
},
"ko-KR": {
"ai.ts": "391a6e14c58d8401bf3bc245eca5bdd4b66efd8225a877bfbbcae01a1c1c019a",
Expand All @@ -48,13 +48,13 @@
"plugin.ts": "01f15b6342cc5b29899ee4144d67b04bb4bc02c6bb10779806be322dc5e9a2bc",
"price.ts": "26e35cb96368585f83a29adbb14e300da9b7f5278a4320063e81a1d1dd0adee9",
"redis.ts": "a73e4fca6d783031370060e20bb1bbba13f663a0773cd031e1744be1cb4e93af",
"setting.ts": "b4a74c0ac5970c355247155f2d61eb609f6c473038e8eced4ceac9124e5e7688",
"setting.ts": "9728c7f38cd3a20fda257a304366cc9493e490c6e941fe455f1b3d11522ef3ca",
"spaceSetting.ts": "defdf3138e5e1782f0282760bcef4ce1b8550cc80b03052fea468d97165cb980",
"sqlEditor.ts": "5c41e5f24d5ba78909795bcc681cabb8460cca075233b665dc4940afe82ce0c9",
"stream.ts": "8418dd25b0fd54b2ef2b15a2573edf0a4876efb4e69936ff82a157ea5cf42be4",
"team.ts": "eb1fb03bdfeb93169ef1d2e347deb5b95071c10c8c93c35cfe2cb5b6606779ea",
"userGuide.ts": "0103ba35af95a3a0ff7a414130b68a7dd5f5f8196e59cd951631ac2090cb04fe",
"workspace.ts": "df144283a0a2d8426f8c465d90a27ecd4b6194e01cee74a427a1e9aa983cc7ca"
"workspace.ts": "fd9c276d878e08b73fce53188b0a190c01b2367bac43fd43812a5085554a545b"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import i18n from '@/i18n';
import { ClipboardPen, Info, Keyboard, SlidersHorizontal } from 'lucide-react';
import { ClipboardPen, Info, Keyboard, SlidersHorizontal, Terminal } from 'lucide-react';
import { useEffect, useMemo } from 'react';
import About from './About';
import BaseSetting from './BaseSetting';
Expand All @@ -8,9 +8,11 @@ import McpSetting from './McpSetting';
import NetworkProxySetting from './NetworkProxySetting';
import SettingLayout, { type SettingMenuItem } from './SettingLayout';
import ShortcutSetting from './ShortcutSetting';
import TerminalSetting from './TerminalSetting';

import { runtimeEditionConfig } from '@/constants/runtimeEdition';
import { useGlobalStore } from '@/store/global';
import { isDesktop } from '@/utils/env';

function CommunitySetting() {
const {
Expand Down Expand Up @@ -42,6 +44,18 @@ function CommunitySetting() {
body: <EditorSetting />,
code: 'editSetting',
},
...(isDesktop
? [
{
title: i18n('setting.nav.terminal'),
describe: i18n('setting.nav.terminalDescribe'),
group: 'general' as const,
icon: Terminal,
body: <TerminalSetting />,
code: 'terminal',
},
]
: []),
...(runtimeEditionConfig.mcpSetting
? [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
import { DEFAULT_TERMINAL_SETTINGS, TERMINAL_THEMES } from '@/constants/terminal';
import i18n from '@/i18n';
import jcefApi from '@/jcef';
import { useGlobalStore } from '@/store/global';
import type { TerminalOpenPosition, TerminalShellId, TerminalThemeId } from '@/typings/settings';
import { Alert, Segmented, Select, Spin, Switch } from 'antd';
import { Check, ChevronDown, Palette, PanelBottom, PanelRight, PanelTop, ShieldCheck, Terminal } from 'lucide-react';
import { useEffect, useState } from 'react';
import { useStyles } from './style';

interface ShellOption {
id: TerminalShellId;
label: string;
available: boolean;
}

const themeOptions = Object.values(TERMINAL_THEMES);

export default function TerminalSetting() {
const { styles, cx } = useStyles();
const { terminalSettings, updateTerminalSettings } = useGlobalStore((state) => ({
terminalSettings: {
...DEFAULT_TERMINAL_SETTINGS,
...state.terminalSettings,
},
updateTerminalSettings: state.updateTerminalSettings,
}));
const [shells, setShells] = useState<ShellOption[]>([]);
const [loading, setLoading] = useState(true);
const [loadFailed, setLoadFailed] = useState(false);

useEffect(() => {
let disposed = false;
jcefApi
.getTerminalCapabilities()
.then((capabilities) => {
if (disposed) {
return;
}
const availableShells = capabilities.shells.filter((shell) => shell.available) as ShellOption[];
setShells(availableShells);
if (!availableShells.some((shell) => shell.id === terminalSettings.shellId)) {
updateTerminalSettings({ shellId: 'system' });
}
})
.catch((error) => {
console.error('get terminal capabilities error', error);
if (!disposed) {
setLoadFailed(true);
}
})
.finally(() => {
if (!disposed) {
setLoading(false);
}
});
return () => {
disposed = true;
};
}, [terminalSettings.shellId, updateTerminalSettings]);

return (
<div className={styles.settingsList}>
<section className={styles.settingRow} data-setting-group="position" data-setting-search-id="terminal.position">
<div className={styles.settingMeta}>
<PanelTop aria-hidden="true" className={styles.settingGroupIcon} size={18} strokeWidth={1.8} />
<div className={styles.settingMetaContent}>
<div className={styles.settingTitle} data-setting-search-title="true">
{i18n('setting.terminal.openPosition')}
</div>
<div className={styles.settingDescription}>{i18n('setting.terminal.openPositionDescribe')}</div>
</div>
</div>
<div className={styles.settingControl}>
<Segmented
block
aria-label={i18n('setting.terminal.openPosition')}
className={styles.positionSegmented}
value={terminalSettings.openPosition}
options={[
{
value: 'tab',
label: (
<span className={styles.positionOption}>
<PanelTop aria-hidden="true" size={15} />
{i18n('setting.terminal.openPositionTab')}
</span>
),
},
{
value: 'bottom',
label: (
<span className={styles.positionOption}>
<PanelBottom aria-hidden="true" size={15} />
{i18n('setting.terminal.openPositionBottom')}
</span>
),
},
{
value: 'right',
label: (
<span className={styles.positionOption}>
<PanelRight aria-hidden="true" size={15} />
{i18n('setting.terminal.openPositionRight')}
</span>
),
},
]}
onChange={(openPosition) =>
updateTerminalSettings({ openPosition: openPosition as TerminalOpenPosition })
}
/>
<div className={styles.hint}>{i18n('setting.terminal.openPositionApplyHint')}</div>
</div>
</section>

<section
className={styles.settingRow}
data-setting-group="close-confirmation"
data-setting-search-id="terminal.confirmBeforeClose"
>
<div className={styles.settingMeta}>
<ShieldCheck aria-hidden="true" className={styles.settingGroupIcon} size={18} strokeWidth={1.8} />
<div className={styles.settingMetaContent}>
<div className={styles.settingTitle} data-setting-search-title="true">
{i18n('setting.terminal.confirmBeforeClose')}
</div>
<div className={styles.settingDescription}>
{i18n('setting.terminal.confirmBeforeCloseDescribe')}
</div>
</div>
</div>
<div className={styles.switchControl}>
<Switch
aria-label={i18n('setting.terminal.confirmBeforeClose')}
checked={terminalSettings.confirmBeforeClose}
onChange={(confirmBeforeClose) => updateTerminalSettings({ confirmBeforeClose })}
/>
</div>
</section>

<section className={styles.settingRow} data-setting-group="shell" data-setting-search-id="terminal.shell">
<div className={styles.settingMeta}>
<Terminal aria-hidden="true" className={styles.settingGroupIcon} size={18} strokeWidth={1.8} />
<div className={styles.settingMetaContent}>
<div className={styles.settingTitle} data-setting-search-title="true">
{i18n('setting.terminal.shell')}
</div>
<div className={styles.settingDescription}>{i18n('setting.terminal.shellDescribe')}</div>
</div>
</div>
<div className={styles.settingControl}>
{loading ? (
<div className={styles.controlState}>
<Spin size="small" />
</div>
) : loadFailed ? (
<Alert
className={styles.capabilitiesAlert}
type="warning"
showIcon
message={i18n('setting.terminal.capabilitiesFailed')}
/>
) : (
<Select
aria-label={i18n('setting.terminal.shell')}
value={terminalSettings.shellId}
className={styles.shellSelect}
suffixIcon={<ChevronDown size={14} />}
options={shells.map((shell) => ({ value: shell.id, label: shell.label }))}
onChange={(shellId: TerminalShellId) => updateTerminalSettings({ shellId })}
/>
)}
<div className={styles.hint}>{i18n('setting.terminal.shellApplyHint')}</div>
</div>
</section>

<section className={styles.settingRow} data-setting-group="theme" data-setting-search-id="terminal.theme">
<div className={styles.settingMeta}>
<Palette aria-hidden="true" className={styles.settingGroupIcon} size={18} strokeWidth={1.8} />
<div className={styles.settingMetaContent}>
<div className={styles.settingTitle} data-setting-search-title="true">
{i18n('setting.terminal.theme')}
</div>
<div className={styles.settingDescription}>{i18n('setting.terminal.themeDescribe')}</div>
</div>
</div>
<div className={styles.settingControl}>
<div aria-label={i18n('setting.terminal.theme')} className={styles.themeGrid} role="group">
{themeOptions.map((config) => {
const isActive = terminalSettings.themeId === config.id;
const colors = [
config.theme.red,
config.theme.yellow,
config.theme.green,
config.theme.cyan,
config.theme.blue,
config.theme.magenta,
];
return (
<button
aria-pressed={isActive}
key={config.id}
type="button"
className={cx(styles.themeOption, {
[styles.activeThemeOption]: isActive,
})}
onClick={() => updateTerminalSettings({ themeId: config.id as TerminalThemeId })}
>
<span
className={cx(styles.themePreview, {
[styles.activeThemePreview]: isActive,
})}
style={{
backgroundColor: config.theme.background,
color: config.theme.foreground,
}}
>
{isActive ? (
<span className={styles.themeCheck}>
<Check aria-hidden="true" size={13} strokeWidth={2.2} />
</span>
) : null}
<span className={styles.colorRow}>
{colors.map((color, index) => (
<span key={`${config.id}-${index}`} style={{ backgroundColor: color }} />
))}
</span>
<span className={styles.commandPreview}>
<span style={{ color: config.theme.green }}>$</span> npm run dev
</span>
</span>
<span className={styles.themeName}>{config.name}</span>
</button>
);
})}
</div>
<div className={styles.hint}>{i18n('setting.terminal.themeApplyHint')}</div>
</div>
</section>
</div>
);
}
Loading
Loading