Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions apps/desktop/src/components/settings/ai/llm/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,12 @@ import { useAuth } from "../../../../auth";
import { useBillingAccess } from "../../../../billing";
import { useConfigValues } from "../../../../config/use-config";
import * as main from "../../../../store/tinybase/main";
import { listAnthropicModels } from "../shared/list-anthropic";
import type { ListModelsResult } from "../shared/list-common";
import { listGoogleModels } from "../shared/list-google";
import { listLMStudioModels } from "../shared/list-lmstudio";
import { listOllamaModels } from "../shared/list-ollama";
import {
listAnthropicModels,
listGenericModels,
listOpenAIModels,
} from "../shared/list-openai";
import { listGenericModels, listOpenAIModels } from "../shared/list-openai";
import { listOpenRouterModels } from "../shared/list-openrouter";
import { ModelCombobox } from "../shared/model-combobox";
import { HealthCheckForConnection } from "./health";
Expand Down
59 changes: 59 additions & 0 deletions apps/desktop/src/components/settings/ai/shared/list-anthropic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Effect, pipe, Schema } from "effect";

import {
DEFAULT_RESULT,
fetchJson,
type ListModelsResult,
type ModelIgnoreReason,
partition,
REQUEST_TIMEOUT,
shouldIgnoreCommonKeywords,
} from "./list-common";

const AnthropicModelSchema = Schema.Struct({
data: Schema.Array(
Schema.Struct({
type: Schema.String,
id: Schema.String,
display_name: Schema.String,
created_at: Schema.String,
}),
),
has_more: Schema.Boolean,
first_id: Schema.String,
last_id: Schema.String,
});

export async function listAnthropicModels(
baseUrl: string,
apiKey: string,
): Promise<ListModelsResult> {
if (!baseUrl) {
return DEFAULT_RESULT;
}

return pipe(
fetchJson(`${baseUrl}/models`, {
"x-api-key": apiKey,
"anthropic-version": "2023-06-01",
"anthropic-dangerous-direct-browser-access": "true",
}),
Effect.andThen((json) => Schema.decodeUnknown(AnthropicModelSchema)(json)),
Effect.map(({ data }) =>
partition(
data,
(model) => {
const reasons: ModelIgnoreReason[] = [];
if (shouldIgnoreCommonKeywords(model.id)) {
reasons.push("common_keyword");
}
return reasons.length > 0 ? reasons : null;
},
(model) => model.id,
),
),
Effect.timeout(REQUEST_TIMEOUT),
Effect.catchAll(() => Effect.succeed(DEFAULT_RESULT)),
Effect.runPromise,
);
}
18 changes: 10 additions & 8 deletions apps/desktop/src/components/settings/ai/shared/list-common.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { fetch as tauriFetch } from "@tauri-apps/plugin-http";
import { Effect } from "effect";

export type ModelIgnoreReason =
Expand Down Expand Up @@ -32,14 +33,15 @@ export const commonIgnoreKeywords = [

export const fetchJson = (url: string, headers: Record<string, string>) =>
Effect.tryPromise({
try: () =>
fetch(url, { headers }).then((r) => {
if (!r.ok) {
throw new Error(`HTTP ${r.status}`);
}
return r.json();
}),
catch: () => new Error("Fetch failed"),
try: async () => {
const r = await tauriFetch(url, { method: "GET", headers });
if (!r.ok) {
const errorBody = await r.text();
throw new Error(`HTTP ${r.status}: ${errorBody}`);
}
return r.json();
},
catch: (e) => e,
});

export const shouldIgnoreCommonKeywords = (id: string): boolean => {
Expand Down
30 changes: 0 additions & 30 deletions apps/desktop/src/components/settings/ai/shared/list-openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,36 +48,6 @@ export async function listOpenAIModels(
);
}

export async function listAnthropicModels(
baseUrl: string,
apiKey: string,
): Promise<ListModelsResult> {
if (!baseUrl) {
return DEFAULT_RESULT;
}

return pipe(
fetchJson(`${baseUrl}/models`, { Authorization: `Bearer ${apiKey}` }),
Effect.andThen((json) => Schema.decodeUnknown(OpenAIModelSchema)(json)),
Effect.map(({ data }) =>
partition(
data,
(model) => {
const reasons: ModelIgnoreReason[] = [];
if (shouldIgnoreCommonKeywords(model.id)) {
reasons.push("common_keyword");
}
return reasons.length > 0 ? reasons : null;
},
(model) => model.id,
),
),
Effect.timeout(REQUEST_TIMEOUT),
Effect.catchAll(() => Effect.succeed(DEFAULT_RESULT)),
Effect.runPromise,
);
}

export async function listGenericModels(
baseUrl: string,
apiKey: string,
Expand Down
Loading