✨ add gitmoji support for commit messages (#165)
Add gitmoji toggle in Git settings to enable emoji support Show Add gitmoji button in commit section when feature is enabled Display gitmoji picker dialog with searchable emoji list
This commit is contained in:
@@ -275,6 +275,9 @@ fn sanitize_settings_update(payload: &Value) -> Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Boolean fields
|
// Boolean fields
|
||||||
|
if let Some(Value::Bool(b)) = obj.get("gitmojiEnabled") {
|
||||||
|
result_obj.insert("gitmojiEnabled".to_string(), json!(b));
|
||||||
|
}
|
||||||
if let Some(Value::Bool(b)) = obj.get("useSystemTheme") {
|
if let Some(Value::Bool(b)) = obj.get("useSystemTheme") {
|
||||||
result_obj.insert("useSystemTheme".to_string(), json!(b));
|
result_obj.insert("useSystemTheme".to_string(), json!(b));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ const getDisplayModel = (
|
|||||||
export const GitSettings: React.FC = () => {
|
export const GitSettings: React.FC = () => {
|
||||||
const settingsCommitMessageModel = useConfigStore((state) => state.settingsCommitMessageModel);
|
const settingsCommitMessageModel = useConfigStore((state) => state.settingsCommitMessageModel);
|
||||||
const setSettingsCommitMessageModel = useConfigStore((state) => state.setSettingsCommitMessageModel);
|
const setSettingsCommitMessageModel = useConfigStore((state) => state.setSettingsCommitMessageModel);
|
||||||
|
const settingsGitmojiEnabled = useConfigStore((state) => state.settingsGitmojiEnabled);
|
||||||
|
const setSettingsGitmojiEnabled = useConfigStore((state) => state.setSettingsGitmojiEnabled);
|
||||||
const providers = useConfigStore((state) => state.providers);
|
const providers = useConfigStore((state) => state.providers);
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = React.useState(true);
|
const [isLoading, setIsLoading] = React.useState(true);
|
||||||
@@ -56,7 +58,7 @@ export const GitSettings: React.FC = () => {
|
|||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const loadSettings = async () => {
|
const loadSettings = async () => {
|
||||||
try {
|
try {
|
||||||
let data: { commitMessageModel?: string } | null = null;
|
let data: { commitMessageModel?: string; gitmojiEnabled?: boolean } | null = null;
|
||||||
|
|
||||||
// 1. Desktop runtime (Tauri)
|
// 1. Desktop runtime (Tauri)
|
||||||
if (isDesktopRuntime()) {
|
if (isDesktopRuntime()) {
|
||||||
@@ -71,6 +73,9 @@ export const GitSettings: React.FC = () => {
|
|||||||
if (settings) {
|
if (settings) {
|
||||||
data = {
|
data = {
|
||||||
commitMessageModel: typeof settings.commitMessageModel === 'string' ? settings.commitMessageModel : undefined,
|
commitMessageModel: typeof settings.commitMessageModel === 'string' ? settings.commitMessageModel : undefined,
|
||||||
|
gitmojiEnabled: typeof (settings as Record<string, unknown>).gitmojiEnabled === 'boolean'
|
||||||
|
? ((settings as Record<string, unknown>).gitmojiEnabled as boolean)
|
||||||
|
: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@@ -90,12 +95,16 @@ export const GitSettings: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
const model = typeof data.commitMessageModel === 'string' && data.commitMessageModel.trim().length > 0
|
const model = typeof data.commitMessageModel === 'string' && data.commitMessageModel.trim().length > 0
|
||||||
? data.commitMessageModel.trim()
|
? data.commitMessageModel.trim()
|
||||||
: undefined;
|
: undefined;
|
||||||
setSettingsCommitMessageModel(model);
|
setSettingsCommitMessageModel(model);
|
||||||
}
|
if (typeof data.gitmojiEnabled === 'boolean') {
|
||||||
|
setSettingsGitmojiEnabled(data.gitmojiEnabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Failed to load git settings:', error);
|
console.warn('Failed to load git settings:', error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -103,7 +112,7 @@ export const GitSettings: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
loadSettings();
|
loadSettings();
|
||||||
}, [setSettingsCommitMessageModel]);
|
}, [setSettingsCommitMessageModel, setSettingsGitmojiEnabled]);
|
||||||
|
|
||||||
const handleModelChange = React.useCallback(async (providerId: string, modelId: string) => {
|
const handleModelChange = React.useCallback(async (providerId: string, modelId: string) => {
|
||||||
const newValue = providerId && modelId ? `${providerId}/${modelId}` : undefined;
|
const newValue = providerId && modelId ? `${providerId}/${modelId}` : undefined;
|
||||||
@@ -118,6 +127,18 @@ export const GitSettings: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, [setSettingsCommitMessageModel]);
|
}, [setSettingsCommitMessageModel]);
|
||||||
|
|
||||||
|
const handleGitmojiChange = React.useCallback(async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const enabled = event.target.checked;
|
||||||
|
setSettingsGitmojiEnabled(enabled);
|
||||||
|
try {
|
||||||
|
await updateDesktopSettings({
|
||||||
|
gitmojiEnabled: enabled,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to save gitmoji setting:', error);
|
||||||
|
}
|
||||||
|
}, [setSettingsGitmojiEnabled]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -139,8 +160,8 @@ export const GitSettings: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="flex flex-col gap-1.5">
|
<fieldset className="flex flex-col gap-1.5">
|
||||||
<label className="typography-ui-label text-muted-foreground">Model for generation</label>
|
<legend className="typography-ui-label text-muted-foreground">Model for generation</legend>
|
||||||
<ModelSelector
|
<ModelSelector
|
||||||
providerId={parsedModel.providerId}
|
providerId={parsedModel.providerId}
|
||||||
modelId={parsedModel.modelId}
|
modelId={parsedModel.modelId}
|
||||||
@@ -151,6 +172,21 @@ export const GitSettings: React.FC = () => {
|
|||||||
This model will be used to analyze diffs and suggest commit messages.
|
This model will be used to analyze diffs and suggest commit messages.
|
||||||
{!settingsCommitMessageModel && <> Default: <span className="text-foreground">opencode/big-pickle</span></>}
|
{!settingsCommitMessageModel && <> Default: <span className="text-foreground">opencode/big-pickle</span></>}
|
||||||
</p>
|
</p>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<label className="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="h-3.5 w-3.5 accent-primary"
|
||||||
|
checked={settingsGitmojiEnabled}
|
||||||
|
onChange={handleGitmojiChange}
|
||||||
|
/>
|
||||||
|
<span className="typography-ui-label text-foreground">Enable gitmoji picker</span>
|
||||||
|
</label>
|
||||||
|
<p className="typography-meta text-muted-foreground pl-5.5">
|
||||||
|
Adds a gitmoji selector to the Git commit message input.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSessionStore } from '@/stores/useSessionStore';
|
import { useSessionStore } from '@/stores/useSessionStore';
|
||||||
|
import { useConfigStore } from '@/stores/useConfigStore';
|
||||||
import { useFireworksCelebration } from '@/contexts/FireworksContext';
|
import { useFireworksCelebration } from '@/contexts/FireworksContext';
|
||||||
import type { GitIdentityProfile, CommitFileEntry } from '@/lib/api/types';
|
import type { GitIdentityProfile, CommitFileEntry } from '@/lib/api/types';
|
||||||
import { useGitIdentitiesStore } from '@/stores/useGitIdentitiesStore';
|
import { useGitIdentitiesStore } from '@/stores/useGitIdentitiesStore';
|
||||||
@@ -15,6 +16,20 @@ import {
|
|||||||
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay';
|
||||||
import { RiGitBranchLine, RiLoader4Line } from '@remixicon/react';
|
import { RiGitBranchLine, RiLoader4Line } from '@remixicon/react';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import {
|
||||||
|
Command,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandInput,
|
||||||
|
CommandItem,
|
||||||
|
CommandList,
|
||||||
|
} from '@/components/ui/command';
|
||||||
import type { Session } from '@opencode-ai/sdk/v2';
|
import type { Session } from '@opencode-ai/sdk/v2';
|
||||||
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
|
||||||
import { useUIStore } from '@/stores/useUIStore';
|
import { useUIStore } from '@/stores/useUIStore';
|
||||||
@@ -86,9 +101,12 @@ export const GitView: React.FC = () => {
|
|||||||
return gitViewSnapshot;
|
return gitViewSnapshot;
|
||||||
}, [currentDirectory]);
|
}, [currentDirectory]);
|
||||||
|
|
||||||
|
const settingsGitmojiEnabled = useConfigStore((state) => state.settingsGitmojiEnabled);
|
||||||
|
|
||||||
const [commitMessage, setCommitMessage] = React.useState(
|
const [commitMessage, setCommitMessage] = React.useState(
|
||||||
initialSnapshot?.commitMessage ?? ''
|
initialSnapshot?.commitMessage ?? ''
|
||||||
);
|
);
|
||||||
|
const [isGitmojiPickerOpen, setIsGitmojiPickerOpen] = React.useState(false);
|
||||||
const [syncAction, setSyncAction] = React.useState<SyncAction>(null);
|
const [syncAction, setSyncAction] = React.useState<SyncAction>(null);
|
||||||
const [commitAction, setCommitAction] = React.useState<CommitAction>(null);
|
const [commitAction, setCommitAction] = React.useState<CommitAction>(null);
|
||||||
const [logMaxCountLocal, setLogMaxCountLocal] = React.useState<number>(25);
|
const [logMaxCountLocal, setLogMaxCountLocal] = React.useState<number>(25);
|
||||||
@@ -124,6 +142,8 @@ export const GitView: React.FC = () => {
|
|||||||
const [commitFilesMap, setCommitFilesMap] = React.useState<Map<string, CommitFileEntry[]>>(new Map());
|
const [commitFilesMap, setCommitFilesMap] = React.useState<Map<string, CommitFileEntry[]>>(new Map());
|
||||||
const [loadingCommitHashes, setLoadingCommitHashes] = React.useState<Set<string>>(new Set());
|
const [loadingCommitHashes, setLoadingCommitHashes] = React.useState<Set<string>>(new Set());
|
||||||
const [remoteUrl, setRemoteUrl] = React.useState<string | null>(null);
|
const [remoteUrl, setRemoteUrl] = React.useState<string | null>(null);
|
||||||
|
const [gitmojiEmojis, setGitmojiEmojis] = React.useState<Array<{ emoji: string; code: string; description: string }>>([]);
|
||||||
|
const [gitmojiSearch, setGitmojiSearch] = React.useState('');
|
||||||
|
|
||||||
const handleCopyCommitHash = React.useCallback((hash: string) => {
|
const handleCopyCommitHash = React.useCallback((hash: string) => {
|
||||||
navigator.clipboard
|
navigator.clipboard
|
||||||
@@ -160,11 +180,13 @@ export const GitView: React.FC = () => {
|
|||||||
|
|
||||||
setLoadingCommitHashes((prev) => {
|
setLoadingCommitHashes((prev) => {
|
||||||
const next = new Set(prev);
|
const next = new Set(prev);
|
||||||
hashesToLoad.forEach((h) => next.add(h));
|
for (const hash of hashesToLoad) {
|
||||||
|
next.add(hash);
|
||||||
|
}
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
|
|
||||||
hashesToLoad.forEach((hash) => {
|
for (const hash of hashesToLoad) {
|
||||||
git
|
git
|
||||||
.getCommitFiles(currentDirectory, hash)
|
.getCommitFiles(currentDirectory, hash)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
@@ -181,7 +203,7 @@ export const GitView: React.FC = () => {
|
|||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
}, [expandedCommitHashes, currentDirectory, git, commitFilesMap, loadingCommitHashes]);
|
}, [expandedCommitHashes, currentDirectory, git, commitFilesMap, loadingCommitHashes]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -213,6 +235,38 @@ export const GitView: React.FC = () => {
|
|||||||
git.getRemoteUrl(currentDirectory).then(setRemoteUrl).catch(() => setRemoteUrl(null));
|
git.getRemoteUrl(currentDirectory).then(setRemoteUrl).catch(() => setRemoteUrl(null));
|
||||||
}, [currentDirectory, git]);
|
}, [currentDirectory, git]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!settingsGitmojiEnabled) {
|
||||||
|
setGitmojiEmojis([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cancelled = false;
|
||||||
|
|
||||||
|
const loadGitmojis = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('https://raw.githubusercontent.com/carloscuesta/gitmoji/master/packages/gitmojis/src/gitmojis.json');
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to load gitmojis: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
const payload = (await response.json()) as { gitmojis?: Array<{ emoji: string; code: string; description: string }> };
|
||||||
|
if (!cancelled) {
|
||||||
|
setGitmojiEmojis(payload.gitmojis ?? []);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (!cancelled) {
|
||||||
|
console.warn('Failed to load gitmoji list:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadGitmojis();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, [settingsGitmojiEnabled]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (currentDirectory) {
|
if (currentDirectory) {
|
||||||
setActiveDirectory(currentDirectory);
|
setActiveDirectory(currentDirectory);
|
||||||
@@ -269,9 +323,9 @@ export const GitView: React.FC = () => {
|
|||||||
|
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
try {
|
try {
|
||||||
const hasLocal = await git.hasLocalIdentity!(currentDirectory);
|
const hasLocal = await git.hasLocalIdentity?.(currentDirectory);
|
||||||
if (cancelled) return;
|
if (cancelled) return;
|
||||||
if (hasLocal) return;
|
if (hasLocal === true) return;
|
||||||
|
|
||||||
beginIdentityApply();
|
beginIdentityApply();
|
||||||
await git.setGitIdentity(currentDirectory, defaultId);
|
await git.setGitIdentity(currentDirectory, defaultId);
|
||||||
@@ -293,18 +347,19 @@ export const GitView: React.FC = () => {
|
|||||||
};
|
};
|
||||||
}, [beginIdentityApply, currentDirectory, defaultGitIdentityId, endIdentityApply, git, isGitRepo, refreshIdentity]);
|
}, [beginIdentityApply, currentDirectory, defaultGitIdentityId, endIdentityApply, git, isGitRepo, refreshIdentity]);
|
||||||
|
|
||||||
const changeEntries = React.useMemo(() => {
|
const changeEntries = React.useMemo(() => {
|
||||||
if (!status) return [];
|
if (!status) return [];
|
||||||
const files = status.files ?? [];
|
const files = status.files ?? [];
|
||||||
const unique = new Map<string, (typeof files)[number]>();
|
const unique = new Map<string, (typeof files)[number]>();
|
||||||
|
|
||||||
files.forEach((file) => {
|
for (const file of files) {
|
||||||
unique.set(file.path, file);
|
unique.set(file.path, file);
|
||||||
});
|
}
|
||||||
|
|
||||||
return Array.from(unique.values()).sort((a, b) => a.path.localeCompare(b.path));
|
return Array.from(unique.values()).sort((a, b) => a.path.localeCompare(b.path));
|
||||||
}, [status]);
|
}, [status]);
|
||||||
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!status || changeEntries.length === 0) {
|
if (!status || changeEntries.length === 0) {
|
||||||
setSelectedPaths(new Set());
|
setSelectedPaths(new Set());
|
||||||
@@ -316,13 +371,13 @@ export const GitView: React.FC = () => {
|
|||||||
const next = new Set<string>();
|
const next = new Set<string>();
|
||||||
const previousSet = previous ?? new Set<string>();
|
const previousSet = previous ?? new Set<string>();
|
||||||
|
|
||||||
changeEntries.forEach((file) => {
|
for (const file of changeEntries) {
|
||||||
if (previousSet.has(file.path)) {
|
if (previousSet.has(file.path)) {
|
||||||
next.add(file.path);
|
next.add(file.path);
|
||||||
} else if (!hasUserAdjustedSelection) {
|
} else if (!hasUserAdjustedSelection) {
|
||||||
next.add(file.path);
|
next.add(file.path);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
@@ -565,7 +620,7 @@ export const GitView: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
let normalized = remoteUrl.trim();
|
let normalized = remoteUrl.trim();
|
||||||
if (normalized.startsWith('git@')) {
|
if (normalized.startsWith('git@')) {
|
||||||
normalized = 'https://' + normalized.slice(4).replace(':', '/');
|
normalized = `https://${normalized.slice(4).replace(':', '/')}`;
|
||||||
}
|
}
|
||||||
if (normalized.endsWith('.git')) {
|
if (normalized.endsWith('.git')) {
|
||||||
normalized = normalized.slice(0, -4);
|
normalized = normalized.slice(0, -4);
|
||||||
@@ -706,6 +761,20 @@ export const GitView: React.FC = () => {
|
|||||||
});
|
});
|
||||||
}, [generatedHighlights, clearGeneratedHighlights]);
|
}, [generatedHighlights, clearGeneratedHighlights]);
|
||||||
|
|
||||||
|
const handleSelectGitmoji = React.useCallback((emoji: string, code: string) => {
|
||||||
|
const token = code || emoji;
|
||||||
|
setCommitMessage((current) => {
|
||||||
|
const trimmed = current.trimStart();
|
||||||
|
if (trimmed.startsWith(emoji) || (code && trimmed.startsWith(code))) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
const prefix = token.endsWith(' ') ? token : `${token} `;
|
||||||
|
return `${prefix}${current}`.trimStart();
|
||||||
|
});
|
||||||
|
setGitmojiSearch('');
|
||||||
|
setIsGitmojiPickerOpen(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleLogMaxCountChange = React.useCallback(
|
const handleLogMaxCountChange = React.useCallback(
|
||||||
(count: number) => {
|
(count: number) => {
|
||||||
setLogMaxCountLocal(count);
|
setLogMaxCountLocal(count);
|
||||||
@@ -773,64 +842,108 @@ export const GitView: React.FC = () => {
|
|||||||
isWorktreeMode={!!worktreeMetadata}
|
isWorktreeMode={!!worktreeMetadata}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ScrollableOverlay outerClassName="flex-1 min-h-0" className="p-3">
|
<ScrollableOverlay outerClassName="flex-1 min-h-0" className="p-3">
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
{/* Two-column layout on large screens: Changes + Commit */}
|
{/* Two-column layout on large screens: Changes + Commit */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-3">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-3">
|
||||||
{hasChanges ? (
|
{hasChanges ? (
|
||||||
<ChangesSection
|
<ChangesSection
|
||||||
changeEntries={changeEntries}
|
changeEntries={changeEntries}
|
||||||
selectedPaths={selectedPaths}
|
selectedPaths={selectedPaths}
|
||||||
diffStats={status?.diffStats}
|
diffStats={status?.diffStats}
|
||||||
revertingPaths={revertingPaths}
|
revertingPaths={revertingPaths}
|
||||||
onToggleFile={toggleFileSelection}
|
onToggleFile={toggleFileSelection}
|
||||||
onSelectAll={selectAll}
|
onSelectAll={selectAll}
|
||||||
onClearSelection={clearSelection}
|
onClearSelection={clearSelection}
|
||||||
onViewDiff={(path) => useUIStore.getState().navigateToDiff(path)}
|
onViewDiff={(path) => useUIStore.getState().navigateToDiff(path)}
|
||||||
onRevertFile={handleRevertFile}
|
onRevertFile={handleRevertFile}
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="lg:col-span-2 flex justify-center">
|
|
||||||
<GitEmptyState
|
|
||||||
behind={status?.behind ?? 0}
|
|
||||||
onPull={() => handleSyncAction('pull')}
|
|
||||||
isPulling={syncAction === 'pull'}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
) : (
|
||||||
)}
|
<div className="lg:col-span-2 flex justify-center">
|
||||||
|
<GitEmptyState
|
||||||
|
behind={status?.behind ?? 0}
|
||||||
|
onPull={() => handleSyncAction('pull')}
|
||||||
|
isPulling={syncAction === 'pull'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{changeEntries.length > 0 && (
|
{changeEntries.length > 0 && (
|
||||||
<CommitSection
|
<CommitSection
|
||||||
selectedCount={selectedCount}
|
selectedCount={selectedCount}
|
||||||
commitMessage={commitMessage}
|
commitMessage={commitMessage}
|
||||||
onCommitMessageChange={setCommitMessage}
|
onCommitMessageChange={setCommitMessage}
|
||||||
generatedHighlights={generatedHighlights}
|
generatedHighlights={generatedHighlights}
|
||||||
onInsertHighlights={handleInsertHighlights}
|
onInsertHighlights={handleInsertHighlights}
|
||||||
onClearHighlights={clearGeneratedHighlights}
|
onClearHighlights={clearGeneratedHighlights}
|
||||||
onGenerateMessage={handleGenerateCommitMessage}
|
onGenerateMessage={handleGenerateCommitMessage}
|
||||||
isGeneratingMessage={isGeneratingMessage}
|
isGeneratingMessage={isGeneratingMessage}
|
||||||
onCommit={() => handleCommit({ pushAfter: false })}
|
onCommit={() => handleCommit({ pushAfter: false })}
|
||||||
onCommitAndPush={() => handleCommit({ pushAfter: true })}
|
onCommitAndPush={() => handleCommit({ pushAfter: true })}
|
||||||
commitAction={commitAction}
|
commitAction={commitAction}
|
||||||
isBusy={isBusy}
|
isBusy={isBusy}
|
||||||
/>
|
gitmojiEnabled={settingsGitmojiEnabled}
|
||||||
)}
|
onOpenGitmojiPicker={() => setIsGitmojiPickerOpen(true)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* History below, constrained width */}
|
||||||
|
<HistorySection
|
||||||
|
log={log}
|
||||||
|
isLogLoading={isLogLoading}
|
||||||
|
logMaxCount={logMaxCountLocal}
|
||||||
|
onLogMaxCountChange={handleLogMaxCountChange}
|
||||||
|
expandedCommitHashes={expandedCommitHashes}
|
||||||
|
onToggleCommit={handleToggleCommit}
|
||||||
|
commitFilesMap={commitFilesMap}
|
||||||
|
loadingCommitHashes={loadingCommitHashes}
|
||||||
|
onCopyHash={handleCopyCommitHash}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</ScrollableOverlay>
|
||||||
|
|
||||||
|
<Dialog open={isGitmojiPickerOpen} onOpenChange={setIsGitmojiPickerOpen}>
|
||||||
|
<DialogContent className="max-w-md p-0 overflow-hidden">
|
||||||
|
<DialogHeader className="px-4 pt-4">
|
||||||
|
<DialogTitle>Pick a gitmoji</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<Command className="h-[420px]">
|
||||||
|
<CommandInput
|
||||||
|
placeholder="Search gitmojis..."
|
||||||
|
value={gitmojiSearch}
|
||||||
|
onValueChange={setGitmojiSearch}
|
||||||
|
/>
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>No gitmojis found.</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{(gitmojiEmojis.length === 0
|
||||||
|
? []
|
||||||
|
: gitmojiEmojis.filter((entry) => {
|
||||||
|
const term = gitmojiSearch.trim().toLowerCase();
|
||||||
|
if (!term) return true;
|
||||||
|
return (
|
||||||
|
entry.emoji.includes(term) ||
|
||||||
|
entry.code.toLowerCase().includes(term) ||
|
||||||
|
entry.description.toLowerCase().includes(term)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
).map((entry) => (
|
||||||
|
<CommandItem
|
||||||
|
key={entry.code}
|
||||||
|
onSelect={() => handleSelectGitmoji(entry.emoji, entry.code)}
|
||||||
|
>
|
||||||
|
<span className="text-lg">{entry.emoji}</span>
|
||||||
|
<span className="typography-ui-label text-foreground">{entry.code}</span>
|
||||||
|
<span className="typography-meta text-muted-foreground">{entry.description}</span>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
{/* History below, constrained width */}
|
|
||||||
<HistorySection
|
|
||||||
log={log}
|
|
||||||
isLogLoading={isLogLoading}
|
|
||||||
logMaxCount={logMaxCountLocal}
|
|
||||||
onLogMaxCountChange={handleLogMaxCountChange}
|
|
||||||
expandedCommitHashes={expandedCommitHashes}
|
|
||||||
onToggleCommit={handleToggleCommit}
|
|
||||||
commitFilesMap={commitFilesMap}
|
|
||||||
loadingCommitHashes={loadingCommitHashes}
|
|
||||||
onCopyHash={handleCopyCommitHash}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ScrollableOverlay>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
|
||||||
import {
|
import {
|
||||||
RiGitCommitLine,
|
RiGitCommitLine,
|
||||||
RiArrowUpLine,
|
RiArrowUpLine,
|
||||||
RiAiGenerate2,
|
RiAiGenerate2,
|
||||||
RiLoader4Line,
|
RiLoader4Line,
|
||||||
|
RiEmotionHappyLine,
|
||||||
} from '@remixicon/react';
|
} from '@remixicon/react';
|
||||||
import {
|
import {
|
||||||
Collapsible,
|
Collapsible,
|
||||||
@@ -30,6 +30,8 @@ interface CommitSectionProps {
|
|||||||
onCommitAndPush: () => void;
|
onCommitAndPush: () => void;
|
||||||
commitAction: CommitAction;
|
commitAction: CommitAction;
|
||||||
isBusy: boolean;
|
isBusy: boolean;
|
||||||
|
gitmojiEnabled: boolean;
|
||||||
|
onOpenGitmojiPicker: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CommitSection: React.FC<CommitSectionProps> = ({
|
export const CommitSection: React.FC<CommitSectionProps> = ({
|
||||||
@@ -45,6 +47,8 @@ export const CommitSection: React.FC<CommitSectionProps> = ({
|
|||||||
onCommitAndPush,
|
onCommitAndPush,
|
||||||
commitAction,
|
commitAction,
|
||||||
isBusy,
|
isBusy,
|
||||||
|
gitmojiEnabled,
|
||||||
|
onOpenGitmojiPicker,
|
||||||
}) => {
|
}) => {
|
||||||
const hasSelectedFiles = selectedCount > 0;
|
const hasSelectedFiles = selectedCount > 0;
|
||||||
const canCommit = commitMessage.trim() && hasSelectedFiles && commitAction === null;
|
const canCommit = commitMessage.trim() && hasSelectedFiles && commitAction === null;
|
||||||
@@ -79,6 +83,19 @@ export const CommitSection: React.FC<CommitSectionProps> = ({
|
|||||||
disabled={commitAction !== null}
|
disabled={commitAction !== null}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{gitmojiEnabled && (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={onOpenGitmojiPicker}
|
||||||
|
className="w-fit"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<RiEmotionHappyLine className="size-4" />
|
||||||
|
Add gitmoji
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Tooltip delayDuration={1000}>
|
<Tooltip delayDuration={1000}>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
|
|||||||
@@ -385,6 +385,7 @@ export interface SettingsPayload {
|
|||||||
autoDeleteEnabled?: boolean;
|
autoDeleteEnabled?: boolean;
|
||||||
autoDeleteAfterDays?: number;
|
autoDeleteAfterDays?: number;
|
||||||
queueModeEnabled?: boolean;
|
queueModeEnabled?: boolean;
|
||||||
|
gitmojiEnabled?: boolean;
|
||||||
|
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ export type DesktopSettings = {
|
|||||||
autoCreateWorktree?: boolean;
|
autoCreateWorktree?: boolean;
|
||||||
queueModeEnabled?: boolean;
|
queueModeEnabled?: boolean;
|
||||||
commitMessageModel?: string; // format: "provider/model"
|
commitMessageModel?: string; // format: "provider/model"
|
||||||
|
gitmojiEnabled?: boolean;
|
||||||
|
|
||||||
// User-added skills catalogs (persisted to ~/.config/openchamber/settings.json)
|
// User-added skills catalogs (persisted to ~/.config/openchamber/settings.json)
|
||||||
skillCatalogs?: SkillCatalogConfig[];
|
skillCatalogs?: SkillCatalogConfig[];
|
||||||
|
|||||||
@@ -52,11 +52,16 @@ const persistToLocalStorage = (settings: DesktopSettings) => {
|
|||||||
} else {
|
} else {
|
||||||
localStorage.removeItem('commitMessageModel');
|
localStorage.removeItem('commitMessageModel');
|
||||||
}
|
}
|
||||||
|
if (typeof settings.gitmojiEnabled === 'boolean') {
|
||||||
|
localStorage.setItem('gitmojiEnabled', String(settings.gitmojiEnabled));
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem('gitmojiEnabled');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
type PersistApi = {
|
type PersistApi = {
|
||||||
hasHydrated?: () => boolean;
|
hasHydrated?: () => boolean;
|
||||||
onFinishHydration?: (callback: () => void) => (() => void) | void;
|
onFinishHydration?: (callback: () => void) => (() => void) | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const sanitizeSkillCatalogs = (value: unknown): DesktopSettings['skillCatalogs'] | undefined => {
|
const sanitizeSkillCatalogs = (value: unknown): DesktopSettings['skillCatalogs'] | undefined => {
|
||||||
@@ -269,6 +274,9 @@ const sanitizeWebSettings = (payload: unknown): DesktopSettings | null => {
|
|||||||
if (typeof candidate.commitMessageModel === 'string' && candidate.commitMessageModel.length > 0) {
|
if (typeof candidate.commitMessageModel === 'string' && candidate.commitMessageModel.length > 0) {
|
||||||
result.commitMessageModel = candidate.commitMessageModel;
|
result.commitMessageModel = candidate.commitMessageModel;
|
||||||
}
|
}
|
||||||
|
if (typeof candidate.gitmojiEnabled === 'boolean') {
|
||||||
|
result.gitmojiEnabled = candidate.gitmojiEnabled;
|
||||||
|
}
|
||||||
if (typeof candidate.queueModeEnabled === 'boolean') {
|
if (typeof candidate.queueModeEnabled === 'boolean') {
|
||||||
result.queueModeEnabled = candidate.queueModeEnabled;
|
result.queueModeEnabled = candidate.queueModeEnabled;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ interface OpenChamberDefaults {
|
|||||||
defaultAgent?: string;
|
defaultAgent?: string;
|
||||||
autoCreateWorktree?: boolean;
|
autoCreateWorktree?: boolean;
|
||||||
commitMessageModel?: string;
|
commitMessageModel?: string;
|
||||||
|
gitmojiEnabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchOpenChamberDefaults = async (): Promise<OpenChamberDefaults> => {
|
const fetchOpenChamberDefaults = async (): Promise<OpenChamberDefaults> => {
|
||||||
@@ -39,6 +40,7 @@ const fetchOpenChamberDefaults = async (): Promise<OpenChamberDefaults> => {
|
|||||||
defaultAgent: settings?.defaultAgent,
|
defaultAgent: settings?.defaultAgent,
|
||||||
autoCreateWorktree: settings?.autoCreateWorktree,
|
autoCreateWorktree: settings?.autoCreateWorktree,
|
||||||
commitMessageModel: settings?.commitMessageModel,
|
commitMessageModel: settings?.commitMessageModel,
|
||||||
|
gitmojiEnabled: settings?.gitmojiEnabled,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,6 +55,7 @@ const fetchOpenChamberDefaults = async (): Promise<OpenChamberDefaults> => {
|
|||||||
const defaultVariant = typeof data?.defaultVariant === 'string' ? data.defaultVariant.trim() : '';
|
const defaultVariant = typeof data?.defaultVariant === 'string' ? data.defaultVariant.trim() : '';
|
||||||
const defaultAgent = typeof data?.defaultAgent === 'string' ? data.defaultAgent.trim() : '';
|
const defaultAgent = typeof data?.defaultAgent === 'string' ? data.defaultAgent.trim() : '';
|
||||||
const commitMessageModel = typeof data?.commitMessageModel === 'string' ? data.commitMessageModel.trim() : '';
|
const commitMessageModel = typeof data?.commitMessageModel === 'string' ? data.commitMessageModel.trim() : '';
|
||||||
|
const gitmojiEnabled = typeof data?.gitmojiEnabled === 'boolean' ? data.gitmojiEnabled : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
defaultModel: defaultModel.length > 0 ? defaultModel : undefined,
|
defaultModel: defaultModel.length > 0 ? defaultModel : undefined,
|
||||||
@@ -60,6 +63,7 @@ const fetchOpenChamberDefaults = async (): Promise<OpenChamberDefaults> => {
|
|||||||
defaultAgent: defaultAgent.length > 0 ? defaultAgent : undefined,
|
defaultAgent: defaultAgent.length > 0 ? defaultAgent : undefined,
|
||||||
autoCreateWorktree: typeof data?.autoCreateWorktree === 'boolean' ? data.autoCreateWorktree : undefined,
|
autoCreateWorktree: typeof data?.autoCreateWorktree === 'boolean' ? data.autoCreateWorktree : undefined,
|
||||||
commitMessageModel: commitMessageModel.length > 0 ? commitMessageModel : undefined,
|
commitMessageModel: commitMessageModel.length > 0 ? commitMessageModel : undefined,
|
||||||
|
gitmojiEnabled,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@@ -80,6 +84,7 @@ const fetchOpenChamberDefaults = async (): Promise<OpenChamberDefaults> => {
|
|||||||
const defaultVariant = typeof data?.defaultVariant === 'string' ? data.defaultVariant.trim() : '';
|
const defaultVariant = typeof data?.defaultVariant === 'string' ? data.defaultVariant.trim() : '';
|
||||||
const defaultAgent = typeof data?.defaultAgent === 'string' ? data.defaultAgent.trim() : '';
|
const defaultAgent = typeof data?.defaultAgent === 'string' ? data.defaultAgent.trim() : '';
|
||||||
const commitMessageModel = typeof data?.commitMessageModel === 'string' ? data.commitMessageModel.trim() : '';
|
const commitMessageModel = typeof data?.commitMessageModel === 'string' ? data.commitMessageModel.trim() : '';
|
||||||
|
const gitmojiEnabled = typeof data?.gitmojiEnabled === 'boolean' ? data.gitmojiEnabled : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
defaultModel: defaultModel.length > 0 ? defaultModel : undefined,
|
defaultModel: defaultModel.length > 0 ? defaultModel : undefined,
|
||||||
@@ -87,6 +92,7 @@ const fetchOpenChamberDefaults = async (): Promise<OpenChamberDefaults> => {
|
|||||||
defaultAgent: defaultAgent.length > 0 ? defaultAgent : undefined,
|
defaultAgent: defaultAgent.length > 0 ? defaultAgent : undefined,
|
||||||
autoCreateWorktree: typeof data?.autoCreateWorktree === 'boolean' ? data.autoCreateWorktree : undefined,
|
autoCreateWorktree: typeof data?.autoCreateWorktree === 'boolean' ? data.autoCreateWorktree : undefined,
|
||||||
commitMessageModel: commitMessageModel.length > 0 ? commitMessageModel : undefined,
|
commitMessageModel: commitMessageModel.length > 0 ? commitMessageModel : undefined,
|
||||||
|
gitmojiEnabled,
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return {};
|
return {};
|
||||||
@@ -379,6 +385,7 @@ interface ConfigStore {
|
|||||||
settingsDefaultAgent: string | undefined;
|
settingsDefaultAgent: string | undefined;
|
||||||
settingsAutoCreateWorktree: boolean;
|
settingsAutoCreateWorktree: boolean;
|
||||||
settingsCommitMessageModel: string | undefined; // format: "provider/model"
|
settingsCommitMessageModel: string | undefined; // format: "provider/model"
|
||||||
|
settingsGitmojiEnabled: boolean;
|
||||||
|
|
||||||
activateDirectory: (directory: string | null | undefined) => Promise<void>;
|
activateDirectory: (directory: string | null | undefined) => Promise<void>;
|
||||||
|
|
||||||
@@ -396,6 +403,7 @@ interface ConfigStore {
|
|||||||
setSettingsDefaultAgent: (agent: string | undefined) => void;
|
setSettingsDefaultAgent: (agent: string | undefined) => void;
|
||||||
setSettingsAutoCreateWorktree: (enabled: boolean) => void;
|
setSettingsAutoCreateWorktree: (enabled: boolean) => void;
|
||||||
setSettingsCommitMessageModel: (model: string | undefined) => void;
|
setSettingsCommitMessageModel: (model: string | undefined) => void;
|
||||||
|
setSettingsGitmojiEnabled: (enabled: boolean) => void;
|
||||||
saveAgentModelSelection: (agentName: string, providerId: string, modelId: string) => void;
|
saveAgentModelSelection: (agentName: string, providerId: string, modelId: string) => void;
|
||||||
getAgentModelSelection: (agentName: string) => { providerId: string; modelId: string } | null;
|
getAgentModelSelection: (agentName: string) => { providerId: string; modelId: string } | null;
|
||||||
checkConnection: () => Promise<boolean>;
|
checkConnection: () => Promise<boolean>;
|
||||||
@@ -440,6 +448,7 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
settingsDefaultAgent: undefined,
|
settingsDefaultAgent: undefined,
|
||||||
settingsAutoCreateWorktree: false,
|
settingsAutoCreateWorktree: false,
|
||||||
settingsCommitMessageModel: undefined,
|
settingsCommitMessageModel: undefined,
|
||||||
|
settingsGitmojiEnabled: false,
|
||||||
|
|
||||||
activateDirectory: async (directory) => {
|
activateDirectory: async (directory) => {
|
||||||
const directoryKey = toDirectoryKey(directory);
|
const directoryKey = toDirectoryKey(directory);
|
||||||
@@ -879,13 +888,13 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const nextState: Partial<ConfigStore> = {
|
const nextState: Partial<ConfigStore> = {
|
||||||
settingsDefaultModel: openChamberDefaults.defaultModel,
|
settingsDefaultModel: openChamberDefaults.defaultModel,
|
||||||
settingsDefaultVariant: openChamberDefaults.defaultVariant,
|
settingsDefaultVariant: openChamberDefaults.defaultVariant,
|
||||||
settingsDefaultAgent: openChamberDefaults.defaultAgent,
|
settingsDefaultAgent: openChamberDefaults.defaultAgent,
|
||||||
settingsAutoCreateWorktree: openChamberDefaults.autoCreateWorktree ?? false,
|
settingsAutoCreateWorktree: openChamberDefaults.autoCreateWorktree ?? false,
|
||||||
settingsCommitMessageModel: openChamberDefaults.commitMessageModel,
|
settingsCommitMessageModel: openChamberDefaults.commitMessageModel,
|
||||||
directoryScoped: {
|
settingsGitmojiEnabled: openChamberDefaults.gitmojiEnabled ?? false,
|
||||||
|
directoryScoped: {
|
||||||
...state.directoryScoped,
|
...state.directoryScoped,
|
||||||
[directoryKey]: nextSnapshot,
|
[directoryKey]: nextSnapshot,
|
||||||
},
|
},
|
||||||
@@ -996,9 +1005,10 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
|
|
||||||
// 2. Fall back to agent's preferred model
|
// 2. Fall back to agent's preferred model
|
||||||
if (!resolvedProviderId && resolvedAgent?.model?.providerID && resolvedAgent?.model?.modelID) {
|
if (!resolvedProviderId && resolvedAgent?.model?.providerID && resolvedAgent?.model?.modelID) {
|
||||||
if (validateModel(resolvedAgent.model.providerID, resolvedAgent.model.modelID)) {
|
const { providerID, modelID } = resolvedAgent.model;
|
||||||
resolvedProviderId = resolvedAgent.model.providerID;
|
if (validateModel(providerID, modelID)) {
|
||||||
resolvedModelId = resolvedAgent.model.modelID;
|
resolvedProviderId = providerID;
|
||||||
|
resolvedModelId = modelID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1010,9 +1020,10 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
} else {
|
} else {
|
||||||
// Last resort: first provider's first model
|
// Last resort: first provider's first model
|
||||||
const firstProvider = providers[0];
|
const firstProvider = providers[0];
|
||||||
if (firstProvider && firstProvider.models[0]) {
|
const firstModel = firstProvider?.models[0];
|
||||||
|
if (firstProvider && firstModel) {
|
||||||
resolvedProviderId = firstProvider.id;
|
resolvedProviderId = firstProvider.id;
|
||||||
resolvedModelId = firstProvider.models[0].id;
|
resolvedModelId = firstModel.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1034,9 +1045,9 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
providers,
|
providers,
|
||||||
agents: safeAgents,
|
agents: safeAgents,
|
||||||
currentAgentName: resolvedAgent.name,
|
currentAgentName: resolvedAgent.name,
|
||||||
currentProviderId: resolvedProviderId ?? baseSnapshot.currentProviderId,
|
currentProviderId: resolvedProviderId ?? baseSnapshot.currentProviderId,
|
||||||
currentModelId: resolvedModelId ?? baseSnapshot.currentModelId,
|
currentModelId: resolvedModelId ?? baseSnapshot.currentModelId,
|
||||||
currentVariant: resolvedVariant,
|
currentVariant: resolvedVariant,
|
||||||
};
|
};
|
||||||
|
|
||||||
const nextState: Partial<ConfigStore> = {
|
const nextState: Partial<ConfigStore> = {
|
||||||
@@ -1046,14 +1057,14 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (state.activeDirectoryKey === directoryKey) {
|
if (state.activeDirectoryKey === directoryKey) {
|
||||||
nextState.currentAgentName = resolvedAgent.name;
|
nextState.currentAgentName = resolvedAgent.name;
|
||||||
if (resolvedProviderId && resolvedModelId) {
|
if (resolvedProviderId && resolvedModelId) {
|
||||||
nextState.currentProviderId = resolvedProviderId;
|
nextState.currentProviderId = resolvedProviderId;
|
||||||
nextState.currentModelId = resolvedModelId;
|
nextState.currentModelId = resolvedModelId;
|
||||||
nextState.currentVariant = resolvedVariant;
|
nextState.currentVariant = resolvedVariant;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nextState;
|
return nextState;
|
||||||
});
|
});
|
||||||
@@ -1178,7 +1189,7 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
|
|
||||||
if (agentName && typeof window !== "undefined") {
|
if (agentName && typeof window !== "undefined") {
|
||||||
const sessionStore = window.__zustand_session_store__;
|
const sessionStore = window.__zustand_session_store__;
|
||||||
if (sessionStore) {
|
if (sessionStore?.getState) {
|
||||||
const { currentSessionId, getAgentModelForSession } = sessionStore.getState();
|
const { currentSessionId, getAgentModelForSession } = sessionStore.getState();
|
||||||
|
|
||||||
if (currentSessionId) {
|
if (currentSessionId) {
|
||||||
@@ -1245,43 +1256,43 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
|
|
||||||
// Fall back to agent's preferred model
|
// Fall back to agent's preferred model
|
||||||
const agent = agents.find((candidate) => candidate.name === agentName);
|
const agent = agents.find((candidate) => candidate.name === agentName);
|
||||||
if (agent?.model?.providerID && agent?.model?.modelID) {
|
const agentModelSelection = agent?.model;
|
||||||
const agentProvider = providers.find((provider) => provider.id === agent.model!.providerID);
|
if (agentModelSelection?.providerID && agentModelSelection?.modelID) {
|
||||||
if (agentProvider) {
|
const { providerID, modelID } = agentModelSelection;
|
||||||
const agentModel = agentProvider.models.find((model) => model.id === agent.model!.modelID);
|
const agentProvider = providers.find((provider) => provider.id === providerID);
|
||||||
|
const agentModel = agentProvider?.models.find((model) => model.id === modelID);
|
||||||
|
|
||||||
if (agentModel) {
|
if (agentModel) {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const directoryKey = state.activeDirectoryKey;
|
const directoryKey = state.activeDirectoryKey;
|
||||||
const baseSnapshot: DirectoryScopedConfig = state.directoryScoped[directoryKey] ?? {
|
const baseSnapshot: DirectoryScopedConfig = state.directoryScoped[directoryKey] ?? {
|
||||||
providers: state.providers,
|
providers: state.providers,
|
||||||
agents: state.agents,
|
agents: state.agents,
|
||||||
currentProviderId: state.currentProviderId,
|
currentProviderId: state.currentProviderId,
|
||||||
currentModelId: state.currentModelId,
|
currentModelId: state.currentModelId,
|
||||||
currentAgentName: state.currentAgentName,
|
currentAgentName: state.currentAgentName,
|
||||||
selectedProviderId: state.selectedProviderId,
|
selectedProviderId: state.selectedProviderId,
|
||||||
agentModelSelections: state.agentModelSelections,
|
agentModelSelections: state.agentModelSelections,
|
||||||
defaultProviders: state.defaultProviders,
|
defaultProviders: state.defaultProviders,
|
||||||
};
|
};
|
||||||
|
|
||||||
const nextSnapshot: DirectoryScopedConfig = {
|
const nextSnapshot: DirectoryScopedConfig = {
|
||||||
...baseSnapshot,
|
...baseSnapshot,
|
||||||
currentProviderId: agent.model!.providerID,
|
currentProviderId: providerID,
|
||||||
currentModelId: agent.model!.modelID,
|
currentModelId: modelID,
|
||||||
selectedProviderId: agent.model!.providerID,
|
selectedProviderId: providerID,
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentProviderId: agent.model!.providerID,
|
currentProviderId: providerID,
|
||||||
currentModelId: agent.model!.modelID,
|
currentModelId: modelID,
|
||||||
selectedProviderId: agent.model!.providerID,
|
selectedProviderId: providerID,
|
||||||
directoryScoped: {
|
directoryScoped: {
|
||||||
...state.directoryScoped,
|
...state.directoryScoped,
|
||||||
[directoryKey]: nextSnapshot,
|
[directoryKey]: nextSnapshot,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1307,6 +1318,10 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
set({ settingsCommitMessageModel: model });
|
set({ settingsCommitMessageModel: model });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setSettingsGitmojiEnabled: (enabled: boolean) => {
|
||||||
|
set({ settingsGitmojiEnabled: enabled });
|
||||||
|
},
|
||||||
|
|
||||||
checkConnection: async () => {
|
checkConnection: async () => {
|
||||||
const maxAttempts = 5;
|
const maxAttempts = 5;
|
||||||
let attempt = 0;
|
let attempt = 0;
|
||||||
@@ -1412,7 +1427,9 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
settingsDefaultVariant: state.settingsDefaultVariant,
|
settingsDefaultVariant: state.settingsDefaultVariant,
|
||||||
settingsDefaultAgent: state.settingsDefaultAgent,
|
settingsDefaultAgent: state.settingsDefaultAgent,
|
||||||
settingsAutoCreateWorktree: state.settingsAutoCreateWorktree,
|
settingsAutoCreateWorktree: state.settingsAutoCreateWorktree,
|
||||||
}),
|
settingsCommitMessageModel: state.settingsCommitMessageModel,
|
||||||
|
settingsGitmojiEnabled: state.settingsGitmojiEnabled,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -619,6 +619,9 @@ const sanitizeSettingsUpdate = (payload) => {
|
|||||||
const trimmed = candidate.commitMessageModel.trim();
|
const trimmed = candidate.commitMessageModel.trim();
|
||||||
result.commitMessageModel = trimmed.length > 0 ? trimmed : undefined;
|
result.commitMessageModel = trimmed.length > 0 ? trimmed : undefined;
|
||||||
}
|
}
|
||||||
|
if (typeof candidate.gitmojiEnabled === 'boolean') {
|
||||||
|
result.gitmojiEnabled = candidate.gitmojiEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
const skillCatalogs = sanitizeSkillCatalogs(candidate.skillCatalogs);
|
const skillCatalogs = sanitizeSkillCatalogs(candidate.skillCatalogs);
|
||||||
if (skillCatalogs) {
|
if (skillCatalogs) {
|
||||||
|
|||||||
Reference in New Issue
Block a user