Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .changeset/tall-points-hang.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@srcbook/web': patch
---

- Add essential support for OpenRouter models.
5 changes: 5 additions & 0 deletions .changeset/wicked-geckos-relate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'srcbook': patch
---

Add GitHub Pages documentation repository structure and configuration.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,7 @@ srcbook/lib/**/*
# Aide
*.code-workspace

# Docs folder
docs/

vite.config.ts.timestamp-*.mjs
23 changes: 23 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# SrcBook Development Guide

## Build & Development Commands
- **Install deps**: `pnpm install`
- **Development**: `pnpm dev`
- **Build**: `pnpm build`
- **Lint**: `pnpm lint`
- **Format check**: `pnpm check-format`
- **Format code**: `pnpm format`
- **Tests**: `pnpm test`
- **Single test**: `pnpm --filter <package> vitest run <test-file> [-t "test name"]`

## Code Style Guidelines
- **Package manager**: pnpm with workspace support
- **Structure**: Monorepo using Turborepo
- **TypeScript**: Strict typing, ES2022 target, ESNext modules
- **Formatting**: Prettier with 2-space indentation, 100 char line limit, semicolons required
- **Imports**: Group imports by external/internal, no unused imports
- **Naming**: camelCase for variables/functions, PascalCase for classes/components/types
- **Error handling**: Prefer Result types over try/catch when appropriate
- **React**: Functional components with hooks, prefer composition over inheritance
- **File extensions**: `.mts` for TypeScript modules, `.tsx` for React components
- **Testing**: Vitest for unit tests, files named `*.test.mts`
11 changes: 11 additions & 0 deletions packages/api/ai/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,17 @@ export async function getModel(): Promise<LanguageModel> {
apiKey: config.xaiKey,
});
return xai(model);

case 'openrouter':
if (!config.openrouterKey) {
throw new Error('OpenRouter API key is not set');
}
const openrouter = createOpenAI({
compatibility: 'compatible',
baseURL: 'https://openrouter.ai/api/v1',
apiKey: config.openrouterKey,
});
return openrouter(model);

case 'custom':
if (typeof aiBaseUrl !== 'string') {
Expand Down
1 change: 1 addition & 0 deletions packages/api/db/schema.mts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const configs = sqliteTable('config', {
anthropicKey: text('anthropic_api_key'),
xaiKey: text('xai_api_key'),
geminiKey: text('gemini_api_key'),
openrouterKey: text('openrouter_api_key'),
customApiKey: text('custom_api_key'),
// TODO: This is deprecated in favor of SRCBOOK_DISABLE_ANALYTICS env variable. Remove this.
enabledAnalytics: integer('enabled_analytics', { mode: 'boolean' }).notNull().default(true),
Expand Down
1 change: 1 addition & 0 deletions packages/api/drizzle/0016_add_openrouter_api_key.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE `config` ADD `openrouter_api_key` text;
2 changes: 2 additions & 0 deletions packages/shared/src/ai.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const AiProvider = {
Anthropic: 'anthropic',
XAI: 'Xai',
Gemini: 'Gemini',
OpenRouter: 'openrouter',
Custom: 'custom',
} as const;

Expand All @@ -14,6 +15,7 @@ export const defaultModels: Record<AiProviderType, string> = {
[AiProvider.Custom]: 'mistral-nemo',
[AiProvider.XAI]: 'grok-beta',
[AiProvider.Gemini]: 'gemini-1.5-pro-latest',
[AiProvider.OpenRouter]: 'anthropic/claude-3-opus-20240229',
} as const;

export function isValidProvider(provider: string): provider is AiProviderType {
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/components/use-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export function SettingsProvider({ config, children }: ProviderPropsType) {
(config.anthropicKey && config.aiProvider === 'anthropic') ||
(config.xaiKey && config.aiProvider === 'Xai') ||
(config.geminiKey && config.aiProvider === 'Gemini') ||
(config.openrouterKey && config.aiProvider === 'openrouter') ||
(config.aiProvider === 'custom' && !!config.aiBaseUrl) ||
false;

Expand Down
39 changes: 38 additions & 1 deletion packages/web/src/routes/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,16 @@ function AiInfoBanner() {
</div>
);

case 'openrouter':
return (
<div className="flex items-center gap-10 bg-sb-yellow-20 text-sb-yellow-80 rounded-sm text-sm font-medium px-3 py-2">
<p>API key required</p>
<a href="https://openrouter.ai/keys" target="_blank" className="underline">
Go to {aiProvider}
</a>
</div>
);

case 'custom':
return (
<div className="flex items-center gap-10 bg-sb-yellow-20 text-sb-yellow-80 rounded-sm text-sm font-medium px-3 py-2">
Expand Down Expand Up @@ -244,13 +254,15 @@ export function AiSettings({ saveButtonLabel }: AiSettingsProps) {
customApiKey: configCustomApiKey,
xaiKey: configXaiKey,
geminiKey: configGeminiKey,
openrouterKey: configOpenrouterKey,
updateConfig: updateConfigContext,
} = useSettings();

const [openaiKey, setOpenaiKey] = useState<string>(configOpenaiKey ?? '');
const [anthropicKey, setAnthropicKey] = useState<string>(configAnthropicKey ?? '');
const [xaiKey, setXaiKey] = useState<string>(configXaiKey ?? '');
const [geminiKey, setGeminiKey] = useState<string>(configGeminiKey ?? '');
const [openrouterKey, setOpenrouterKey] = useState<string>(configOpenrouterKey ?? '');
const [customApiKey, setCustomApiKey] = useState<string>(configCustomApiKey ?? '');
const [model, setModel] = useState<string>(aiModel);
const [baseUrl, setBaseUrl] = useState<string>(aiBaseUrl || '');
Expand Down Expand Up @@ -280,10 +292,15 @@ export function AiSettings({ saveButtonLabel }: AiSettingsProps) {
model !== aiModel;

const geminiKeySaveEnabled =
(typeof configGeminiKey === 'string' && geminiKey !== configXaiKey) ||
(typeof configGeminiKey === 'string' && geminiKey !== configGeminiKey) ||
((configGeminiKey === null || configGeminiKey === undefined) && geminiKey.length > 0) ||
model !== aiModel;

const openrouterKeySaveEnabled =
(typeof configOpenrouterKey === 'string' && openrouterKey !== configOpenrouterKey) ||
((configOpenrouterKey === null || configOpenrouterKey === undefined) && openrouterKey.length > 0) ||
model !== aiModel;

const customModelSaveEnabled =
(typeof configCustomApiKey === 'string' && customApiKey !== configCustomApiKey) ||
((configCustomApiKey === null || configCustomApiKey === undefined) &&
Expand All @@ -305,6 +322,7 @@ export function AiSettings({ saveButtonLabel }: AiSettingsProps) {
<SelectItem value="anthropic">anthropic</SelectItem>
<SelectItem value="Xai">Xai</SelectItem>
<SelectItem value="Gemini">Gemini</SelectItem>
<SelectItem value="openrouter">openrouter</SelectItem>
<SelectItem value="custom">custom</SelectItem>
</SelectContent>
</Select>
Expand Down Expand Up @@ -395,6 +413,25 @@ export function AiSettings({ saveButtonLabel }: AiSettingsProps) {
</div>
)}

{aiProvider === 'openrouter' && (
<div className="flex gap-2">
<Input
name="openrouterKey"
placeholder="OpenRouter API key"
type="password"
value={openrouterKey}
onChange={(e) => setOpenrouterKey(e.target.value)}
/>
<Button
className="px-5"
onClick={() => updateConfigContext({ openrouterKey, aiModel: model })}
disabled={!openrouterKeySaveEnabled}
>
{saveButtonLabel ?? 'Save'}
</Button>
</div>
)}

{aiProvider === 'custom' && (
<div>
<p className="opacity-70 text-sm mb-4">
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export type SettingsType = {
anthropicKey?: string | null;
xaiKey?: string | null;
geminiKey?: string | null;
openrouterKey?: string | null;
aiProvider: AiProviderType;
customApiKey: string | null;
aiModel: string;
Expand Down
Loading