thatgfsj-code 3.0.4 → 3.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/README.md +64 -21
  2. package/dist/app/index.d.ts +93 -22
  3. package/dist/app/index.d.ts.map +1 -1
  4. package/dist/app/index.js +232 -40
  5. package/dist/app/index.js.map +1 -1
  6. package/dist/cmd/index.d.ts +12 -20
  7. package/dist/cmd/index.d.ts.map +1 -1
  8. package/dist/cmd/index.js +192 -146
  9. package/dist/cmd/index.js.map +1 -1
  10. package/dist/config/index.d.ts +4 -0
  11. package/dist/config/index.d.ts.map +1 -1
  12. package/dist/config/index.js +5 -0
  13. package/dist/config/index.js.map +1 -1
  14. package/dist/config/types.d.ts +5 -0
  15. package/dist/config/types.d.ts.map +1 -1
  16. package/dist/llm/anthropic.d.ts +1 -1
  17. package/dist/llm/anthropic.d.ts.map +1 -1
  18. package/dist/llm/anthropic.js +116 -84
  19. package/dist/llm/anthropic.js.map +1 -1
  20. package/dist/llm/gemini.d.ts +27 -3
  21. package/dist/llm/gemini.d.ts.map +1 -1
  22. package/dist/llm/gemini.js +196 -73
  23. package/dist/llm/gemini.js.map +1 -1
  24. package/dist/llm/index.d.ts +20 -1
  25. package/dist/llm/index.d.ts.map +1 -1
  26. package/dist/llm/index.js +66 -6
  27. package/dist/llm/index.js.map +1 -1
  28. package/dist/llm/openai.d.ts +5 -1
  29. package/dist/llm/openai.d.ts.map +1 -1
  30. package/dist/llm/openai.js +96 -56
  31. package/dist/llm/openai.js.map +1 -1
  32. package/dist/mcp/client.d.ts +84 -34
  33. package/dist/mcp/client.d.ts.map +1 -1
  34. package/dist/mcp/client.js +291 -126
  35. package/dist/mcp/client.js.map +1 -1
  36. package/dist/session/compactor.d.ts +27 -7
  37. package/dist/session/compactor.d.ts.map +1 -1
  38. package/dist/session/compactor.js +78 -23
  39. package/dist/session/compactor.js.map +1 -1
  40. package/dist/session/index.d.ts +96 -33
  41. package/dist/session/index.d.ts.map +1 -1
  42. package/dist/session/index.js +278 -60
  43. package/dist/session/index.js.map +1 -1
  44. package/dist/tools/file.d.ts +10 -2
  45. package/dist/tools/file.d.ts.map +1 -1
  46. package/dist/tools/file.js +54 -5
  47. package/dist/tools/file.js.map +1 -1
  48. package/dist/tools/git.d.ts +16 -2
  49. package/dist/tools/git.d.ts.map +1 -1
  50. package/dist/tools/git.js +61 -18
  51. package/dist/tools/git.js.map +1 -1
  52. package/dist/tools/nwt.d.ts.map +1 -1
  53. package/dist/tools/nwt.js +8 -1
  54. package/dist/tools/nwt.js.map +1 -1
  55. package/dist/tools/search.d.ts +8 -1
  56. package/dist/tools/search.d.ts.map +1 -1
  57. package/dist/tools/search.js +105 -34
  58. package/dist/tools/search.js.map +1 -1
  59. package/dist/tools/shell.d.ts +0 -4
  60. package/dist/tools/shell.d.ts.map +1 -1
  61. package/dist/tools/shell.js +11 -26
  62. package/dist/tools/shell.js.map +1 -1
  63. package/dist/tools/types.d.ts +7 -0
  64. package/dist/tools/types.d.ts.map +1 -1
  65. package/dist/tools/types.js.map +1 -1
  66. package/dist/tui/app.d.ts.map +1 -1
  67. package/dist/tui/app.js +90 -18
  68. package/dist/tui/app.js.map +1 -1
  69. package/dist/tui/components/ChatMessage.d.ts.map +1 -1
  70. package/dist/tui/components/ChatMessage.js +9 -2
  71. package/dist/tui/components/ChatMessage.js.map +1 -1
  72. package/dist/tui/components/ConfirmPrompt.d.ts +21 -0
  73. package/dist/tui/components/ConfirmPrompt.d.ts.map +1 -0
  74. package/dist/tui/components/ConfirmPrompt.js +45 -0
  75. package/dist/tui/components/ConfirmPrompt.js.map +1 -0
  76. package/dist/tui/components/Header.d.ts +10 -5
  77. package/dist/tui/components/Header.d.ts.map +1 -1
  78. package/dist/tui/components/Header.js +13 -5
  79. package/dist/tui/components/Header.js.map +1 -1
  80. package/dist/tui/components/StatusBar.d.ts +7 -0
  81. package/dist/tui/components/StatusBar.d.ts.map +1 -1
  82. package/dist/tui/components/StatusBar.js +8 -3
  83. package/dist/tui/components/StatusBar.js.map +1 -1
  84. package/dist/tui/components/Thinking.d.ts +5 -0
  85. package/dist/tui/components/Thinking.d.ts.map +1 -1
  86. package/dist/tui/components/Thinking.js +24 -2
  87. package/dist/tui/components/Thinking.js.map +1 -1
  88. package/dist/tui/components/ToolCall.d.ts +6 -1
  89. package/dist/tui/components/ToolCall.d.ts.map +1 -1
  90. package/dist/tui/components/ToolCall.js +50 -22
  91. package/dist/tui/components/ToolCall.js.map +1 -1
  92. package/dist/tui/components/UserInput.d.ts +5 -0
  93. package/dist/tui/components/UserInput.d.ts.map +1 -1
  94. package/dist/tui/components/UserInput.js +9 -13
  95. package/dist/tui/components/UserInput.js.map +1 -1
  96. package/dist/tui/hooks/useChat.d.ts +1 -0
  97. package/dist/tui/hooks/useChat.d.ts.map +1 -1
  98. package/dist/tui/hooks/useChat.js +36 -16
  99. package/dist/tui/hooks/useChat.js.map +1 -1
  100. package/dist/tui/hooks/useCommands.d.ts +7 -1
  101. package/dist/tui/hooks/useCommands.d.ts.map +1 -1
  102. package/dist/tui/hooks/useCommands.js +77 -41
  103. package/dist/tui/hooks/useCommands.js.map +1 -1
  104. package/dist/tui/theme.d.ts +34 -0
  105. package/dist/tui/theme.d.ts.map +1 -0
  106. package/dist/tui/theme.js +34 -0
  107. package/dist/tui/theme.js.map +1 -0
  108. package/dist/tui/welcome.d.ts.map +1 -1
  109. package/dist/tui/welcome.js +9 -7
  110. package/dist/tui/welcome.js.map +1 -1
  111. package/dist/types.d.ts +16 -1
  112. package/dist/types.d.ts.map +1 -1
  113. package/dist/utils/diff.d.ts +5 -0
  114. package/dist/utils/diff.d.ts.map +1 -1
  115. package/dist/utils/diff.js +15 -0
  116. package/dist/utils/diff.js.map +1 -1
  117. package/dist/version.d.ts +12 -0
  118. package/dist/version.d.ts.map +1 -0
  119. package/dist/version.js +29 -0
  120. package/dist/version.js.map +1 -0
  121. package/package.json +19 -7
  122. package/CHANGELOG.md +0 -192
  123. package/DEVELOPMENT.md +0 -286
  124. package/ROADMAP.md +0 -91
  125. package/docs/API_KEY_GUIDE.md +0 -236
  126. package/docs/FAQ.md +0 -182
  127. package/install.bat +0 -63
  128. package/install.ps1 +0 -238
  129. package/install.sh +0 -113
  130. package/src/app/index.ts +0 -197
  131. package/src/cache/fingerprint.ts +0 -101
  132. package/src/cache/index.ts +0 -21
  133. package/src/cache/smartModel.ts +0 -133
  134. package/src/cache/stats.ts +0 -199
  135. package/src/cache/volatile.ts +0 -47
  136. package/src/cmd/index.tsx +0 -288
  137. package/src/config/index.ts +0 -156
  138. package/src/config/providers.ts +0 -234
  139. package/src/config/types.ts +0 -71
  140. package/src/hooks/index.ts +0 -111
  141. package/src/llm/anthropic.ts +0 -413
  142. package/src/llm/gemini.ts +0 -169
  143. package/src/llm/index.ts +0 -303
  144. package/src/llm/openai.ts +0 -243
  145. package/src/llm/provider.ts +0 -71
  146. package/src/mcp/client.ts +0 -330
  147. package/src/prompts/index.ts +0 -260
  148. package/src/session/compactor.ts +0 -103
  149. package/src/session/index.ts +0 -181
  150. package/src/session/message.ts +0 -42
  151. package/src/skills/brainstorming.ts +0 -43
  152. package/src/skills/code-review.ts +0 -45
  153. package/src/skills/executing-plans.ts +0 -27
  154. package/src/skills/frontend-design.ts +0 -35
  155. package/src/skills/git-workflow.ts +0 -36
  156. package/src/skills/improve-architecture.ts +0 -38
  157. package/src/skills/index.ts +0 -136
  158. package/src/skills/neuroweave.ts +0 -47
  159. package/src/skills/playwright.ts +0 -72
  160. package/src/skills/prototype.ts +0 -30
  161. package/src/skills/subagent.ts +0 -28
  162. package/src/skills/supabase.ts +0 -44
  163. package/src/skills/systematic-debugging.ts +0 -44
  164. package/src/skills/tdd.ts +0 -39
  165. package/src/skills/triage.ts +0 -35
  166. package/src/skills/verification.ts +0 -31
  167. package/src/skills/writing-plans.ts +0 -42
  168. package/src/tools/file.ts +0 -143
  169. package/src/tools/git.ts +0 -132
  170. package/src/tools/index.ts +0 -111
  171. package/src/tools/nwt.ts +0 -598
  172. package/src/tools/search.ts +0 -263
  173. package/src/tools/shell.ts +0 -136
  174. package/src/tools/types.ts +0 -122
  175. package/src/tui/app.tsx +0 -159
  176. package/src/tui/components/ChatList.tsx +0 -41
  177. package/src/tui/components/ChatMessage.tsx +0 -54
  178. package/src/tui/components/Header.tsx +0 -66
  179. package/src/tui/components/InitWizard.tsx +0 -247
  180. package/src/tui/components/Markdown.tsx +0 -35
  181. package/src/tui/components/ModelSelector.tsx +0 -107
  182. package/src/tui/components/StatusBar.tsx +0 -30
  183. package/src/tui/components/Thinking.tsx +0 -17
  184. package/src/tui/components/ToolCall.tsx +0 -102
  185. package/src/tui/components/UserInput.tsx +0 -151
  186. package/src/tui/hooks/useChat.ts +0 -287
  187. package/src/tui/hooks/useCommands.ts +0 -271
  188. package/src/tui/index.ts +0 -6
  189. package/src/tui/welcome.ts +0 -177
  190. package/src/types.ts +0 -104
  191. package/src/utils/diff.ts +0 -71
  192. package/src/utils/project.ts +0 -99
  193. package/src/utils/stableStringify.ts +0 -47
  194. package/src/utils/thinking.ts +0 -119
  195. package/tests/cache/fingerprint.test.ts +0 -75
  196. package/tests/cache/providerCatalog.test.ts +0 -31
  197. package/tests/cache/smartModel.test.ts +0 -100
  198. package/tests/cache/stableStringify.test.ts +0 -43
  199. package/tests/cache/stats.test.ts +0 -146
  200. package/tests/cache/volatile.test.ts +0 -75
  201. package/tests/smoke-pollution.mjs +0 -78
  202. package/tests/smoke-thinking.mjs +0 -110
  203. package/tests/smoke-tool-stream.mjs +0 -69
  204. package/tests/smoke-tool.mjs +0 -117
  205. package/tsconfig.json +0 -21
@@ -1,41 +0,0 @@
1
- /** @jsxImportSource react */
2
- import React, { memo } from 'react';
3
- import { Box, Static } from 'ink';
4
- import { ChatMessage, type MessageData } from './ChatMessage.js';
5
- import type { ToolCallData } from './ToolCall.js';
6
-
7
- interface Props {
8
- messages: MessageData[];
9
- streaming?: string;
10
- streamingToolCalls?: ToolCallData[];
11
- width?: number;
12
- }
13
-
14
- export const ChatList = memo(function ChatList({ messages, streaming, streamingToolCalls, width }: Props) {
15
- const hasStreaming = !!(streaming || (streamingToolCalls && streamingToolCalls.length > 0));
16
-
17
- return (
18
- <Box flexDirection="column">
19
- {/* Completed messages - Static prevents re-rendering */}
20
- {messages.length > 0 && (
21
- <Static items={messages}>
22
- {(msg: MessageData, index: number) => (
23
- <ChatMessage key={`msg-${index}`} message={msg} width={width} />
24
- )}
25
- </Static>
26
- )}
27
-
28
- {/* Streaming content - only this part re-renders */}
29
- {hasStreaming && (
30
- <ChatMessage
31
- message={{
32
- role: 'assistant',
33
- content: streaming || '',
34
- toolCalls: streamingToolCalls && streamingToolCalls.length > 0 ? streamingToolCalls : undefined,
35
- }}
36
- width={width}
37
- />
38
- )}
39
- </Box>
40
- );
41
- });
@@ -1,54 +0,0 @@
1
- /** @jsxImportSource react */
2
- import React from 'react';
3
- import { Box, Text } from 'ink';
4
- import { Markdown } from './Markdown.js';
5
- import { ToolCall, type ToolCallData } from './ToolCall.js';
6
-
7
- interface MessageData {
8
- role: 'user' | 'assistant' | 'tool';
9
- content: string;
10
- toolCalls?: ToolCallData[];
11
- }
12
-
13
- interface Props {
14
- message: MessageData;
15
- width?: number;
16
- }
17
-
18
- function UserMessage({ content }: { content: string }) {
19
- return (
20
- <Box flexDirection="column" marginBottom={1} paddingLeft={1}>
21
- <Text bold color="#06B6D4">You</Text>
22
- <Box paddingLeft={2}>
23
- <Text>{content}</Text>
24
- </Box>
25
- </Box>
26
- );
27
- }
28
-
29
- function AssistantMessage({ content, toolCalls }: { content: string; toolCalls?: ToolCallData[] }) {
30
- return (
31
- <Box flexDirection="column" marginBottom={1}>
32
- {toolCalls && toolCalls.map((tc, i) => (
33
- <ToolCall key={i} tool={tc} />
34
- ))}
35
- {content && (
36
- <Box flexDirection="column" paddingLeft={1}>
37
- <Text bold color="#22D3EE">AI</Text>
38
- <Box paddingLeft={2}>
39
- <Markdown content={content} />
40
- </Box>
41
- </Box>
42
- )}
43
- </Box>
44
- );
45
- }
46
-
47
- export const ChatMessage = React.memo(function ChatMessage({ message }: Props) {
48
- if (message.role === 'user') {
49
- return <UserMessage content={message.content} />;
50
- }
51
- return <AssistantMessage content={message.content} toolCalls={message.toolCalls} />;
52
- });
53
-
54
- export type { MessageData };
@@ -1,66 +0,0 @@
1
- /** @jsxImportSource react */
2
- import React from 'react';
3
- import { Box, Text } from 'ink';
4
-
5
- interface Props {
6
- provider: string;
7
- model: string;
8
- /**
9
- * v3.0.0: optional cache hit-rate + estimated savings. When provided,
10
- * the header shows `⚡ 命中率 87% · 节省 ¥0.42` on the right side. When
11
- * null (no rounds yet, or provider does not surface usage), the right
12
- * side just shows provider/model.
13
- */
14
- cacheHitRate?: number | null;
15
- cacheSavingsCNY?: number;
16
- /**
17
- * v3.0.3: cache TTL marker. '5m' (default for short sessions) or '1h'
18
- * (auto-decided for long sessions, or pinned by the user). 'auto' is
19
- * the user-pinned setting and shows up as ⏱ auto. null = not yet
20
- * decided (waiting for first round).
21
- */
22
- cacheTtl?: '5m' | '1h' | 'auto' | null;
23
- }
24
-
25
- /**
26
- * Choose a color for the hit-rate chip based on Reasonix-style thresholds:
27
- * ≥ 80% green (excellent — most of the prefix is being reused)
28
- * ≥ 50% yellow (partial hit — system prompt is cached but messages tail is not)
29
- * < 50% gray (cold — every round re-creates the cache; investigate)
30
- */
31
- function hitRateColor(rate: number): string {
32
- if (rate >= 0.8) return '#10B981';
33
- if (rate >= 0.5) return '#F59E0B';
34
- return '#6B7280';
35
- }
36
-
37
- export const Header = React.memo(function Header({ provider, model, cacheHitRate, cacheSavingsCNY, cacheTtl }: Props) {
38
- const showCache = typeof cacheHitRate === 'number' && cacheHitRate > 0;
39
- return (
40
- <Box flexDirection="column" marginBottom={0}>
41
- <Box justifyContent="space-between" width="100%">
42
- <Box>
43
- <Text color="#06B6D4" bold> ⚡ </Text>
44
- <Text color="#22D3EE" bold>THATGFSJ CODE</Text>
45
- <Text dimColor> v3.0.4</Text>
46
- </Box>
47
- <Box>
48
- {showCache && (
49
- <>
50
- <Text color={hitRateColor(cacheHitRate!)} bold> ⚡ {(cacheHitRate! * 100).toFixed(1)}% </Text>
51
- <Text dimColor> · 节省 ¥{(cacheSavingsCNY ?? 0).toFixed(2)} </Text>
52
- <Text dimColor> · </Text>
53
- </>
54
- )}
55
- {cacheTtl && (
56
- <Text color="#A78BFA" bold> ⏱ {cacheTtl} </Text>
57
- )}
58
- <Text color="#06B6D4" bold> {provider} </Text>
59
- <Text dimColor>/</Text>
60
- <Text color="#22D3EE"> {model} </Text>
61
- </Box>
62
- </Box>
63
- <Text color="#374151">{'─'.repeat(80)}</Text>
64
- </Box>
65
- );
66
- });
@@ -1,247 +0,0 @@
1
- /** @jsxImportSource react */
2
- import React, { useState } from 'react';
3
- import { Box, Text, useInput } from 'ink';
4
- import SelectInput from 'ink-select-input';
5
- import TextInput from 'ink-text-input';
6
- import { writeFileSync, existsSync, mkdirSync } from 'fs';
7
- import { join } from 'path';
8
- import { homedir } from 'os';
9
- import { listProviders, getModelsForProvider, isCustomProvider } from '../../config/providers.js';
10
- import type { ProviderName } from '../../config/types.js';
11
-
12
- interface Props {
13
- onComplete: (provider: ProviderName, model: string, apiKey: string, baseUrl?: string) => void;
14
- onCancel: () => void;
15
- }
16
-
17
- type InitStep = 'provider' | 'custom_url' | 'api_key' | 'model' | 'custom_model' | 'cache_strategy';
18
-
19
- interface CacheChoice {
20
- enabled: boolean;
21
- ttl: '5m' | '1h';
22
- }
23
-
24
- /**
25
- * v3.0.1: InitWizard is now fully self-contained — it manages its own
26
- * step state AND its own keyboard input for text-entry steps (api_key,
27
- * custom_url, custom_model). The previous version had a fatal bug:
28
- *
29
- * The text-entry steps (`api_key`, `custom_url`, `custom_model`) only
30
- * rendered a `<Text>` prompt with no actual input box. The host
31
- * component (app.tsx) had its own UserInput BUT only rendered it when
32
- * viewMode was NOT 'init_wizard' (see app.tsx ternary). So when the
33
- * wizard's internal step advanced to 'api_key', the user could SEE the
34
- * "输入 API Key: 服务商: deepseek" prompt but had NO way to type into
35
- * it. Ink silently swallowed keystrokes (no useInput handler was
36
- * active), and the user was stuck on a frozen screen — exactly the
37
- * bug that prompted the user-reported "选择 deepseek 然后退出":
38
- * pressing Ctrl+C felt like the only escape.
39
- *
40
- * Fix: this file now uses `useInput` and `ink-text-input` for the text
41
- * steps. The SetViewMode-based input delegation in app.tsx is no longer
42
- * required for the InitWizard flow.
43
- */
44
- export function InitWizard({ onComplete, onCancel }: Props) {
45
- const [step, setStep] = useState<InitStep>('provider');
46
- const [selectedProvider, setSelectedProvider] = useState<ProviderName>('siliconflow');
47
- const [apiKey, setApiKey] = useState('');
48
- const [selectedModel, setSelectedModel] = useState('');
49
- const [customUrl, setCustomUrl] = useState('');
50
- const [cacheChoice, setCacheChoice] = useState<CacheChoice>({ enabled: true, ttl: '5m' });
51
-
52
- const providers = listProviders();
53
- const models = getModelsForProvider(selectedProvider);
54
-
55
- const saveConfig = (provider: ProviderName, model: string, key: string, url?: string) => {
56
- const dir = join(homedir(), '.thatgfsj');
57
- const configPath = join(dir, 'config.json');
58
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
59
-
60
- const config: Record<string, any> = {
61
- provider,
62
- model,
63
- apiKey: key,
64
- temperature: 0.7,
65
- maxTokens: 4096,
66
- contextLength: 50,
67
- cache: {
68
- enabled: cacheChoice.enabled,
69
- ttl: cacheChoice.ttl,
70
- strategy: 'auto',
71
- },
72
- };
73
- if (url) config.baseUrl = url;
74
- writeFileSync(configPath, JSON.stringify(config, null, 2));
75
- };
76
-
77
- // v3.0.1: ESC cancels the wizard from any text-entry step. This is the
78
- // hook that the previous version was missing — when the user pressed
79
- // ESC on the frozen "输入 API Key" screen, nothing happened because no
80
- // useInput was active. Now it bails out cleanly.
81
- useInput((_input, key) => {
82
- if (key.escape && (step === 'api_key' || step === 'custom_url' || step === 'custom_model')) {
83
- onCancel();
84
- }
85
- });
86
-
87
- // Step 1: Provider selection
88
- if (step === 'provider') {
89
- const items = providers.map(p => ({ label: p.name, value: p.key }));
90
- return (
91
- <Box flexDirection="column" paddingLeft={1}>
92
- <Text color="#06B6D4" bold>选择服务商 (↑↓ 回车):</Text>
93
- <SelectInput
94
- items={items}
95
- onSelect={(item) => {
96
- const provider = item.value as ProviderName;
97
- setSelectedProvider(provider);
98
- if (isCustomProvider(provider)) {
99
- setStep('custom_url');
100
- } else {
101
- setStep('api_key');
102
- }
103
- }}
104
- />
105
- </Box>
106
- );
107
- }
108
-
109
- // Custom URL input — v3.0.1 NOW has a real TextInput
110
- if (step === 'custom_url') {
111
- return (
112
- <Box flexDirection="column" paddingLeft={1}>
113
- <Text color="#06B6D4" bold>输入中转站 URL:</Text>
114
- <Text dimColor>例如: https://api.example.com/v1</Text>
115
- <Text color="#F59E0B">(请直接输入 URL 并回车,ESC 取消)</Text>
116
- <Box marginTop={1}>
117
- <Text color="#06B6D4">❯ </Text>
118
- <TextInput
119
- value={customUrl}
120
- onChange={setCustomUrl}
121
- onSubmit={(value) => {
122
- if (value.trim()) {
123
- setCustomUrl(value.trim());
124
- setStep('api_key');
125
- }
126
- }}
127
- />
128
- </Box>
129
- </Box>
130
- );
131
- }
132
-
133
- // Step 2: API Key — v3.0.1 NOW has a real TextInput
134
- if (step === 'api_key') {
135
- const providerLabel = providers.find(p => p.key === selectedProvider)?.name || selectedProvider;
136
- return (
137
- <Box flexDirection="column" paddingLeft={1}>
138
- <Text color="#06B6D4" bold>输入 API Key:</Text>
139
- <Text dimColor>服务商: {providerLabel}</Text>
140
- <Text color="#F59E0B">(请直接输入 Key 并回车,ESC 取消)</Text>
141
- <Box marginTop={1}>
142
- <Text color="#06B6D4">❯ </Text>
143
- <TextInput
144
- value={apiKey}
145
- onChange={setApiKey}
146
- onSubmit={(value) => {
147
- if (value.trim()) {
148
- setApiKey(value.trim());
149
- if (isCustomProvider(selectedProvider)) {
150
- // Custom OpenAI/Anthropic: after key, go to model
151
- setStep('model');
152
- } else {
153
- // Standard providers: skip to model selection
154
- setStep('model');
155
- }
156
- }
157
- }}
158
- />
159
- </Box>
160
- </Box>
161
- );
162
- }
163
-
164
- // Step 3: Model selection
165
- if (step === 'model') {
166
- const items = [
167
- ...models.map(m => ({ label: `${m.name} - ${m.desc}`, value: m.id })),
168
- { label: '+ 输入自定义模型名', value: '__custom__' },
169
- ];
170
- return (
171
- <Box flexDirection="column" paddingLeft={1}>
172
- <Text color="#06B6D4" bold>选择模型 (↑↓ 回车):</Text>
173
- <SelectInput
174
- items={items}
175
- onSelect={(item) => {
176
- if (item.value === '__custom__') {
177
- setStep('custom_model');
178
- } else {
179
- setSelectedModel(item.value);
180
- setStep('cache_strategy');
181
- }
182
- }}
183
- />
184
- </Box>
185
- );
186
- }
187
-
188
- // Custom model name input — v3.0.1 NOW has a real TextInput
189
- if (step === 'custom_model') {
190
- return (
191
- <Box flexDirection="column" paddingLeft={1}>
192
- <Text color="#06B6D4" bold>输入模型名称:</Text>
193
- <Text color="#F59E0B">(请直接输入模型名并回车,ESC 取消)</Text>
194
- <Box marginTop={1}>
195
- <Text color="#06B6D4">❯ </Text>
196
- <TextInput
197
- value={selectedModel}
198
- onChange={setSelectedModel}
199
- onSubmit={(value) => {
200
- if (value.trim()) {
201
- setSelectedModel(value.trim());
202
- setStep('cache_strategy');
203
- }
204
- }}
205
- />
206
- </Box>
207
- </Box>
208
- );
209
- }
210
-
211
- // v3.0.4: prompt-cache strategy step.
212
- // Default is 1h (long-task TTL): we cannot predict how long a task
213
- // will run, so we pick the TTL that cannot expire mid-task. 5m is
214
- // for users who are sure the session is short.
215
- if (step === 'cache_strategy') {
216
- const items = [
217
- { label: '🕐 1 小时(推荐:默认长任务,缓存不中途失效)', value: '1h' },
218
- { label: '⏱ 5 分钟(只适合你确认是短会话)', value: '5m' },
219
- { label: '✗ 关闭(每次请求都重新计算)', value: 'off' },
220
- ];
221
- return (
222
- <Box flexDirection="column" paddingLeft={1}>
223
- <Text color="#06B6D4" bold>Prompt Caching 策略:</Text>
224
- <Text dimColor>对 Anthropic / DeepSeek / Gemini 有效。</Text>
225
- <Text dimColor>推荐 1 小时:任务开始前无法预知长度,5 分钟 TTL 会在长任务中途过期。</Text>
226
- <SelectInput
227
- items={items}
228
- onSelect={(item) => {
229
- if (item.value === 'off') {
230
- setCacheChoice({ enabled: false, ttl: '1h' });
231
- } else {
232
- setCacheChoice({ enabled: true, ttl: item.value as '5m' | '1h' });
233
- }
234
- saveConfig(selectedProvider, selectedModel, apiKey, customUrl || undefined);
235
- onComplete(selectedProvider, selectedModel, apiKey, customUrl || undefined);
236
- }}
237
- />
238
- </Box>
239
- );
240
- }
241
-
242
- return null;
243
- }
244
-
245
- // Export steps for external handling
246
- export type { InitStep };
247
- export { isCustomProvider };
@@ -1,35 +0,0 @@
1
- /** @jsxImportSource react */
2
- import React, { useMemo } from 'react';
3
- import { Text } from 'ink';
4
- import { marked } from 'marked';
5
- import TerminalRenderer from 'marked-terminal';
6
-
7
- // Configure marked to use terminal renderer
8
- marked.setOptions({
9
- // @ts-ignore - marked-terminal types are incomplete
10
- renderer: new TerminalRenderer({
11
- showSectionPrefix: false,
12
- tab: 2,
13
- }),
14
- });
15
-
16
- interface Props {
17
- content: string;
18
- }
19
-
20
- export function Markdown({ content }: Props) {
21
- const rendered = useMemo(() => {
22
- try {
23
- const result = marked.parse(content);
24
- // marked.parse returns string | Promise<string> depending on async option
25
- if (typeof result === 'string') {
26
- return result.trimEnd();
27
- }
28
- return content;
29
- } catch {
30
- return content;
31
- }
32
- }, [content]);
33
-
34
- return <Text>{rendered}</Text>;
35
- }
@@ -1,107 +0,0 @@
1
- /** @jsxImportSource react */
2
- import React, { useState, useEffect } from 'react';
3
- import { Box, Text } from 'ink';
4
- import SelectInput from 'ink-select-input';
5
- import { readFileSync, existsSync } from 'fs';
6
- import { join } from 'path';
7
- import { homedir } from 'os';
8
- import { PROVIDERS, getModelsForProvider } from '../../config/providers.js';
9
- import type { ProviderName } from '../../config/types.js';
10
-
11
- interface Props {
12
- currentModel: string;
13
- currentProvider?: ProviderName;
14
- onSelect: (model: string) => void;
15
- onAddNew: () => void;
16
- }
17
-
18
- interface SavedModel {
19
- label: string;
20
- value: string;
21
- }
22
-
23
- function loadSavedModels(currentProvider?: ProviderName): SavedModel[] {
24
- const configPath = join(homedir(), '.thatgfsj', 'config.json');
25
- const models: SavedModel[] = [];
26
- const seen = new Set<string>();
27
-
28
- // 1. Load from history if exists
29
- const historyPath = join(homedir(), '.thatgfsj', 'models.json');
30
- if (existsSync(historyPath)) {
31
- try {
32
- const history = JSON.parse(readFileSync(historyPath, 'utf-8'));
33
- for (const m of history) {
34
- if (!seen.has(m)) {
35
- seen.add(m);
36
- models.push({ label: m, value: m });
37
- }
38
- }
39
- } catch {}
40
- }
41
-
42
- // 2. Always include current model
43
- if (existsSync(configPath)) {
44
- try {
45
- const config = JSON.parse(readFileSync(configPath, 'utf-8'));
46
- if (config.model && !seen.has(config.model)) {
47
- seen.add(config.model);
48
- models.push({ label: `${config.model} (当前)`, value: config.model });
49
- }
50
- } catch {}
51
- }
52
-
53
- // 3. v3.0.0 bug fix: ALWAYS include the current provider's catalog of
54
- // models, otherwise users on a custom relay (mimo / one-api / etc.) who
55
- // only have the current model in history see a list of 1 item and can't
56
- // switch to deepseek / gpt-4o / etc. without going through init wizard.
57
- // This is the model_selector "选 deepseek 然后退出" bug: the list only
58
- // contained the current model, so the user pressed Enter on the wrong
59
- // item (or thought the list was broken), then Ctrl+C'd out.
60
- if (currentProvider && PROVIDERS[currentProvider]) {
61
- const catalog = getModelsForProvider(currentProvider);
62
- for (const m of catalog) {
63
- if (!seen.has(m.id)) {
64
- seen.add(m.id);
65
- models.push({ label: m.id, value: m.id });
66
- }
67
- }
68
- }
69
-
70
- // 4. Universal fallbacks if user is on no history and no catalog
71
- if (models.length === 0) {
72
- const defaults = ['deepseek-chat', 'gpt-4o', 'mimo-v2.5-pro'];
73
- for (const m of defaults) {
74
- models.push({ label: m, value: m });
75
- }
76
- }
77
-
78
- return models;
79
- }
80
-
81
- export function ModelSelector({ currentModel, currentProvider, onSelect, onAddNew }: Props) {
82
- const [items, setItems] = useState<SavedModel[]>([]);
83
-
84
- useEffect(() => {
85
- const saved = loadSavedModels(currentProvider);
86
- // Add "add new" option at the end
87
- saved.push({ label: '+ 添加新模型', value: '__add_new__' });
88
- setItems(saved);
89
- }, [currentProvider]);
90
-
91
- return (
92
- <Box flexDirection="column" paddingLeft={1}>
93
- <Text color="#06B6D4" bold>当前模型: {currentModel}</Text>
94
- <Text dimColor>选择模型 (↑↓ 回车):</Text>
95
- <SelectInput
96
- items={items}
97
- onSelect={(item) => {
98
- if (item.value === '__add_new__') {
99
- onAddNew();
100
- } else {
101
- onSelect(item.value);
102
- }
103
- }}
104
- />
105
- </Box>
106
- );
107
- }
@@ -1,30 +0,0 @@
1
- /** @jsxImportSource react */
2
- import React from 'react';
3
- import { Box, Text } from 'ink';
4
-
5
- interface Props {
6
- messageCount: number;
7
- skills: string[];
8
- }
9
-
10
- export const StatusBar = React.memo(function StatusBar({ messageCount, skills }: Props) {
11
- const activeSkills = skills.slice(0, 3).join(', ');
12
- const moreSkills = skills.length > 3 ? ` +${skills.length - 3}` : '';
13
-
14
- return (
15
- <Box flexDirection="column" marginTop={0}>
16
- <Text color="#374151">{'─'.repeat(80)}</Text>
17
- <Box justifyContent="space-between" width="100%">
18
- <Box>
19
- <Text backgroundColor="#06B6D4" color="#0F172A" bold> ⚡ THATGFSJ CODE </Text>
20
- <Text dimColor> │ {messageCount} 条消息</Text>
21
- </Box>
22
- <Box>
23
- {skills.length > 0 && (
24
- <Text dimColor> 技能: {activeSkills}{moreSkills} </Text>
25
- )}
26
- </Box>
27
- </Box>
28
- </Box>
29
- );
30
- });
@@ -1,17 +0,0 @@
1
- /** @jsxImportSource react */
2
- import React from 'react';
3
- import { Box, Text } from 'ink';
4
-
5
- interface Props {
6
- active?: boolean;
7
- }
8
-
9
- export function Thinking({ active = false }: Props) {
10
- if (!active) return null;
11
-
12
- return (
13
- <Box paddingLeft={1}>
14
- <Text color="#06B6D4">⟳ Thinking...</Text>
15
- </Box>
16
- );
17
- }