Skip to content
Open
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
200 changes: 200 additions & 0 deletions src/brands/deepseek.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import type { TweakccConfig, Theme } from './types.js';
import { DEFAULT_THEMES } from './defaultThemes.js';
import { buildBrandMiscConfig } from './miscDefaults.js';
import { buildDiffPalette } from './diffPalette.js';
import { formatUserMessage, getUserLabel } from './userLabel.js';

type Rgb = { r: number; g: number; b: number };

const clamp = (value: number) => Math.max(0, Math.min(255, Math.round(value)));

const hexToRgb = (hex: string): Rgb => {
const normalized = hex.replace('#', '').trim();
if (normalized.length === 3) {
const [r, g, b] = normalized.split('');
return {
r: clamp(parseInt(r + r, 16)),
g: clamp(parseInt(g + g, 16)),
b: clamp(parseInt(b + b, 16)),
};
}
if (normalized.length !== 6) {
throw new Error(`Unsupported hex color: ${hex}`);
}
return {
r: clamp(parseInt(normalized.slice(0, 2), 16)),
g: clamp(parseInt(normalized.slice(2, 4), 16)),
b: clamp(parseInt(normalized.slice(4, 6), 16)),
};
};

const rgb = (hex: string) => {
const { r, g, b } = hexToRgb(hex);
return `rgb(${r},${g},${b})`;
};

const mix = (hexA: string, hexB: string, weight: number) => {
const a = hexToRgb(hexA);
const b = hexToRgb(hexB);
const w = Math.max(0, Math.min(1, weight));
return `rgb(${clamp(a.r + (b.r - a.r) * w)},${clamp(a.g + (b.g - a.g) * w)},${clamp(a.b + (b.b - a.b) * w)})`;
};

const lighten = (hex: string, weight: number) => mix(hex, '#ffffff', weight);

type Palette = {
base: string;
surface: string;
panel: string;
border: string;
borderStrong: string;
text: string;
textMuted: string;
textDim: string;
core: string;
deep: string;
orange: string;
yellow: string;
cyan: string;
blue: string;
green: string;
red: string;
};

const DEEPSEEK_CORE = '#4d9de0';

const palette: Palette = {
base: '#080c14',
surface: '#0e1420',
panel: '#141c2c',
border: '#1e2e48',
borderStrong: '#2a4060',
text: '#e8eef8',
textMuted: '#b0c0d8',
textDim: '#6888a8',
core: DEEPSEEK_CORE,
deep: '#2563eb',
orange: '#f59e0b',
yellow: '#fbbf24',
cyan: '#06b6d4',
blue: '#3b82f6',
green: '#10b981',
red: '#ef4444',
};

const makeTheme = (): Theme => {
const tint = (hex: string, weight: number) => mix(palette.base, hex, weight);
return {
name: 'DeepSeek Abyss',
id: 'dark',
colors: {
autoAccept: rgb(palette.green),
bashBorder: rgb(palette.core),
claude: rgb(palette.core),
claudeShimmer: lighten(palette.core, 0.28),
claudeBlue_FOR_SYSTEM_SPINNER: rgb(palette.blue),
claudeBlueShimmer_FOR_SYSTEM_SPINNER: lighten(palette.blue, 0.3),
permission: rgb(palette.orange),
permissionShimmer: lighten(palette.orange, 0.25),
planMode: rgb(palette.core),
ide: rgb(palette.cyan),
promptBorder: rgb(palette.border),
promptBorderShimmer: rgb(palette.borderStrong),
text: rgb(palette.text),
inverseText: rgb(palette.base),
inactive: rgb(palette.textDim),
subtle: tint(palette.core, 0.18),
suggestion: rgb(palette.deep),
remember: rgb(palette.core),
background: rgb(palette.base),
success: rgb(palette.green),
error: rgb(palette.red),
warning: rgb(palette.yellow),
warningShimmer: lighten(palette.yellow, 0.2),
...buildDiffPalette(),
red_FOR_SUBAGENTS_ONLY: rgb(palette.red),
blue_FOR_SUBAGENTS_ONLY: rgb(palette.blue),
green_FOR_SUBAGENTS_ONLY: rgb(palette.green),
yellow_FOR_SUBAGENTS_ONLY: rgb(palette.yellow),
purple_FOR_SUBAGENTS_ONLY: rgb(palette.deep),
orange_FOR_SUBAGENTS_ONLY: rgb(palette.orange),
pink_FOR_SUBAGENTS_ONLY: rgb(palette.core),
cyan_FOR_SUBAGENTS_ONLY: rgb(palette.cyan),
professionalBlue: rgb(palette.blue),
rainbow_red: rgb(palette.red),
rainbow_orange: rgb(palette.orange),
rainbow_yellow: rgb(palette.yellow),
rainbow_green: rgb(palette.green),
rainbow_blue: rgb(palette.cyan),
rainbow_indigo: rgb(palette.blue),
rainbow_violet: rgb(palette.core),
rainbow_red_shimmer: lighten(palette.red, 0.35),
rainbow_orange_shimmer: lighten(palette.orange, 0.35),
rainbow_yellow_shimmer: lighten(palette.yellow, 0.25),
rainbow_green_shimmer: lighten(palette.green, 0.35),
rainbow_blue_shimmer: lighten(palette.cyan, 0.35),
rainbow_indigo_shimmer: lighten(palette.blue, 0.35),
rainbow_violet_shimmer: lighten(palette.core, 0.35),
clawd_body: rgb(palette.core),
clawd_background: rgb(palette.base),
userMessageBackground: mix('#383838', palette.core, 0.12),
bashMessageBackgroundColor: mix('#404040', palette.core, 0.08),
memoryBackgroundColor: mix('#383838', palette.cyan, 0.1),
rate_limit_fill: rgb(palette.core),
rate_limit_empty: rgb(palette.borderStrong),
},
};
};

const abyssTheme = makeTheme();

export const buildDeepSeekTweakccConfig = (): TweakccConfig => ({
ccVersion: '',
ccInstallationPath: null,
lastModified: new Date().toISOString(),
changesApplied: false,
hidePiebaldAnnouncement: true,
settings: {
themes: [abyssTheme, ...DEFAULT_THEMES.filter((t) => t.id !== abyssTheme.id)],
thinkingVerbs: {
format: '{}... ',
verbs: [
'Diving',
'Surfacing',
'Probing',
'Seeking',
'Fathoming',
'Sounding',
'Dredging',
'Plumbing',
'Trawling',
'Charting',
'Scanning',
'Tracing',
'Mapping',
'Resolving',
],
},
thinkingStyle: {
updateInterval: 80,
phases: ['~', '≈', '~', '≋', '~', '≈'],
reverseMirror: false,
},
userMessageDisplay: {
format: formatUserMessage(getUserLabel()),
styling: ['bold'],
foregroundColor: 'default',
backgroundColor: 'default',
borderStyle: 'topBottomDouble',
borderColor: rgb(palette.core),
paddingX: 1,
paddingY: 0,
fitBoxToContent: true,
},
inputBox: {
removeBorder: true,
},
misc: buildBrandMiscConfig(),
claudeMdAltNames: null,
},
});
7 changes: 7 additions & 0 deletions src/brands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { buildOllamaTweakccConfig } from './ollama.js';
import { buildGatewayzTweakccConfig } from './gatewayz.js';
import { buildVercelTweakccConfig } from './vercel.js';
import { buildNanoGPTTweakccConfig } from './nanogpt.js';
import { buildDeepSeekTweakccConfig } from './deepseek.js';

export interface BrandPreset {
key: string;
Expand All @@ -30,6 +31,12 @@ const BRAND_PRESETS: Record<string, BrandPreset> = {
description: 'Electric violet + neon cyberpunk palette',
buildTweakccConfig: buildMinimaxTweakccConfig,
},
deepseek: {
key: 'deepseek',
label: 'DeepSeek Abyss',
description: 'Deep ocean blue palette with teal accents',
buildTweakccConfig: buildDeepSeekTweakccConfig,
},
kimi: {
key: 'kimi',
label: 'Kimi Teal',
Expand Down
20 changes: 20 additions & 0 deletions src/providers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const PROVIDER_DISPLAY_ORDER = [
'kimi',
'minimax',
'zai',
'deepseek',
'openrouter',
'vercel',
'ollama',
Expand Down Expand Up @@ -124,6 +125,25 @@ const PROVIDERS: Record<string, ProviderTemplate> = {
},
apiKeyLabel: 'Kimi API key',
},
deepseek: {
key: 'deepseek',
label: 'DeepSeek',
description: 'DeepSeek-V3.2 via DeepSeek Anthropic API',
baseUrl: 'https://api.deepseek.com/anthropic',
env: {
API_TIMEOUT_MS: '600000',
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 1,
ANTHROPIC_MODEL: 'deepseek-chat',
ANTHROPIC_SMALL_FAST_MODEL: 'deepseek-chat',
ANTHROPIC_DEFAULT_SONNET_MODEL: 'deepseek-chat',
ANTHROPIC_DEFAULT_OPUS_MODEL: 'deepseek-reasoner',
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'deepseek-chat',
CC_MIRROR_SPLASH: 1,
CC_MIRROR_PROVIDER_LABEL: 'DeepSeek',
CC_MIRROR_SPLASH_STYLE: 'deepseek',
},
apiKeyLabel: 'DeepSeek API key',
},
openrouter: {
key: 'openrouter',
label: 'OpenRouter',
Expand Down
27 changes: 27 additions & 0 deletions src/tui/content/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,32 @@ export const PROVIDER_EDUCATION: Record<string, ProviderEducation> = {
'Subscribe to Kimi Code, create an API key in the console, and set ANTHROPIC_BASE_URL to https://api.kimi.com/coding/.',
},

deepseek: {
headline: 'DeepSeek — Deep Seek, Deep Think',
tagline: 'Ocean depths, sharp reasoning',
features: [
'Official Anthropic-compatible API endpoint',
'deepseek-reasoner for Opus (reasoning mode)',
'deepseek-chat for Sonnet/Haiku (fast mode)',
'DeepSeek-V3.2 under the hood',
'Ocean blue-themed interface',
],
bestFor: 'Cost-effective reasoning and coding with DeepSeek V3.2',
models: {
opus: 'deepseek-reasoner',
sonnet: 'deepseek-chat',
haiku: 'deepseek-chat',
},
requiresMapping: false,
hasPromptPack: false,
setupLinks: {
subscribe: 'https://platform.deepseek.com',
apiKey: 'https://platform.deepseek.com/api_keys',
docs: 'https://api-docs.deepseek.com/guides/anthropic_api',
},
setupNote: 'Create an account at platform.deepseek.com, top up credits, then generate an API key.',
},

openrouter: {
headline: 'OpenRouter — One API, Any Model',
tagline: 'Many paths, one door',
Expand Down Expand Up @@ -257,6 +283,7 @@ export const PROVIDER_COMPARISON = {
'kimi',
'minimax',
'zai',
'deepseek',
'openrouter',
'vercel',
'ollama',
Expand Down
5 changes: 5 additions & 0 deletions src/tui/screens/ModelConfigScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ function getPlaceholder(providerKey: string | undefined, model: 'opus' | 'sonnet
sonnet: 'MiniMax-M2.5',
haiku: 'MiniMax-M2.5',
},
deepseek: {
opus: 'deepseek-reasoner',
sonnet: 'deepseek-chat',
haiku: 'deepseek-chat',
},
openrouter: {
opus: 'anthropic/claude-3-opus',
sonnet: 'anthropic/claude-3.5-sonnet',
Expand Down