From 103cf6235625acf482fe740ed218a79b0d64ca6b Mon Sep 17 00:00:00 2001 From: OmarMcAdam Date: Fri, 13 Nov 2026 16:24:10 -0800 Subject: [PATCH 1/2] feat(app): open Ollama from AI Providers instead of the settings sidebar Behind ollamaInAiProviders (cloud and self-hosted, off). When on, the settings sidebar and section menu drop the Ollama page, AI Providers shows a secondary Use Ollama button that opens /settings/ollama, and the sidebar keeps AI Providers highlighted on that page. The button is hidden when the organization blocks local models. --- .../session/status/use-desktop-feature.ts | 2 +- .../domains/settings/pages/ai-view.tsx | 6 ++ .../domains/settings/shell/settings-page.tsx | 27 +++++++-- .../domains/settings/shell/settings-shell.tsx | 3 +- .../src/react-app/shell/settings-route.tsx | 4 ++ .../app/tests/ollama-in-ai-providers.test.tsx | 55 +++++++++++++++++++ .../features/ollama-in-ai-providers/README.md | 35 ++++++++++++ packages/features/src/all-flags.ts | 1 + .../src/flags/ollama-in-ai-providers.ts | 13 +++++ 9 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 apps/app/tests/ollama-in-ai-providers.test.tsx create mode 100644 docs/features/ollama-in-ai-providers/README.md create mode 100644 packages/features/src/flags/ollama-in-ai-providers.ts diff --git a/apps/app/src/react-app/domains/session/status/use-desktop-feature.ts b/apps/app/src/react-app/domains/session/status/use-desktop-feature.ts index bd2a2145ffd..b398b7dbc5b 100644 --- a/apps/app/src/react-app/domains/session/status/use-desktop-feature.ts +++ b/apps/app/src/react-app/domains/session/status/use-desktop-feature.ts @@ -8,7 +8,7 @@ import { useAutomationsDenContext } from "@/react-app/domains/automations/use-au const FEATURE_STALE_MS = 30_000 -type DesktopFeatureKey = "remoteComputers" | "followUpSuggestions" | "questionMinimize" | "importOpencodeConnections" +type DesktopFeatureKey = "remoteComputers" | "followUpSuggestions" | "questionMinimize" | "importOpencodeConnections" | "ollamaInAiProviders" /** * Reads one desktop feature from the registry. Signed in, it follows the diff --git a/apps/app/src/react-app/domains/settings/pages/ai-view.tsx b/apps/app/src/react-app/domains/settings/pages/ai-view.tsx index 76b6ca3e18e..531aebc7282 100644 --- a/apps/app/src/react-app/domains/settings/pages/ai-view.tsx +++ b/apps/app/src/react-app/domains/settings/pages/ai-view.tsx @@ -77,6 +77,8 @@ export type AiSettingsViewProps = { /** Device provider sharing: which keys on this device can be shared with the organization, or already are. */ shareRows?: ReadonlyMap; onShareProvider?: (provider: { id: string; name: string }) => void; + /** Feature `ollamaInAiProviders`: opens the Ollama page from the bottom of AI Providers. */ + onUseOllama?: () => void; }; function deviceSourceLabel(source: ConnectedProvider["source"]) { @@ -241,5 +243,9 @@ export function AiSettingsView(props: AiSettingsViewProps) { {props.gatewayConnectProviders?.map((provider) => )} : null)} + + {props.onUseOllama ?
+ +
: null} ; } diff --git a/apps/app/src/react-app/domains/settings/shell/settings-page.tsx b/apps/app/src/react-app/domains/settings/shell/settings-page.tsx index 4d93b15128b..4642a48b773 100644 --- a/apps/app/src/react-app/domains/settings/shell/settings-page.tsx +++ b/apps/app/src/react-app/domains/settings/shell/settings-page.tsx @@ -51,6 +51,7 @@ import { isElectronRuntime } from "../../../../app/utils"; import { cn } from "@/lib/utils"; import { usePlatform } from "../../../kernel/platform"; import { useCheckDesktopRestriction } from "../../cloud/desktop-config-provider"; +import { useDesktopFeature } from "../../session/status/use-desktop-feature"; import { SettingsContent, SettingsPanel, @@ -220,9 +221,12 @@ export function getWorkspaceSettingsTabs(): SettingsTab[] { export function getGlobalSettingsTabs( developerMode: boolean, capabilities: Pick, - options: { desktop?: boolean } = {}, + options: { desktop?: boolean; ollamaInAiProviders?: boolean } = {}, ): SettingsTab[] { - const tabs: SettingsTab[] = ["ai", "ollama", "appearance", "shortcuts", "environment"]; + // Feature `ollamaInAiProviders`: Ollama opens from a button on AI Providers instead of the sidebar. + const tabs: SettingsTab[] = options.ollamaInAiProviders + ? ["ai", "appearance", "shortcuts", "environment"] + : ["ai", "ollama", "appearance", "shortcuts", "environment"]; if (options.desktop) tabs.push("computer"); if (capabilities.autoUpdate) tabs.push("updates"); if (developerMode) tabs.push("debug"); @@ -238,7 +242,12 @@ export function isSettingsTabBeta(_tab: SettingsTab) { return false; } -export function isSettingsTabActive(activeTab: SettingsTab, tab: SettingsTab) { +export function isSettingsTabActive( + activeTab: SettingsTab, + tab: SettingsTab, + options: { ollamaInAiProviders?: boolean } = {}, +) { + if (options.ollamaInAiProviders && activeTab === "ollama") return tab === "ai"; return activeTab === tab; } @@ -269,6 +278,8 @@ export type SettingsNavGroups = { workspace: SettingsTab[]; global: SettingsTab[]; cloud: SettingsTab[]; + /** Feature `ollamaInAiProviders`: the Ollama page is reached from AI Providers, which stays highlighted there. */ + ollamaInAiProviders: boolean; }; /** @@ -280,13 +291,15 @@ export type SettingsNavGroups = { export function useSettingsNavGroups(developerMode: boolean): SettingsNavGroups { const platform = usePlatform(); const checkRestriction = useCheckDesktopRestriction(); + const ollamaInAiProviders = useDesktopFeature("ollamaInAiProviders"); const allowed = (tabs: SettingsTab[]) => tabs.filter((tab) => isSettingsTabAllowed({ tab, checkRestriction })); return { hub: allowed(["general"]), workspace: allowed(getWorkspaceSettingsTabs()), - global: allowed(getGlobalSettingsTabs(developerMode, platform.capabilities, { desktop: isElectronRuntime() })), + global: allowed(getGlobalSettingsTabs(developerMode, platform.capabilities, { desktop: isElectronRuntime(), ollamaInAiProviders })), cloud: allowed([...CLOUD_SETTINGS_TABS, "devices"]), + ollamaInAiProviders, }; } @@ -320,6 +333,7 @@ function SettingsSidebarGroup(props: { tabs: SettingsTab[]; activeTab: SettingsTab; onSelectTab: (tab: SettingsTab) => void; + ollamaInAiProviders: boolean; }) { if (props.tabs.length === 0) return null; return ( @@ -332,7 +346,7 @@ function SettingsSidebarGroup(props: { return ( } @@ -413,18 +427,21 @@ export function SettingsSidebar(props: SettingsSidebarProps) { tabs={groups.workspace} activeTab={props.activeTab} onSelectTab={props.onSelectTab} + ollamaInAiProviders={groups.ollamaInAiProviders} /> diff --git a/apps/app/src/react-app/domains/settings/shell/settings-shell.tsx b/apps/app/src/react-app/domains/settings/shell/settings-shell.tsx index d5fe457cd5d..52ad83691cf 100644 --- a/apps/app/src/react-app/domains/settings/shell/settings-shell.tsx +++ b/apps/app/src/react-app/domains/settings/shell/settings-shell.tsx @@ -27,6 +27,7 @@ import { SettingsSidebar, getSettingsTabIcon, getSettingsTabLabel, + isSettingsTabActive, isSettingsTabBeta, useSettingsNavGroups, } from "./settings-page"; @@ -194,7 +195,7 @@ function SettingsSectionMenu(props: Pick props.onSelectTab(tab)} - className={props.activeTab === tab ? "bg-foreground/10 text-accent-foreground" : undefined} + className={isSettingsTabActive(props.activeTab, tab, { ollamaInAiProviders: groups.ollamaInAiProviders }) ? "bg-foreground/10 text-accent-foreground" : undefined} > {getSettingsTabLabel(tab)} diff --git a/apps/app/src/react-app/shell/settings-route.tsx b/apps/app/src/react-app/shell/settings-route.tsx index 893b8a08e53..96c19dc4939 100644 --- a/apps/app/src/react-app/shell/settings-route.tsx +++ b/apps/app/src/react-app/shell/settings-route.tsx @@ -1171,6 +1171,7 @@ function SettingsRouteContent(props: SettingsSurfaceProps = {}) { const runtimeWorkspaceId = selectedWorkspaceEndpoint?.workspaceId ?? selectedWorkspace?.id ?? null; const importOpencodeConnectionsEnabled = useDesktopFeature("importOpencodeConnections"); + const ollamaInAiProvidersEnabled = useDesktopFeature("ollamaInAiProviders"); const opencodeConnectionsImport = useMemo(() => { if (!importOpencodeConnectionsEnabled || !openworkClient || !runtimeWorkspaceId) return undefined; return { @@ -2394,6 +2395,9 @@ function SettingsRouteContent(props: SettingsSurfaceProps = {}) { showOpenWorkModelsDeviceRow={openWorkModelsRow.showDeviceRow} organizationProviderIds={organizationProviderIds} onOpenDen={openProviderCloudPage} + onUseOllama={ollamaInAiProvidersEnabled && !checkDesktopRestriction({ restriction: "allowLocalModels" }) + ? () => navigateSettingsPath("ollama") + : undefined} cloudProvidersView={ { + if (ownedDom) await GlobalRegistrator.unregister(); +}); + +test("off, Ollama stays its own settings page in the sidebar", () => { + expect(getGlobalSettingsTabs(false, { autoUpdate: false })).toContain("ollama"); + expect(isSettingsTabActive("ollama", "ollama")).toBe(true); + expect(isSettingsTabActive("ollama", "ai")).toBe(false); +}); + +test("on, the sidebar drops Ollama and highlights AI Providers while the Ollama page is open", () => { + const tabs = getGlobalSettingsTabs(false, { autoUpdate: false }, { ollamaInAiProviders: true }); + expect(tabs).not.toContain("ollama"); + expect(tabs).toContain("ai"); + expect(isSettingsTabActive("ollama", "ai", { ollamaInAiProviders: true })).toBe(true); + expect(isSettingsTabActive("ollama", "ollama", { ollamaInAiProviders: true })).toBe(false); + expect(isSettingsTabActive("appearance", "ai", { ollamaInAiProviders: true })).toBe(false); +}); + +const view: AiSettingsViewProps = { + busy: false, + providerAuthBusy: false, + providerStatusLabel: "", + providerStatusStyle: "", + providerSummary: "", + providerLoadState: { status: "ready", error: null }, + onRetryProviders: () => {}, + connectedProviders: [], + disconnectingProviderId: null, + providerConnectError: null, + providerDisconnectStatus: null, + providerDisconnectError: null, + onOpenProviderAuth: () => {}, + onDisconnectProvider: () => {}, + canDisconnectProvider: () => true, + canAddProviders: true, +}; + +test("AI Providers shows Use Ollama only when the page is given a way to open it", () => { + expect(renderToStaticMarkup()).not.toContain("Use Ollama"); + const withButton = renderToStaticMarkup( {}} />); + expect(withButton).toContain("ai-providers-use-ollama"); + expect(withButton).toContain("Use Ollama"); +}); diff --git a/docs/features/ollama-in-ai-providers/README.md b/docs/features/ollama-in-ai-providers/README.md new file mode 100644 index 00000000000..8b349b5d2e3 --- /dev/null +++ b/docs/features/ollama-in-ai-providers/README.md @@ -0,0 +1,35 @@ +# Ollama under AI Providers (`ollamaInAiProviders`) + +- **Feature key:** `ollamaInAiProviders` +- **Deployments:** cloud and self-hosted (`everywhere`), off by default +- **Author:** @OmarMcAdam + +## What people get + +Off, desktop Settings lists **Ollama** as its own page in the Global group of +the sidebar, next to **AI Providers**. + +On, the sidebar (and the compact section menu) no longer lists Ollama. Ollama +is a way to get models, so it lives with the other providers: the bottom of +**AI Providers** has a secondary **Use Ollama** button that opens the same +Ollama page as before (`/settings/ollama`). While the Ollama page is open, the +sidebar highlights **AI Providers**. + +The Ollama page itself is unchanged: install, sync and manage local models +work exactly as before. The command palette still finds the Ollama page. + +When the organization turns off local models (`allowLocalModels`), the +**Use Ollama** button is not shown, and opening `/settings/ollama` directly +still shows the existing "blocked by your organization" notice. + +## Off + +Turning it off brings back the Ollama sidebar entry and removes the button. It +is safe to turn off at any moment: nothing is stored, and Ollama models a +person already set up keep working, since only navigation changes. + +## Try it + +Turn the feature on for an organization, open desktop Settings, check the +sidebar has no Ollama entry, open **AI Providers**, scroll to the bottom and +click **Use Ollama**. diff --git a/packages/features/src/all-flags.ts b/packages/features/src/all-flags.ts index 71fb6507411..155d7357c72 100644 --- a/packages/features/src/all-flags.ts +++ b/packages/features/src/all-flags.ts @@ -13,6 +13,7 @@ export { default as importOpencodeConnections } from "@openwork/features/flags/i export { default as managedDeployments } from "@openwork/features/flags/managed-deployments" export { default as mcpThreadEvents } from "@openwork/features/flags/mcp-thread-events" export { default as nativeMcpEvents } from "@openwork/features/flags/native-mcp-events" +export { default as ollamaInAiProviders } from "@openwork/features/flags/ollama-in-ai-providers" export { default as opencodeV2Only } from "@openwork/features/flags/opencode-v2-only" export { default as questionMinimize } from "@openwork/features/flags/question-minimize" export { default as remoteComputers } from "@openwork/features/flags/remote-computers" diff --git a/packages/features/src/flags/ollama-in-ai-providers.ts b/packages/features/src/flags/ollama-in-ai-providers.ts new file mode 100644 index 00000000000..2c454de742b --- /dev/null +++ b/packages/features/src/flags/ollama-in-ai-providers.ts @@ -0,0 +1,13 @@ +import { defineFeature, everywhere } from "@openwork/features/define" + +export default defineFeature({ + label: "Desktop: Ollama under AI Providers", + description: "Settings no longer lists Ollama as its own page in the sidebar. Instead, AI Providers has a Use Ollama button at the bottom that opens the same Ollama page. Turning it off brings back the Ollama entry in the sidebar, as before.", + category: "models", + added: "2026-11-13", + since: "2026-11", + author: "OmarMcAdam", + docs: "docs/features/ollama-in-ai-providers/README.md", + deployments: everywhere, + default: false, +}) From 460fbee0992a8e66cd7c2b25c5187b9c188f773e Mon Sep 17 00:00:00 2001 From: OmarMcAdam Date: Fri, 13 Nov 2026 16:40:55 -0800 Subject: [PATCH 2/2] feat(app): ship Ollama under AI Providers without a feature flag Drop the ollamaInAiProviders flag and its doc: the settings sidebar no longer lists Ollama, AI Providers always offers Use Ollama (unless the organization blocks local models), and the command palette still finds the Ollama page. --- .../session/status/use-desktop-feature.ts | 2 +- .../domains/settings/pages/ai-view.tsx | 2 +- .../domains/settings/shell/settings-page.tsx | 30 +++++----------- .../domains/settings/shell/settings-shell.tsx | 2 +- .../shell/command-palette-settings.ts | 3 ++ .../src/react-app/shell/settings-route.tsx | 3 +- .../app/tests/ollama-in-ai-providers.test.tsx | 27 ++++++++------ .../features/ollama-in-ai-providers/README.md | 35 ------------------- packages/features/src/all-flags.ts | 1 - .../src/flags/ollama-in-ai-providers.ts | 13 ------- 10 files changed, 31 insertions(+), 87 deletions(-) delete mode 100644 docs/features/ollama-in-ai-providers/README.md delete mode 100644 packages/features/src/flags/ollama-in-ai-providers.ts diff --git a/apps/app/src/react-app/domains/session/status/use-desktop-feature.ts b/apps/app/src/react-app/domains/session/status/use-desktop-feature.ts index b398b7dbc5b..bd2a2145ffd 100644 --- a/apps/app/src/react-app/domains/session/status/use-desktop-feature.ts +++ b/apps/app/src/react-app/domains/session/status/use-desktop-feature.ts @@ -8,7 +8,7 @@ import { useAutomationsDenContext } from "@/react-app/domains/automations/use-au const FEATURE_STALE_MS = 30_000 -type DesktopFeatureKey = "remoteComputers" | "followUpSuggestions" | "questionMinimize" | "importOpencodeConnections" | "ollamaInAiProviders" +type DesktopFeatureKey = "remoteComputers" | "followUpSuggestions" | "questionMinimize" | "importOpencodeConnections" /** * Reads one desktop feature from the registry. Signed in, it follows the diff --git a/apps/app/src/react-app/domains/settings/pages/ai-view.tsx b/apps/app/src/react-app/domains/settings/pages/ai-view.tsx index 531aebc7282..eb1095a5cb3 100644 --- a/apps/app/src/react-app/domains/settings/pages/ai-view.tsx +++ b/apps/app/src/react-app/domains/settings/pages/ai-view.tsx @@ -77,7 +77,7 @@ export type AiSettingsViewProps = { /** Device provider sharing: which keys on this device can be shared with the organization, or already are. */ shareRows?: ReadonlyMap; onShareProvider?: (provider: { id: string; name: string }) => void; - /** Feature `ollamaInAiProviders`: opens the Ollama page from the bottom of AI Providers. */ + /** Opens the Ollama page from the bottom of AI Providers. */ onUseOllama?: () => void; }; diff --git a/apps/app/src/react-app/domains/settings/shell/settings-page.tsx b/apps/app/src/react-app/domains/settings/shell/settings-page.tsx index 4642a48b773..b99ba660928 100644 --- a/apps/app/src/react-app/domains/settings/shell/settings-page.tsx +++ b/apps/app/src/react-app/domains/settings/shell/settings-page.tsx @@ -51,7 +51,6 @@ import { isElectronRuntime } from "../../../../app/utils"; import { cn } from "@/lib/utils"; import { usePlatform } from "../../../kernel/platform"; import { useCheckDesktopRestriction } from "../../cloud/desktop-config-provider"; -import { useDesktopFeature } from "../../session/status/use-desktop-feature"; import { SettingsContent, SettingsPanel, @@ -221,12 +220,10 @@ export function getWorkspaceSettingsTabs(): SettingsTab[] { export function getGlobalSettingsTabs( developerMode: boolean, capabilities: Pick, - options: { desktop?: boolean; ollamaInAiProviders?: boolean } = {}, + options: { desktop?: boolean } = {}, ): SettingsTab[] { - // Feature `ollamaInAiProviders`: Ollama opens from a button on AI Providers instead of the sidebar. - const tabs: SettingsTab[] = options.ollamaInAiProviders - ? ["ai", "appearance", "shortcuts", "environment"] - : ["ai", "ollama", "appearance", "shortcuts", "environment"]; + // Ollama opens from a button on AI Providers, not from the sidebar. + const tabs: SettingsTab[] = ["ai", "appearance", "shortcuts", "environment"]; if (options.desktop) tabs.push("computer"); if (capabilities.autoUpdate) tabs.push("updates"); if (developerMode) tabs.push("debug"); @@ -242,12 +239,9 @@ export function isSettingsTabBeta(_tab: SettingsTab) { return false; } -export function isSettingsTabActive( - activeTab: SettingsTab, - tab: SettingsTab, - options: { ollamaInAiProviders?: boolean } = {}, -) { - if (options.ollamaInAiProviders && activeTab === "ollama") return tab === "ai"; +export function isSettingsTabActive(activeTab: SettingsTab, tab: SettingsTab) { + // The Ollama page is reached from AI Providers, which stays highlighted there. + if (activeTab === "ollama") return tab === "ai"; return activeTab === tab; } @@ -278,8 +272,6 @@ export type SettingsNavGroups = { workspace: SettingsTab[]; global: SettingsTab[]; cloud: SettingsTab[]; - /** Feature `ollamaInAiProviders`: the Ollama page is reached from AI Providers, which stays highlighted there. */ - ollamaInAiProviders: boolean; }; /** @@ -291,15 +283,13 @@ export type SettingsNavGroups = { export function useSettingsNavGroups(developerMode: boolean): SettingsNavGroups { const platform = usePlatform(); const checkRestriction = useCheckDesktopRestriction(); - const ollamaInAiProviders = useDesktopFeature("ollamaInAiProviders"); const allowed = (tabs: SettingsTab[]) => tabs.filter((tab) => isSettingsTabAllowed({ tab, checkRestriction })); return { hub: allowed(["general"]), workspace: allowed(getWorkspaceSettingsTabs()), - global: allowed(getGlobalSettingsTabs(developerMode, platform.capabilities, { desktop: isElectronRuntime(), ollamaInAiProviders })), + global: allowed(getGlobalSettingsTabs(developerMode, platform.capabilities, { desktop: isElectronRuntime() })), cloud: allowed([...CLOUD_SETTINGS_TABS, "devices"]), - ollamaInAiProviders, }; } @@ -333,7 +323,6 @@ function SettingsSidebarGroup(props: { tabs: SettingsTab[]; activeTab: SettingsTab; onSelectTab: (tab: SettingsTab) => void; - ollamaInAiProviders: boolean; }) { if (props.tabs.length === 0) return null; return ( @@ -346,7 +335,7 @@ function SettingsSidebarGroup(props: { return ( } @@ -427,21 +416,18 @@ export function SettingsSidebar(props: SettingsSidebarProps) { tabs={groups.workspace} activeTab={props.activeTab} onSelectTab={props.onSelectTab} - ollamaInAiProviders={groups.ollamaInAiProviders} /> diff --git a/apps/app/src/react-app/domains/settings/shell/settings-shell.tsx b/apps/app/src/react-app/domains/settings/shell/settings-shell.tsx index 52ad83691cf..d49667bc84c 100644 --- a/apps/app/src/react-app/domains/settings/shell/settings-shell.tsx +++ b/apps/app/src/react-app/domains/settings/shell/settings-shell.tsx @@ -195,7 +195,7 @@ function SettingsSectionMenu(props: Pick props.onSelectTab(tab)} - className={isSettingsTabActive(props.activeTab, tab, { ollamaInAiProviders: groups.ollamaInAiProviders }) ? "bg-foreground/10 text-accent-foreground" : undefined} + className={isSettingsTabActive(props.activeTab, tab) ? "bg-foreground/10 text-accent-foreground" : undefined} > {getSettingsTabLabel(tab)} diff --git a/apps/app/src/react-app/shell/command-palette-settings.ts b/apps/app/src/react-app/shell/command-palette-settings.ts index 34eb68bfaf9..9f0d61605bc 100644 --- a/apps/app/src/react-app/shell/command-palette-settings.ts +++ b/apps/app/src/react-app/shell/command-palette-settings.ts @@ -15,6 +15,7 @@ import { ADVANCED_SETTINGS_SECTIONS } from "@/react-app/domains/settings/advance import type { PaletteItem } from "./command-palette-search"; const SETTINGS_KEYWORDS: Partial> = { + ollama: ["local models", "llama", "offline", "on device"], ai: ["provider", "providers", "api key", "anthropic", "openai", "openrouter", "google", "gemini", "claude", "gpt", "connect model", "models"], preferences: ["default model", "reasoning", "thinking", "compaction", "effort"], permissions: ["authorized folders", "folder access", "file access", "allow", "permission denied", "sandbox", "approvals"], @@ -52,6 +53,8 @@ export function buildCommandPaletteSettingsItems(input: { "general", ...getWorkspaceSettingsTabs(), ...getGlobalSettingsTabs(input.developerMode, input.capabilities), + // Not in the sidebar (opened from AI Providers), but still searchable. + "ollama", ...CLOUD_SETTINGS_TABS, ] satisfies SettingsTab[]).filter((tab) => isSettingsTabAllowed({ tab, checkRestriction })); diff --git a/apps/app/src/react-app/shell/settings-route.tsx b/apps/app/src/react-app/shell/settings-route.tsx index 96c19dc4939..0b9334b77cc 100644 --- a/apps/app/src/react-app/shell/settings-route.tsx +++ b/apps/app/src/react-app/shell/settings-route.tsx @@ -1171,7 +1171,6 @@ function SettingsRouteContent(props: SettingsSurfaceProps = {}) { const runtimeWorkspaceId = selectedWorkspaceEndpoint?.workspaceId ?? selectedWorkspace?.id ?? null; const importOpencodeConnectionsEnabled = useDesktopFeature("importOpencodeConnections"); - const ollamaInAiProvidersEnabled = useDesktopFeature("ollamaInAiProviders"); const opencodeConnectionsImport = useMemo(() => { if (!importOpencodeConnectionsEnabled || !openworkClient || !runtimeWorkspaceId) return undefined; return { @@ -2395,7 +2394,7 @@ function SettingsRouteContent(props: SettingsSurfaceProps = {}) { showOpenWorkModelsDeviceRow={openWorkModelsRow.showDeviceRow} organizationProviderIds={organizationProviderIds} onOpenDen={openProviderCloudPage} - onUseOllama={ollamaInAiProvidersEnabled && !checkDesktopRestriction({ restriction: "allowLocalModels" }) + onUseOllama={!checkDesktopRestriction({ restriction: "allowLocalModels" }) ? () => navigateSettingsPath("ollama") : undefined} cloudProvidersView={ diff --git a/apps/app/tests/ollama-in-ai-providers.test.tsx b/apps/app/tests/ollama-in-ai-providers.test.tsx index 863df1cd02a..801af7e249c 100644 --- a/apps/app/tests/ollama-in-ai-providers.test.tsx +++ b/apps/app/tests/ollama-in-ai-providers.test.tsx @@ -6,6 +6,7 @@ import { renderToStaticMarkup } from "react-dom/server"; const ownedDom = typeof window === "undefined"; if (ownedDom) GlobalRegistrator.register({ url: "http://localhost/" }); const { getGlobalSettingsTabs, isSettingsTabActive } = await import("../src/react-app/domains/settings/shell/settings-page"); +const { buildCommandPaletteSettingsItems } = await import("../src/react-app/shell/command-palette-settings"); const { AiSettingsView } = await import("../src/react-app/domains/settings/pages/ai-view"); type AiSettingsViewProps = import("../src/react-app/domains/settings/pages/ai-view").AiSettingsViewProps; @@ -13,19 +14,23 @@ afterAll(async () => { if (ownedDom) await GlobalRegistrator.unregister(); }); -test("off, Ollama stays its own settings page in the sidebar", () => { - expect(getGlobalSettingsTabs(false, { autoUpdate: false })).toContain("ollama"); - expect(isSettingsTabActive("ollama", "ollama")).toBe(true); - expect(isSettingsTabActive("ollama", "ai")).toBe(false); -}); - -test("on, the sidebar drops Ollama and highlights AI Providers while the Ollama page is open", () => { - const tabs = getGlobalSettingsTabs(false, { autoUpdate: false }, { ollamaInAiProviders: true }); +test("the sidebar drops Ollama and highlights AI Providers while the Ollama page is open", () => { + const tabs = getGlobalSettingsTabs(false, { autoUpdate: false }); expect(tabs).not.toContain("ollama"); expect(tabs).toContain("ai"); - expect(isSettingsTabActive("ollama", "ai", { ollamaInAiProviders: true })).toBe(true); - expect(isSettingsTabActive("ollama", "ollama", { ollamaInAiProviders: true })).toBe(false); - expect(isSettingsTabActive("appearance", "ai", { ollamaInAiProviders: true })).toBe(false); + expect(isSettingsTabActive("ollama", "ai")).toBe(true); + expect(isSettingsTabActive("ollama", "ollama")).toBe(false); + expect(isSettingsTabActive("appearance", "ai")).toBe(false); +}); + +test("the command palette still finds the Ollama page", () => { + const items = buildCommandPaletteSettingsItems({ + developerMode: false, + capabilities: { autoUpdate: false }, + onOpenSettings: () => {}, + onOpenExtensions: () => {}, + }); + expect(items.some((item) => item.id === "settings:ollama")).toBe(true); }); const view: AiSettingsViewProps = { diff --git a/docs/features/ollama-in-ai-providers/README.md b/docs/features/ollama-in-ai-providers/README.md deleted file mode 100644 index 8b349b5d2e3..00000000000 --- a/docs/features/ollama-in-ai-providers/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# Ollama under AI Providers (`ollamaInAiProviders`) - -- **Feature key:** `ollamaInAiProviders` -- **Deployments:** cloud and self-hosted (`everywhere`), off by default -- **Author:** @OmarMcAdam - -## What people get - -Off, desktop Settings lists **Ollama** as its own page in the Global group of -the sidebar, next to **AI Providers**. - -On, the sidebar (and the compact section menu) no longer lists Ollama. Ollama -is a way to get models, so it lives with the other providers: the bottom of -**AI Providers** has a secondary **Use Ollama** button that opens the same -Ollama page as before (`/settings/ollama`). While the Ollama page is open, the -sidebar highlights **AI Providers**. - -The Ollama page itself is unchanged: install, sync and manage local models -work exactly as before. The command palette still finds the Ollama page. - -When the organization turns off local models (`allowLocalModels`), the -**Use Ollama** button is not shown, and opening `/settings/ollama` directly -still shows the existing "blocked by your organization" notice. - -## Off - -Turning it off brings back the Ollama sidebar entry and removes the button. It -is safe to turn off at any moment: nothing is stored, and Ollama models a -person already set up keep working, since only navigation changes. - -## Try it - -Turn the feature on for an organization, open desktop Settings, check the -sidebar has no Ollama entry, open **AI Providers**, scroll to the bottom and -click **Use Ollama**. diff --git a/packages/features/src/all-flags.ts b/packages/features/src/all-flags.ts index 155d7357c72..71fb6507411 100644 --- a/packages/features/src/all-flags.ts +++ b/packages/features/src/all-flags.ts @@ -13,7 +13,6 @@ export { default as importOpencodeConnections } from "@openwork/features/flags/i export { default as managedDeployments } from "@openwork/features/flags/managed-deployments" export { default as mcpThreadEvents } from "@openwork/features/flags/mcp-thread-events" export { default as nativeMcpEvents } from "@openwork/features/flags/native-mcp-events" -export { default as ollamaInAiProviders } from "@openwork/features/flags/ollama-in-ai-providers" export { default as opencodeV2Only } from "@openwork/features/flags/opencode-v2-only" export { default as questionMinimize } from "@openwork/features/flags/question-minimize" export { default as remoteComputers } from "@openwork/features/flags/remote-computers" diff --git a/packages/features/src/flags/ollama-in-ai-providers.ts b/packages/features/src/flags/ollama-in-ai-providers.ts deleted file mode 100644 index 2c454de742b..00000000000 --- a/packages/features/src/flags/ollama-in-ai-providers.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { defineFeature, everywhere } from "@openwork/features/define" - -export default defineFeature({ - label: "Desktop: Ollama under AI Providers", - description: "Settings no longer lists Ollama as its own page in the sidebar. Instead, AI Providers has a Use Ollama button at the bottom that opens the same Ollama page. Turning it off brings back the Ollama entry in the sidebar, as before.", - category: "models", - added: "2026-11-13", - since: "2026-11", - author: "OmarMcAdam", - docs: "docs/features/ollama-in-ai-providers/README.md", - deployments: everywhere, - default: false, -})