Skip to content

Commit 7eae40d

Browse files
fix(model-selector): show accurate free and training indicators (#3943)
* fix(model-selector): respect explicit model metadata Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> * style(model-selector): format metadata tests Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> * fix(model-selector): preserve data collection helper behavior Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> * fix(model-selector): preserve exclusive model metadata behavior Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> * fix(model-selector): preserve OpenRouter metadata behavior Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> * fix(model-selector): preserve list indicator size Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> --------- Co-authored-by: chrarnoldus <12196001+chrarnoldus@users.noreply.github.com> Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com>
1 parent 1e0d5df commit 7eae40d

26 files changed

Lines changed: 130 additions & 40 deletions

‎apps/mobile/src/app/(app)/agent-chat/model-picker.tsx‎

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
FREE_MODEL_FREE_LABEL,
1212
getFreeModelDataAccessibilityLabel,
1313
isFreeModelOption,
14+
mayTrainOnYourPrompts,
1415
} from '@/lib/free-model-data-disclosure';
1516
import { useThemeColors } from '@/lib/hooks/use-theme-colors';
1617
import { type ModelOption, thinkingEffortLabel } from '@/lib/hooks/use-available-models';
@@ -202,7 +203,7 @@ export default function ModelPickerScreen() {
202203
const modelOption = item.model;
203204
const selected = modelOption.id === selectedModel;
204205
const free = isFreeModelOption(modelOption);
205-
const collectsData = isFreeModelOption(modelOption);
206+
const collectsData = mayTrainOnYourPrompts(modelOption);
206207
const hasVariants = modelOption.variants.length > 1;
207208

208209
return (
@@ -218,16 +219,18 @@ export default function ModelPickerScreen() {
218219
<View className="flex-1">
219220
<Text className="text-base text-foreground">{modelOption.name}</Text>
220221
<Text className="text-xs text-muted-foreground">{modelOption.id}</Text>
221-
{free ? (
222+
{free || collectsData ? (
222223
<View className="mt-1 flex-row items-center gap-1 self-start">
223-
<View
224-
className="rounded-full px-2 py-0.5"
225-
style={{ backgroundColor: colors.good }}
226-
>
227-
<Text className="text-[11px] font-medium text-white" numberOfLines={1}>
228-
{FREE_MODEL_FREE_LABEL}
229-
</Text>
230-
</View>
224+
{free ? (
225+
<View
226+
className="rounded-full px-2 py-0.5"
227+
style={{ backgroundColor: colors.good }}
228+
>
229+
<Text className="text-[11px] font-medium text-white" numberOfLines={1}>
230+
{FREE_MODEL_FREE_LABEL}
231+
</Text>
232+
</View>
233+
) : null}
231234
{collectsData ? (
232235
<BookOpenCheck
233236
accessibilityLabel={FREE_MODEL_DATA_LABEL}

‎apps/mobile/src/components/agents/model-selector.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { BookOpenCheck, Brain, ChevronDown } from 'lucide-react-native';
55
import { Text } from '@/components/ui/text';
66
import {
77
getFreeModelDataAccessibilityLabel,
8-
isFreeModelOption,
8+
mayTrainOnYourPrompts,
99
} from '@/lib/free-model-data-disclosure';
1010
import { type ModelOption, thinkingEffortLabel } from '@/lib/hooks/use-available-models';
1111
import { useThemeColors } from '@/lib/hooks/use-theme-colors';
@@ -43,7 +43,7 @@ export function ModelSelector({
4343

4444
const selectedModel = options.find(m => m.id === value);
4545
const label = selectedModel?.name ?? (value || 'Model');
46-
const collectsData = isFreeModelOption(selectedModel);
46+
const collectsData = mayTrainOnYourPrompts(selectedModel);
4747
const hasVariants = selectedModel ? selectedModel.variants.length > 1 : false;
4848
const variantLabel = variant ? thinkingEffortLabel(variant) : '';
4949
const compactVariantLabel = variant ? compactThinkingEffortLabel(variant) : '';

‎apps/mobile/src/lib/free-model-data-disclosure.test.ts‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
FREE_MODEL_FREE_LABEL,
55
getFreeModelDataAccessibilityLabel,
66
isFreeModelOption,
7+
mayTrainOnYourPrompts,
78
} from './free-model-data-disclosure';
89

910
describe('free model data disclosure', () => {
@@ -20,6 +21,24 @@ describe('free model data disclosure', () => {
2021
expect(isFreeModelOption({ id: 'anthropic/claude' })).toBe(false);
2122
});
2223

24+
it('detects training eligibility independently of freeness', () => {
25+
expect(
26+
mayTrainOnYourPrompts({
27+
id: 'paid-training-model',
28+
isFree: false,
29+
mayTrainOnYourPrompts: true,
30+
})
31+
).toBe(true);
32+
expect(
33+
mayTrainOnYourPrompts({
34+
id: 'free-private-model',
35+
isFree: true,
36+
mayTrainOnYourPrompts: false,
37+
})
38+
).toBe(false);
39+
expect(mayTrainOnYourPrompts({ id: 'free-model', isFree: true })).toBe(false);
40+
});
41+
2342
it('adds a data collection phrase to accessibility labels', () => {
2443
expect(getFreeModelDataAccessibilityLabel('Kilo Auto')).toBe('Kilo Auto, Data collected');
2544
});
Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
export const FREE_MODEL_DATA_LABEL = 'Data collected';
22
export const FREE_MODEL_FREE_LABEL = 'Free';
33

4-
export function isFreeModelOption(model: { id: string; isFree?: boolean } | undefined) {
4+
type ModelDataDisclosure = {
5+
id: string;
6+
isFree?: boolean;
7+
mayTrainOnYourPrompts?: boolean;
8+
};
9+
10+
export function isFreeModelOption(model: ModelDataDisclosure | undefined) {
511
return model?.isFree === true;
612
}
713

14+
export function mayTrainOnYourPrompts(model: ModelDataDisclosure | undefined) {
15+
return model?.mayTrainOnYourPrompts === true;
16+
}
17+
818
export function getFreeModelDataAccessibilityLabel(label: string) {
919
return `${label}, ${FREE_MODEL_DATA_LABEL}`;
1020
}

‎apps/mobile/src/lib/hooks/use-available-models.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ export type ModelOption = {
1313
variants: string[];
1414
isPreferred: boolean;
1515
isFree?: boolean;
16+
mayTrainOnYourPrompts?: boolean;
1617
};
1718

1819
type ModelResponse = {
1920
data: {
2021
id: string;
2122
name: string;
2223
isFree?: boolean;
24+
mayTrainOnYourPrompts?: boolean;
2325
preferredIndex?: number;
2426
opencode?: {
2527
variants?: Record<string, unknown>;
@@ -101,6 +103,7 @@ export function useAvailableModels(organizationId: string | undefined) {
101103
id: model.id,
102104
name: formatShortModelName(model.name),
103105
isFree: model.isFree,
106+
mayTrainOnYourPrompts: model.mayTrainOnYourPrompts,
104107
variants: Object.keys(model.opencode?.variants ?? {}),
105108
preferredIndex: model.preferredIndex,
106109
}));
@@ -127,6 +130,7 @@ export function useAvailableModels(organizationId: string | undefined) {
127130
variants: item.variants,
128131
isPreferred: item.preferredIndex !== undefined,
129132
isFree: item.isFree,
133+
mayTrainOnYourPrompts: item.mayTrainOnYourPrompts,
130134
}));
131135
}, [data]);
132136

‎apps/web/src/app/(app)/claw/components/SettingsTab.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2134,6 +2134,7 @@ export function SettingsTab({
21342134
id: model.id,
21352135
name: model.name,
21362136
isFree: model.isFree,
2137+
mayTrainOnYourPrompts: model.mayTrainOnYourPrompts,
21372138
})),
21382139
trackedOpenClawVersion: trackedVersion,
21392140
runningOpenClawVersion: runningVersion,

‎apps/web/src/app/(app)/claw/hooks/useClawModelOptions.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export function useClawModelOptions(): {
5151
id: model.id,
5252
name: model.name,
5353
isFree: model.isFree,
54+
mayTrainOnYourPrompts: model.mayTrainOnYourPrompts,
5455
})),
5556
trackedOpenClawVersion: trackedVersion,
5657
runningOpenClawVersion: runningVersion,

‎apps/web/src/app/(app)/cloud/webhooks/[triggerId]/EditWebhookTriggerContent.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export function EditWebhookTriggerContent({
9191
id: model.id,
9292
name: model.name,
9393
isFree: model.isFree,
94+
mayTrainOnYourPrompts: model.mayTrainOnYourPrompts,
9495
})),
9596
[modelsData?.data]
9697
);

‎apps/web/src/app/(app)/cloud/webhooks/new/CreateWebhookTriggerContent.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export function CreateWebhookTriggerContent({ organizationId }: CreateWebhookTri
7777
id: model.id,
7878
name: model.name,
7979
isFree: model.isFree,
80+
mayTrainOnYourPrompts: model.mayTrainOnYourPrompts,
8081
})),
8182
[modelsData?.data]
8283
);

‎apps/web/src/app/(app)/gastown/[townId]/rigs/[rigId]/settings/RigSettingsPageClient.tsx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ export function RigSettingsPageClient({ townId, rigId, organizationId }: Props)
101101
id: model.id,
102102
name: model.name,
103103
isFree: model.isFree,
104+
mayTrainOnYourPrompts: model.mayTrainOnYourPrompts,
104105
})) ?? [],
105106
[modelsData]
106107
);

0 commit comments

Comments
 (0)