Repository: cline/cline
Pull request #2722: ENG-317 / Feat: Add model "favorites" toggle for Cline & OpenRouter providers
Author: login: canvrno
Created: 2025-04-07T20:23:06Z | Merged: 2025-04-11T00:20:17Z

### Description

This PR adds a "favorites" feature to the OpenRouter and Cline provider model switchers. Users can click a star icon to "favorite" a model, which will keep it at the top of the list. Un-favoriting a model will return it to it's original position in the list. Telemetry captures use of this feature if user have opted in.

### Test Procedure

Open the provider settings, switch among providers (including non Cline/OpenRouter ones). When switching, set favorites and ensure the favorites are moved to the top of the list and that they are remembered. Confirm that settings for favorites are not undone when switching to other providers or when switching between the Plan/Act provider settings. When un-favoriting a model, it's star should un-fill and the model should return to it's original position in the list.

When models are favorited/unfavorited, telemetry is captured if users have opted in. The telemetry captures the model ID, and a boolean indicating it was added/removed as a favorite.


### Type of Change

<!-- Put an 'x' in all boxes that apply -->

-   [ ] 🐛 Bug fix (non-breaking change which fixes an issue)
-   [X] ✨ New feature (non-breaking change which adds functionality)
-   [ ] 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
-   [ ] 📚 Documentation update

### Pre-flight Checklist

<!-- Put an 'x' in all boxes that apply -->

-   [X] Changes are limited to a single feature, bugfix or chore (split larger changes into separate PRs)
-   [X] Tests are passing (`npm test`) and code is formatted and linted (`npm run format && npm run lint`)
-   [X] I have created a changeset using `npm run changeset` (required for user-facing changes)
-   [X] I have reviewed [contributor guidelines](https://github.com/cline/cline/blob/main/CONTRIBUTING.md)

### Screenshots

<!-- For UI changes, add screenshots here -->

https://github.com/user-attachments/assets/a9f37fd3-a3bb-4d68-b0fe-bae13441663a

### Additional Notes

<!-- Add any additional notes for reviewers -->

<!-- ELLIPSIS_HIDDEN -->


----

> [!IMPORTANT]
> Add model favoriting feature for Cline and OpenRouter providers with telemetry support and UI updates.
> 
>   - **Feature**:
>     - Add model favoriting feature for Cline and OpenRouter providers in `OpenRouterModelPicker.tsx`.
>     - Users can click a star icon to favorite/unfavorite models, moving favorites to the top of the list.
>   - **State Management**:
>     - Add `favoritedModelIds` to `ApiConfiguration` in `api.ts` and manage it in `state.ts`.
>     - Update `getAllExtensionState()` and `updateApiConfiguration()` in `state.ts` to handle `favoritedModelIds`.
>   - **Telemetry**:
>     - Capture model favoriting events in `TelemetryService.ts` using `captureModelFavoritesUsage()`.
>   - **UI Changes**:
>     - Add `StarIcon` component in `OpenRouterModelPicker.tsx` for favoriting models.
>     - Update `ApiOptions.tsx` to handle provider changes without losing favorite settings.
> 
> <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=cline%2Fcline&utm_source=github&utm_medium=referral)<sup> for 9cdf7714a74315de862e9259c7654aa31d39fd05. It will automatically update as commits are pushed.</sup>


<!-- ELLIPSIS_HIDDEN -->


Diff:
diff --git a/.changeset/neat-wolves-grab.md b/.changeset/neat-wolves-grab.md
new file mode 100644
index 00000000000..26e5a295c3a
--- /dev/null
+++ b/.changeset/neat-wolves-grab.md
@@ -0,0 +1,5 @@
+---
+"claude-dev": patch
+---
+
+Added telemetry for model favoriting
diff --git a/.changeset/twelve-rocks-drum.md b/.changeset/twelve-rocks-drum.md
new file mode 100644
index 00000000000..05b10974db2
--- /dev/null
+++ b/.changeset/twelve-rocks-drum.md
@@ -0,0 +1,5 @@
+---
+"claude-dev": minor
+---
+
+Adds favorite toggles for models when using the Cline & OpenRouter providers
diff --git a/src/core/controller/index.ts b/src/core/controller/index.ts
index 73cd52329ec..6b61b5cdafb 100644
--- a/src/core/controller/index.ts
+++ b/src/core/controller/index.ts
@@ -706,6 +706,27 @@ export class Controller {
 				}
 				break
 			}
+			case "toggleFavoriteModel": {
+				if (message.modelId) {
+					const { apiConfiguration } = await getAllExtensionState(this.context)
+					const favoritedModelIds = apiConfiguration.favoritedModelIds || []
+
+					// Toggle favorite status
+					const updatedFavorites = favoritedModelIds.includes(message.modelId)
+						? favoritedModelIds.filter((id) => id !== message.modelId)
+						: [...favoritedModelIds, message.modelId]
+
+					await updateGlobalState(this.context, "favoritedModelIds", updatedFavorites)
+
+					// Capture telemetry for model favorite toggle
+					const isFavorited = !favoritedModelIds.includes(message.modelId)
+					telemetryService.captureModelFavoritesUsage(message.modelId, isFavorited)
+
+					// Post state to webview without changing any other configuration
+					await this.postStateToWebview()
+				}
+				break
+			}
 			// Add more switch case statements here as more webview message commands
 			// are created within the webview context (i.e. inside media/main.js)
 		}
diff --git a/src/core/storage/state-keys.ts b/src/core/storage/state-keys.ts
index 5b45796a90f..cf6d23dba48 100644
--- a/src/core/storage/state-keys.ts
+++ b/src/core/storage/state-keys.ts
@@ -66,3 +66,4 @@ export type GlobalStateKey =
 	| "asksageApiUrl"
 	| "thinkingBudgetTokens"
 	| "planActSeparateModelsSetting"
+	| "favoritedModelIds"
diff --git a/src/core/storage/state.ts b/src/core/storage/state.ts
index e609f158fc9..1ac7dccba73 100644
--- a/src/core/storage/state.ts
+++ b/src/core/storage/state.ts
@@ -116,6 +116,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
 		thinkingBudgetTokens,
 		sambanovaApiKey,
 		planActSeparateModelsSettingRaw,
+		favoritedModelIds,
 	] = await Promise.all([
 		getGlobalState(context, "apiProvider") as Promise<ApiProvider | undefined>,
 		getGlobalState(context, "apiModelId") as Promise<string | undefined>,
@@ -181,6 +182,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
 		getGlobalState(context, "thinkingBudgetTokens") as Promise<number | undefined>,
 		getSecret(context, "sambanovaApiKey") as Promise<string | undefined>,
 		getGlobalState(context, "planActSeparateModelsSetting") as Promise<boolean | undefined>,
+		getGlobalState(context, "favoritedModelIds") as Promise<string[] | undefined>,
 	])
 
 	let apiProvider: ApiProvider
@@ -272,6 +274,7 @@ export async function getAllExtensionState(context: vscode.ExtensionContext) {
 			asksageApiUrl,
 			xaiApiKey,
 			sambanovaApiKey,
+			favoritedModelIds,
 		},
 		lastShownAnnouncementId,
 		customInstructions,
@@ -343,6 +346,7 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
 		thinkingBudgetTokens,
 		clineApiKey,
 		sambanovaApiKey,
+		favoritedModelIds,
 	} = apiConfiguration
 	await updateGlobalState(context, "apiProvider", apiProvider)
 	await updateGlobalState(context, "apiModelId", apiModelId)
@@ -394,6 +398,7 @@ export async function updateApiConfiguration(context: vscode.ExtensionContext, a
 	await updateGlobalState(context, "thinkingBudgetTokens", thinkingBudgetTokens)
 	await storeSecret(context, "clineApiKey", clineApiKey)
 	await storeSecret(context, "sambanovaApiKey", sambanovaApiKey)
+	await updateGlobalState(context, "favoritedModelIds", favoritedModelIds)
 }
 
 export async function resetExtensionState(context: vscode.ExtensionContext) {
diff --git a/src/services/telemetry/TelemetryService.ts b/src/services/telemetry/TelemetryService.ts
index 4c0c6f81814..5324bbe98e3 100644
--- a/src/services/telemetry/TelemetryService.ts
+++ b/src/services/telemetry/TelemetryService.ts
@@ -65,6 +65,8 @@ class PostHogClient {
 			PLAN_MODE_TOGGLED: "ui.plan_mode_toggled",
 			// Tracks when action mode is toggled on
 			ACT_MODE_TOGGLED: "ui.act_mode_toggled",
+			// Tracks when users use the "favorite" button in the model picker
+			MODEL_FAVORITE_TOGGLED: "ui.model_favorite_toggled",
 		},
 	}
 
@@ -495,6 +497,21 @@ class PostHogClient {
 		})
 	}
 
+	/**
+	 * Records when the user uses the model favorite button in the model picker
+	 * @param model The name of the model the user has interacted with
+	 * @param isFavorited Whether the model is being favorited (true) or unfavorited (false)
+	 */
+	public captureModelFavoritesUsage(model: string, isFavorited: boolean) {
+		this.capture({
+			event: PostHogClient.EVENTS.UI.MODEL_FAVORITE_TOGGLED,
+			properties: {
+				model,
+				isFavorited,
+			},
+		})
+	}
+
 	public isTelemetryEnabled(): boolean {
 		return this.telemetryEnabled
 	}
diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts
index 63d3d911a9f..a329b697df2 100644
--- a/src/shared/WebviewMessage.ts
+++ b/src/shared/WebviewMessage.ts
@@ -68,6 +68,7 @@ export interface WebviewMessage {
 		| "requestTotalTasksSize"
 		| "taskFeedback"
 		| "searchFiles"
+		| "toggleFavoriteModel"
 	// | "relaunchChromeDebugMode"
 	text?: string
 	disabled?: boolean
@@ -100,6 +101,8 @@ export interface WebviewMessage {
 	feedbackType?: TaskFeedbackType
 	mentionsRequestId?: string
 	query?: string
+	// For toggleFavoriteModel
+	modelId?: string
 }
 
 export type ClineAskResponse = "yesButtonClicked" | "noButtonClicked" | "messageResponse"
diff --git a/src/shared/api.ts b/src/shared/api.ts
index d11304b5a55..e3e3c0a5e98 100644
--- a/src/shared/api.ts
+++ b/src/shared/api.ts
@@ -77,6 +77,7 @@ export interface ApiHandlerOptions {
 
 export type ApiConfiguration = ApiHandlerOptions & {
 	apiProvider?: ApiProvider
+	favoritedModelIds?: string[]
 }
 
 // Models
diff --git a/webview-ui/src/components/chat/ContextMenu.tsx b/webview-ui/src/components/chat/ContextMenu.tsx
index 93f70f87fda..2aea8146386 100644
--- a/webview-ui/src/components/chat/ContextMenu.tsx
+++ b/webview-ui/src/components/chat/ContextMenu.tsx
@@ -1,5 +1,5 @@
 import React, { useEffect, useMemo, useRef, useState } from "react"
-import { ContextMenuOptionType, ContextMenuQueryItem, getContextMenuOptions } from "@/utils/context-mentions"
+import { ContextMenuOptionType, ContextMenuQueryItem, getContextMenuOptions, SearchResult } from "@/utils/context-mentions"
 import { cleanPathPrefix } from "@/components/common/CodeAccordian"
 
 interface ContextMenuProps {
diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx
index 3a8f49f5f20..6c5e17e2cfd 100644
--- a/webview-ui/src/components/settings/ApiOptions.tsx
+++ b/webview-ui/src/components/settings/ApiOptions.tsx
@@ -100,10 +100,25 @@ const ApiOptions = ({ showModelOptions, apiErrorMessage, modelIdErrorMessage, is
 	const [providerSortingSelected, setProviderSortingSelected] = useState(!!apiConfiguration?.openRouterProviderSorting)
 
 	const handleInputChange = (field: keyof ApiConfiguration) => (event: any) => {
+		const newValue = event.target.value
+
+		// Update local state
 		setApiConfiguration({
 			...apiConfiguration,
-			[field]: event.target.value,
+			[field]: newValue,
 		})
+
+		// If the field is the provider, save it immediately
+		// Neccesary for favorite model selection to work without undoing provider changes
+		if (field === "apiProvider") {
+			vscode.postMessage({
+				type: "apiConfiguration",
+				apiConfiguration: {
+					...apiConfiguration,
+					apiProvider: newValue,
+				},
+			})
+		}
 	}
 
 	const { selectedProvider, selectedModelId, selectedModelInfo } = useMemo(() => {
diff --git a/webview-ui/src/components/settings/OpenRouterModelPicker.tsx b/webview-ui/src/components/settings/OpenRouterModelPicker.tsx
index b67287f74f5..542faf966f7 100644
--- a/webview-ui/src/components/settings/OpenRouterModelPicker.tsx
+++ b/webview-ui/src/components/settings/OpenRouterModelPicker.tsx
@@ -12,6 +12,25 @@ import { ModelInfoView, normalizeApiConfiguration } from "./ApiOptions"
 import { CODE_BLOCK_BG_COLOR } from "@/components/common/CodeBlock"
 import ThinkingBudgetSlider from "./ThinkingBudgetSlider"
 
+// Star icon for favorites
+const StarIcon = ({ isFavorite, onClick }: { isFavorite: boolean; onClick: (e: React.MouseEvent) => void }) => {
+	return (
+		<div
+			onClick={onClick}
+			style={{
+				cursor: "pointer",
+				color: isFavorite ? "var(--vscode-terminal-ansiBlue)" : "var(--vscode-descriptionForeground)",
+				marginLeft: "8px",
+				fontSize: "16px",
+				display: "flex",
+				alignItems: "center",
+				justifyContent: "center",
+			}}>
+			{isFavorite ? "★" : "☆"}
+		</div>
+	)
+}
+
 export interface OpenRouterModelPickerProps {
 	isPopup?: boolean
 }
@@ -90,9 +109,18 @@ const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup }
 		const results: { id: string; html: string }[] = searchTerm
 			? highlight(fuse.search(searchTerm), "model-item-highlight")
 			: searchableItems
-		// results.sort((a, b) => a.id.localeCompare(b.id)) NOTE: sorting like this causes ids in objects to be reordered and mismatched
-		return results
-	}, [searchableItems, searchTerm, fuse])
+
+		// Sort favorited models to the top
+		const favoritedModelIds = apiConfiguration?.favoritedModelIds || []
+		return results.sort((a, b) => {
+			const aIsFavorite = favoritedModelIds.includes(a.id)
+			const bIsFavorite = favoritedModelIds.includes(b.id)
+
+			if (aIsFavorite && !bIsFavorite) return -1
+			if (!aIsFavorite && bIsFavorite) return 1
+			return a.id.localeCompare(b.id)
+		})
+	}, [searchableItems, searchTerm, fuse, apiConfiguration?.favoritedModelIds])
 
 	const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
 		if (!isDropdownVisible) return
@@ -201,21 +229,34 @@ const OpenRouterModelPicker: React.FC<OpenRouterModelPickerProps> = ({ isPopup }
 					</VSCodeTextField>
 					{isDropdownVisible && (
 						<DropdownList ref={dropdownListRef}>
-							{modelSearchResults.map((item, index) => (
-								<DropdownItem
-									key={item.id}
-									ref={(el) => (itemRefs.current[index] = el)}
-									isSelected={index === selectedIndex}
-									onMouseEnter={() => setSelectedIndex(index)}
-									onClick={() => {
-										handleModelChange(item.id)
-										setIsDropdownVisible(false)
-									}}
-									dangerouslySetInnerHTML={{
-										__html: item.html,
-									}}
-								/>
-							))}
+							{modelSearchResults.map((item, index) => {
+								const isFavorite = (apiConfiguration?.favoritedModelIds || []).includes(item.id)
+								return (
+									<DropdownItem
+										key={item.id}
+										ref={(el) => (itemRefs.current[index] = el)}
+										isSelected={index === selectedIndex}
+										onMouseEnter={() => setSelectedIndex(index)}
+										onClick={() => {
+											handleModelChange(item.id)
+											setIsDropdownVisible(false)
+										}}>
+										<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
+											<span dangerouslySetInnerHTML={{ __html: item.html }} />
+											<StarIcon
+												isFavorite={isFavorite}
+												onClick={(e) => {
+													e.stopPropagation()
+													vscode.postMessage({
+														type: "toggleFavoriteModel",
+														modelId: item.id,
+													})
+												}}
+											/>
+										</div>
+									</DropdownItem>
+								)
+							})}
 						</DropdownList>
 					)}
 				</DropdownWrapper>


Reviews:
user: login: ellipsis-dev[bot]
body: :x: Changes requested. Reviewed everything up to 3d3e679349d282be48fd1b258e4705f1861b6bdf in <ellipsistime>2 minutes and 33 seconds</ellipsistime>

<details>
<summary>More details</summary>

 - Looked at `269` lines of code in `9` files
 - Skipped `0` files when reviewing.
 - Skipped posting `8` drafted comments based on config settings.


<details>
<summary>1. <code>webview-ui/src/components/settings/OpenRouterModelPicker.tsx:200</code></summary>

- **Draft comment:** 
Consider separating the search input state from the actual selected model. Currently, the onInput handler on line 200 calls handleModelChange immediately (and lowercases the input), which updates the configuration on every keystroke. This can lead to unintended model changes while the user is simply searching. A separate state variable for the search term (used only for filtering) that only updates the configuration when an option is explicitly selected might improve the UX.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>2. <code>webview-ui/src/components/settings/ApiOptions.tsx:111</code></summary>

- **Draft comment:** 
Typo in comment: "Neccesary" should be "Necessary". Also, consider typing the event (e.g. React.ChangeEvent<HTMLInputElement>) instead of using 'any'.
- **Reason this comment was not posted:** 
Comment was on unchanged code.

</details>

<details>
<summary>3. <code>webview-ui/src/components/settings/OpenRouterModelPicker.tsx:200</code></summary>

- **Draft comment:** 
The onInput handler converts the input value to lowercase via toLowerCase(). This may unintentionally alter user search queries or selected modelIDs if case sensitivity matters. Consider separating the search term state from the selected model ID.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>4. <code>src/core/storage/state-keys.ts:61</code></summary>

- **Draft comment:** 
Typographical error: The key 'qwenApiLine' appears to be a typo. It should likely be 'qwenApiKey' for consistency with the naming conventions used in the rest of the file.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>5. <code>src/shared/api.ts:998</code></summary>

- **Draft comment:** 
Typographical error: The key 'qwq-plus-latest' in mainlandQwenModels appears to be a typo. It likely should be 'qwen-plus-latest' to be consistent with similar keys in internationalQwenModels.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>6. <code>src/shared/api.ts:1008</code></summary>

- **Draft comment:** 
Typographical error: The key 'qwq-plus' in mainlandQwenModels appears to contain a typo. It likely should be 'qwen-plus' to maintain consistency with the naming convention used in internationalQwenModels.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>7. <code>webview-ui/src/components/chat/ContextMenu.tsx:3</code></summary>

- **Draft comment:** 
Typographical error: Consider renaming 'CodeAccordian' to 'CodeAccordion' to avoid confusion and maintain consistency.
- **Reason this comment was not posted:** 
Comment was on unchanged code.

</details>

<details>
<summary>8. <code>webview-ui/src/components/settings/ApiOptions.tsx:112</code></summary>

- **Draft comment:** 
Typo: The comment says 'Neccesary for favorite model selection to work without undoing provider changes'. Please change 'Neccesary' to 'Necessary'.
- **Reason this comment was not posted:** 
Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50%
While the comment does correctly identify a spelling error in changed code, fixing spelling in comments is a very minor issue that doesn't affect functionality. The comment is technically correct but extremely low value. The rules say to only keep comments that require clear code changes, and to avoid obvious or unimportant changes. A spelling fix in a comment is about as minor as it gets.
The spelling error is real and the fix is correct. Poor spelling in comments could be seen as unprofessional and worth fixing for code quality.
While correct, this type of minor spelling fix in comments doesn't meet the bar of being an important enough issue to warrant a PR comment. The rules explicitly say to avoid obvious and unimportant changes.
Delete this comment. While technically correct, spelling fixes in comments are too minor to warrant a PR comment according to the review rules.

</details>


Workflow ID: <workflowid>`wflow_yZbbDxdTluPnSf37`</workflowid>

</details>


----
**Want Ellipsis to fix these issues?** Tag `@ellipsis-dev` in a comment. You can customize Ellipsis with :+1: / :-1: [feedback](https://docs.ellipsis.dev/review), review rules, user-specific overrides, `quiet` mode, and [more](https://docs.ellipsis.dev/config).

state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T04:01:02Z
user: login: dcbartlett
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T05:45:54Z
user: login: ellipsis-dev[bot]
body: :x: Changes requested. Incremental review on 0ed3c0507d409aaffbe0635bcec75abab7013165 in <ellipsistime>1 minute and 59 seconds</ellipsistime>

<details>
<summary>More details</summary>

 - Looked at `64` lines of code in `3` files
 - Skipped `0` files when reviewing.
 - Skipped posting `5` drafted comments based on config settings.


<details>
<summary>1. <code>src/services/telemetry/TelemetryService.ts:88</code></summary>

- **Draft comment:** 
Avoid hard-coding the PostHog API key in source code. Use environment variables or config files instead.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>2. <code>src/services/telemetry/TelemetryService.ts:500</code></summary>

- **Draft comment:** 
Capture method 'captureModelFavoritesUsage' is consistent with other telemetry methods. Ensure its usage is gated properly with telemetry settings.
- **Reason this comment was not posted:** 
Confidence changes required: `0%` <= threshold `50%`
None

</details>

<details>
<summary>3. <code>.changeset/neat-wolves-grab.md:5</code></summary>

- **Draft comment:** 
Changelog message clearly notes telemetry addition. Consider adding a bit more context if needed.
- **Reason this comment was not posted:** 
Confidence changes required: `10%` <= threshold `50%`
None

</details>

<details>
<summary>4. <code>src/core/controller/index.ts:722</code></summary>

- **Draft comment:** 
Consider renaming 'isFavorited' to 'isNowFavorited' to more clearly indicate the state after toggling.
- **Reason this comment was not posted:** 
Confidence changes required: `50%` <= threshold `50%`
None

</details>

<details>
<summary>5. <code>src/services/telemetry/TelemetryService.ts:500</code></summary>

- **Draft comment:** 
The new captureModelFavoritesUsage method and event constant MODEL_FAVORITE_TOGGLED are well integrated and follow the existing telemetry patterns.
- **Reason this comment was not posted:** 
Confidence changes required: `0%` <= threshold `50%`
None

</details>


Workflow ID: <workflowid>`wflow_0QMzTur6SPp8kIMX`</workflowid>

</details>


----
**Want Ellipsis to fix these issues?** Tag `@ellipsis-dev` in a comment. You can customize Ellipsis with :+1: / :-1: [feedback](https://docs.ellipsis.dev/review), review rules, user-specific overrides, `quiet` mode, and [more](https://docs.ellipsis.dev/config).

state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T20:01:13Z
user: login: ellipsis-dev[bot]
body: :+1: Looks good to me! Incremental review on c957d18f4c0e28753334d748f97b95632e6b6a7c in <ellipsistime>1 minute and 19 seconds</ellipsistime>

<details>
<summary>More details</summary>

 - Looked at `206` lines of code in `1` files
 - Skipped `0` files when reviewing.
 - Skipped posting `2` drafted comments based on config settings.


<details>
<summary>1. <code>webview-ui/src/components/settings/OpenRouterModelPicker.test.tsx:1</code></summary>

- **Draft comment:** 
This entire test file was deleted. Ensure the added favorites feature continues to be properly tested (either by moving/renaming these tests or adding equivalent tests elsewhere).
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>

<details>
<summary>2. <code>webview-ui/src/components/settings/OpenRouterModelPicker.test.tsx:1</code></summary>

- **Draft comment:** 
The entire test file has been removed. Ensure that the new 'favorites' toggle functionality is still adequately covered by tests to prevent regressions.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>


Workflow ID: <workflowid>`wflow_VXNwtGE59rYQkkZ1`</workflowid>

</details>


----
You can customize Ellipsis with :+1: / :-1: [feedback](https://docs.ellipsis.dev/review), review rules, user-specific overrides, `quiet` mode, and [more](https://docs.ellipsis.dev/config).

state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T20:03:29Z
user: login: ellipsis-dev[bot]
body: :+1: Looks good to me! Incremental review on 9cdf7714a74315de862e9259c7654aa31d39fd05 in <ellipsistime>1 minute and 52 seconds</ellipsistime>

<details>
<summary>More details</summary>

 - Looked at `17` lines of code in `1` files
 - Skipped `0` files when reviewing.
 - Skipped posting `4` drafted comments based on config settings.


<details>
<summary>1. <code>src/core/controller/index.ts:722</code></summary>

- **Draft comment:** 
Telemetry is captured by computing `isFavorited` from the original `favoritedModelIds` array (before state update). Consider whether you want to use the updated state (i.e. `updatedFavorites`) for telemetry, to ensure consistency between state and telemetry data.
- **Reason this comment was not posted:** 
Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 0% vs. threshold = 50%
Looking at the code:
1. updatedFavorites is created by either adding or removing modelId from favoritedModelIds
2. isFavorited is computed using !favoritedModelIds.includes() to determine if it was newly favorited
3. The current logic is actually correct - if the modelId wasn't in favoritedModelIds originally, that means it was just favorited
4. Using updatedFavorites would give the wrong result - it would say false when something was just favorited
Could there be a reason why using the updated state would be better for consistency in telemetry data? What if other code relies on the updated state?
No - the telemetry should record what action was just taken (favoriting vs unfavoriting). Using the original state is the correct way to determine this. The updated state would give the opposite of what we want.
The comment is incorrect. The current code correctly determines if an item was just favorited by checking if it wasn't in the original array. Using updatedFavorites would give the wrong result.

</details>

<details>
<summary>2. <code>src/core/controller/index.ts:722</code></summary>

- **Draft comment:** 
Remove the leftover debug logging if it is not needed.
- **Reason this comment was not posted:** 
Comment looked like it was already resolved.

</details>

<details>
<summary>3. <code>src/core/controller/index.ts:722</code></summary>

- **Draft comment:** 
Good cleanup removing the console.log. As a minor suggestion, consider computing the new favorite status using the updatedFavorites array (e.g. using updatedFavorites.includes(message.modelId)) to reflect the new state more directly.
- **Reason this comment was not posted:** 
Confidence changes required: `33%` <= threshold `50%`
None

</details>

<details>
<summary>4. <code>src/core/controller/index.ts:397</code></summary>

- **Draft comment:** 
Typographical error: in the comment on line 397, replace 'superceded' with 'superseded'.
- **Reason this comment was not posted:** 
Comment was not on a location in the diff, so it can't be submitted as a review comment.

</details>


Workflow ID: <workflowid>`wflow_P0SAkKzJSk9XIaLG`</workflowid>

</details>


----
You can customize Ellipsis with :+1: / :-1: [feedback](https://docs.ellipsis.dev/review), review rules, user-specific overrides, `quiet` mode, and [more](https://docs.ellipsis.dev/config).

state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T20:07:14Z
user: login: saoudrizwan
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T00:59:52Z
user: login: dcbartlett
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T00:11:45Z
user: login: saoudrizwan
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T00:18:53Z

Review comments:
diff_hunk: @@ -0,0 +1,5 @@
+---
+"claude-dev": minor
+---
+
+Adds favorite toggles for models when using the Cline & OpenRouter providers"
path: .changeset/twelve-rocks-drum.md
user: login: ellipsis-dev[bot]
body: Remove the extra trailing double quote at the end of line 5 to ensure proper formatting.
```suggestion
Adds favorite toggles for models when using the Cline & OpenRouter providers
```
created_at: 2025-04-08T04:01:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -706,6 +706,23 @@ export class Controller {
 				}
 				break
 			}
+			case "toggleFavoriteModel": {
+				if (message.modelId) {
+					const { apiConfiguration } = await getAllExtensionState(this.context)
+					const favoritedModelIds = apiConfiguration.favoritedModelIds || []
+
+					// Toggle favorite status
+					const updatedFavorites = favoritedModelIds.includes(message.modelId)
+						? favoritedModelIds.filter((id) => id !== message.modelId)
+						: [...favoritedModelIds, message.modelId]
+
+					await updateGlobalState(this.context, "favoritedModelIds", updatedFavorites)
+
+					// Post state to webview without changing any other configuration
+					await this.postStateToWebview()
path: src/core/controller/index.ts
user: login: dcbartlett
body: Let's add some telemetry around when a model gets set to favorite.  This will help us understand if this feature is useful to people.  
created_at: 2025-04-08T05:45:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -706,6 +706,30 @@ export class Controller {
 				}
 				break
 			}
+			case "toggleFavoriteModel": {
+				if (message.modelId) {
+					const { apiConfiguration } = await getAllExtensionState(this.context)
+					const favoritedModelIds = apiConfiguration.favoritedModelIds || []
+
+					// Toggle favorite status
+					const updatedFavorites = favoritedModelIds.includes(message.modelId)
+						? favoritedModelIds.filter((id) => id !== message.modelId)
+						: [...favoritedModelIds, message.modelId]
+
+					await updateGlobalState(this.context, "favoritedModelIds", updatedFavorites)
+
+					// Determine if the model is being favorited or unfavorited
+					const isFavorited = !favoritedModelIds.includes(message.modelId)
+
+					// Capture telemetry for model favorite toggle
+					console.log("Toggling model favorite status:", message.modelId, "isFavorited:", isFavorited)
path: src/core/controller/index.ts
user: login: ellipsis-dev[bot]
body: Remove or downgrade the `console.log` debug statement before production release to avoid excessive logging.
```suggestion

```
created_at: 2025-04-08T20:01:13Z
author_association: CONTRIBUTOR

PR comments:
user: login: linear[bot]
created_at: 2025-04-07T20:23:08Z
body: <p><a href="https://linear.app/cline-bot/issue/ENG-317/favoriting-models-in-the-openrouter-and-cline-model-pickers">ENG-317 Favoriting models in the OpenRouter &amp; Cline Model Pickers</a></p>
author_association: NONE
user: login: changeset-bot[bot]
created_at: 2025-04-07T20:23:09Z
body: ###  🦋  Changeset detected

Latest commit: 9cdf7714a74315de862e9259c7654aa31d39fd05

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 1 package</summary>

  | Name       | Type  |
| ---------- | ----- |
| claude-dev | Minor |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/cline/cline/new/canvrno/eng-317?filename=.changeset/smart-phones-rescue.md&value=---%0A%22claude-dev%22%3A%20patch%0A---%0A%0AENG-317%20%2F%20Feat%3A%20Add%20model%20%22favorites%22%20toggle%20for%20Cline%20%26%20OpenRouter%20providers%0A)


author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-07T20:25:48Z
body: <!-- COVERAGE_REPORT -->
## Coverage Report

### Extension Coverage

Base branch: 45%

PR branch: 45%

⚠️ **Warning: Coverage decreased by 0.01%**

Consider adding tests to cover your changes.

### Webview Coverage

Base branch: 11%

PR branch: 11%

⚠️ **Warning: Coverage decreased by 0.02%**

Consider adding tests to cover your changes.

### Overall Assessment

⚠️ **Test coverage has decreased in this PR**

Please consider adding tests to maintain or improve coverage.



<sub>Last updated: 2025-04-08T20:07:36.091791</sub>
author_association: CONTRIBUTOR