import { useAtomValue } from "@effect/atom-react";import { useNavigate } from "@tanstack/react-router";import { useRef } from "react";import type { ProviderInstanceId, SourceControlWritingStyleMode } from "@t3tools/contracts";import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings";import { createModelSelection } from "@t3tools/shared/model";import { resolveSourceControlWriterModelSelection } from "@t3tools/shared/serverSettings"; import { usePrimarySettings, useUpdatePrimarySettings } from "../../hooks/useSettings";import { applyProviderInstanceSettings, deriveProviderInstanceEntries, sortProviderInstanceEntries,} from "../../providerInstances";import { getCustomModelOptionsByInstance, resolveAppModelSelectionState,} from "../../modelSelection";import { primaryServerProvidersAtom } from "../../state/server";import { usePrimaryEnvironmentId } from "../../state/environments";import { ProviderModelPicker } from "../chat/ProviderModelPicker";import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../ui/select";import { Switch } from "../ui/switch";import { Textarea } from "../ui/textarea";import { SETTINGS_PICKER_TRIGGER_CLASSNAME, SettingResetButton, SettingsRow, SettingsSection,} from "./settingsLayout";import { searchableSetting } from "./settingsSearch"; const MODE_OPTIONS: Record<SourceControlWritingStyleMode, { label: string; description: string }> = { repo_conventions: { label: "Repository conventions", description: "In each project, matches recent change descriptions and change request titles.", }, conventional_commits: { label: "Conventional Commits", description: "Use Conventional Commit prefixes and keep change request text concise.", }, custom: { label: "Custom instructions", description: "Use your instructions for change descriptions and change requests in every project.", }, }; export function SourceControlWritingSettingsSection() { const settings = usePrimarySettings(); const updateSettings = useUpdatePrimarySettings(); const navigate = useNavigate(); const environmentId = usePrimaryEnvironmentId(); const serverProviders = useAtomValue(primaryServerProvidersAtom); const customInstructionsRef = useRef<HTMLTextAreaElement>(null); const style = settings.sourceControlWritingStyle; const defaults = DEFAULT_UNIFIED_SETTINGS.sourceControlWritingStyle; const isSourceControlWritingStyleDirty = style.mode !== defaults.mode || style.customInstructions !== defaults.customInstructions; const textGenerationProviders = serverProviders.filter( (provider) => provider.supportsTextGeneration !== false, ); const defaultModelSelection = resolveAppModelSelectionState(settings, textGenerationProviders); const usesDedicatedModel = settings.sourceControlWriterModelSelection !== null; const activeSelection = resolveAppModelSelectionState( { ...settings, textGenerationModelSelection: resolveSourceControlWriterModelSelection( settings, textGenerationProviders, ), }, textGenerationProviders, ); const instanceEntries = sortProviderInstanceEntries( applyProviderInstanceSettings(deriveProviderInstanceEntries(textGenerationProviders), settings), ); const canEnableDedicatedModel = instanceEntries.some( (entry) => entry.instanceId === defaultModelSelection.instanceId && entry.enabled && entry.isAvailable, ); const modelOptionsByInstance = getCustomModelOptionsByInstance( settings, textGenerationProviders, activeSelection.instanceId, activeSelection.model, ); return ( <SettingsSection id="source-control-text-generation" title="Text generation"> <SettingsRow serverScoped {...searchableSetting("source-control-writing-style")} description={MODE_OPTIONS[style.mode].description} resetAction={ isSourceControlWritingStyleDirty ? ( <SettingResetButton label="source control writing style" onClick={() => updateSettings({ sourceControlWritingStyle: { mode: defaults.mode, customInstructions: defaults.customInstructions, }, }) } /> ) : null } control={ <Select value={style.mode} onValueChange={(value) => { const customInstructions = customInstructionsRef.current?.value.trim(); updateSettings({ sourceControlWritingStyle: { mode: value as SourceControlWritingStyleMode, ...(customInstructions !== undefined ? { customInstructions } : {}), }, }); }} > <SelectTrigger size="sm" className="w-full sm:w-56" aria-label="Source control writing style" > <SelectValue>{MODE_OPTIONS[style.mode].label}</SelectValue> </SelectTrigger> <SelectPopup align="end" alignItemWithTrigger={false}> {(Object.keys(MODE_OPTIONS) as SourceControlWritingStyleMode[]).map((mode) => ( <SelectItem key={mode} hideIndicator value={mode}> {MODE_OPTIONS[mode].label} </SelectItem> ))} </SelectPopup> </Select> } > {style.mode === "custom" ? ( <div className="mt-3 max-w-2xl pb-3.5"> <Textarea key={style.customInstructions} ref={customInstructionsRef} defaultValue={style.customInstructions} onBlur={(event) => { const customInstructions = event.target.value.trim(); if (customInstructions !== style.customInstructions) { updateSettings({ sourceControlWritingStyle: { customInstructions } }); } }} rows={4} placeholder="Keep titles concise. Use short bullet points in descriptions." aria-label="Custom source control writing instructions" /> </div> ) : null} </SettingsRow> <SettingsRow serverScoped {...searchableSetting("follow-change-request-templates")} description="Use the repository's template for change request descriptions when available." resetAction={ style.followChangeRequestTemplates !== defaults.followChangeRequestTemplates ? ( <SettingResetButton label="change request templates" onClick={() => updateSettings({ sourceControlWritingStyle: { followChangeRequestTemplates: defaults.followChangeRequestTemplates, }, }) } /> ) : null } control={ <Switch checked={style.followChangeRequestTemplates} onCheckedChange={(checked) => updateSettings({ sourceControlWritingStyle: { followChangeRequestTemplates: Boolean(checked), }, }) } aria-label="Follow change request templates" /> } /> <SettingsRow serverScoped {...searchableSetting("source-control-writer-model")} description="Model for source control text and branch or bookmark names. Off uses the global default." control={ <div className="flex flex-wrap items-center justify-end gap-2"> {usesDedicatedModel && !canEnableDedicatedModel ? ( <span className="text-sm text-muted-foreground"> No text generation providers available. </span> ) : null} {usesDedicatedModel && canEnableDedicatedModel ? ( <ProviderModelPicker activeInstanceId={activeSelection.instanceId} model={activeSelection.model} lockedProvider={null} instanceEntries={instanceEntries} modelOptionsByInstance={modelOptionsByInstance} triggerVariant="outline" triggerClassName={SETTINGS_PICKER_TRIGGER_CLASSNAME} triggerAriaLabel="Source control writer model" {...(environmentId ? { onOpenProviderSetup: (instanceId: ProviderInstanceId) => { void navigate({ to: "/settings/providers", search: { environmentId, instanceId }, }); }, } : {})} onInstanceModelChange={(instanceId, model) => { updateSettings({ sourceControlWriterModelSelection: createModelSelection(instanceId, model), }); }} /> ) : null} <Switch checked={usesDedicatedModel} disabled={!usesDedicatedModel && !canEnableDedicatedModel} onCheckedChange={(checked) => updateSettings({ sourceControlWriterModelSelection: checked ? createModelSelection( defaultModelSelection.instanceId, defaultModelSelection.model, defaultModelSelection.options, ) : null, }) } aria-label="Use a separate source control writer model" /> </div> } /> </SettingsSection> );}
import { useAtomValue } from "@effect/atom-react";
import { useNavigate } from "@tanstack/react-router";
import { useRef } from "react";
import type { ProviderInstanceId, SourceControlWritingStyleMode } from "@t3tools/contracts";
import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings";
import { createModelSelection } from "@t3tools/shared/model";
import { resolveSourceControlWriterModelSelection } from "@t3tools/shared/serverSettings";
import { usePrimarySettings, useUpdatePrimarySettings } from "../../hooks/useSettings";
import {
applyProviderInstanceSettings,
deriveProviderInstanceEntries,
sortProviderInstanceEntries,
} from "../../providerInstances";
import {
getCustomModelOptionsByInstance,
resolveAppModelSelectionState,
} from "../../modelSelection";
import { primaryServerProvidersAtom } from "../../state/server";
import { usePrimaryEnvironmentId } from "../../state/environments";
import { ProviderModelPicker } from "../chat/ProviderModelPicker";
import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../ui/select";
import { Switch } from "../ui/switch";
import { Textarea } from "../ui/textarea";
import {
SETTINGS_PICKER_TRIGGER_CLASSNAME,
SettingResetButton,
SettingsRow,
SettingsSection,
} from "./settingsLayout";
import { searchableSetting } from "./settingsSearch";
const MODE_OPTIONS: Record<SourceControlWritingStyleMode, { label: string; description: string }> =
{
repo_conventions: {
label: "Repository conventions",
description: "In each project, matches recent change descriptions and change request titles.",
},
conventional_commits: {
label: "Conventional Commits",
description: "Use Conventional Commit prefixes and keep change request text concise.",
},
custom: {
label: "Custom instructions",
description:
"Use your instructions for change descriptions and change requests in every project.",
},
};
export function SourceControlWritingSettingsSection() {
const settings = usePrimarySettings();
const updateSettings = useUpdatePrimarySettings();
const navigate = useNavigate();
const environmentId = usePrimaryEnvironmentId();
const serverProviders = useAtomValue(primaryServerProvidersAtom);
const customInstructionsRef = useRef<HTMLTextAreaElement>(null);
const style = settings.sourceControlWritingStyle;
const defaults = DEFAULT_UNIFIED_SETTINGS.sourceControlWritingStyle;
const isSourceControlWritingStyleDirty =
style.mode !== defaults.mode || style.customInstructions !== defaults.customInstructions;
const textGenerationProviders = serverProviders.filter(
(provider) => provider.supportsTextGeneration !== false,
);
const defaultModelSelection = resolveAppModelSelectionState(settings, textGenerationProviders);
const usesDedicatedModel = settings.sourceControlWriterModelSelection !== null;
const activeSelection = resolveAppModelSelectionState(
{
...settings,
textGenerationModelSelection: resolveSourceControlWriterModelSelection(
settings,
textGenerationProviders,
),
},
textGenerationProviders,
);
const instanceEntries = sortProviderInstanceEntries(
applyProviderInstanceSettings(deriveProviderInstanceEntries(textGenerationProviders), settings),
);
const canEnableDedicatedModel = instanceEntries.some(
(entry) =>
entry.instanceId === defaultModelSelection.instanceId && entry.enabled && entry.isAvailable,
);
const modelOptionsByInstance = getCustomModelOptionsByInstance(
settings,
textGenerationProviders,
activeSelection.instanceId,
activeSelection.model,
);
return (
<SettingsSection id="source-control-text-generation" title="Text generation">
<SettingsRow
serverScoped
{...searchableSetting("source-control-writing-style")}
description={MODE_OPTIONS[style.mode].description}
resetAction={
isSourceControlWritingStyleDirty ? (
<SettingResetButton
label="source control writing style"
onClick={() =>
updateSettings({
sourceControlWritingStyle: {
mode: defaults.mode,
customInstructions: defaults.customInstructions,
},
})
}
/>
) : null
}
control={
<Select
value={style.mode}
onValueChange={(value) => {
const customInstructions = customInstructionsRef.current?.value.trim();
updateSettings({
sourceControlWritingStyle: {
mode: value as SourceControlWritingStyleMode,
...(customInstructions !== undefined ? { customInstructions } : {}),
},
});
}}
>
<SelectTrigger
size="sm"
className="w-full sm:w-56"
aria-label="Source control writing style"
>
<SelectValue>{MODE_OPTIONS[style.mode].label}</SelectValue>
</SelectTrigger>
<SelectPopup align="end" alignItemWithTrigger={false}>
{(Object.keys(MODE_OPTIONS) as SourceControlWritingStyleMode[]).map((mode) => (
<SelectItem key={mode} hideIndicator value={mode}>
{MODE_OPTIONS[mode].label}
</SelectItem>
))}
</SelectPopup>
</Select>
}
>
{style.mode === "custom" ? (
<div className="mt-3 max-w-2xl pb-3.5">
<Textarea
key={style.customInstructions}
ref={customInstructionsRef}
defaultValue={style.customInstructions}
onBlur={(event) => {
const customInstructions = event.target.value.trim();
if (customInstructions !== style.customInstructions) {
updateSettings({ sourceControlWritingStyle: { customInstructions } });
}
}}
rows={4}
placeholder="Keep titles concise. Use short bullet points in descriptions."
aria-label="Custom source control writing instructions"
/>
</div>
) : null}
</SettingsRow>
<SettingsRow
serverScoped
{...searchableSetting("follow-change-request-templates")}
description="Use the repository's template for change request descriptions when available."
resetAction={
style.followChangeRequestTemplates !== defaults.followChangeRequestTemplates ? (
<SettingResetButton
label="change request templates"
onClick={() =>
updateSettings({
sourceControlWritingStyle: {
followChangeRequestTemplates: defaults.followChangeRequestTemplates,
},
})
}
/>
) : null
}
control={
<Switch
checked={style.followChangeRequestTemplates}
onCheckedChange={(checked) =>
updateSettings({
sourceControlWritingStyle: {
followChangeRequestTemplates: Boolean(checked),
},
})
}
aria-label="Follow change request templates"
/>
}
/>
<SettingsRow
serverScoped
{...searchableSetting("source-control-writer-model")}
description="Model for source control text and branch or bookmark names. Off uses the global default."
control={
<div className="flex flex-wrap items-center justify-end gap-2">
{usesDedicatedModel && !canEnableDedicatedModel ? (
<span className="text-sm text-muted-foreground">
No text generation providers available.
</span>
) : null}
{usesDedicatedModel && canEnableDedicatedModel ? (
<ProviderModelPicker
activeInstanceId={activeSelection.instanceId}
model={activeSelection.model}
lockedProvider={null}
instanceEntries={instanceEntries}
modelOptionsByInstance={modelOptionsByInstance}
triggerVariant="outline"
triggerClassName={SETTINGS_PICKER_TRIGGER_CLASSNAME}
triggerAriaLabel="Source control writer model"
{...(environmentId
? {
onOpenProviderSetup: (instanceId: ProviderInstanceId) => {
void navigate({
to: "/settings/providers",
search: { environmentId, instanceId },
});
},
}
: {})}
onInstanceModelChange={(instanceId, model) => {
updateSettings({
sourceControlWriterModelSelection: createModelSelection(instanceId, model),
});
}}
/>
) : null}
<Switch
checked={usesDedicatedModel}
disabled={!usesDedicatedModel && !canEnableDedicatedModel}
onCheckedChange={(checked) =>
updateSettings({
sourceControlWriterModelSelection: checked
? createModelSelection(
defaultModelSelection.instanceId,
defaultModelSelection.model,
defaultModelSelection.options,
)
: null,
})
}
aria-label="Use a separate source control writer model"
/>
</div>
}
/>
</SettingsSection>
);
}