thatgfsj-code 3.0.3 → 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 (212) 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/cache/smartModel.d.ts +29 -22
  7. package/dist/cache/smartModel.d.ts.map +1 -1
  8. package/dist/cache/smartModel.js +33 -40
  9. package/dist/cache/smartModel.js.map +1 -1
  10. package/dist/cmd/index.d.ts +12 -20
  11. package/dist/cmd/index.d.ts.map +1 -1
  12. package/dist/cmd/index.js +192 -146
  13. package/dist/cmd/index.js.map +1 -1
  14. package/dist/config/index.d.ts +4 -0
  15. package/dist/config/index.d.ts.map +1 -1
  16. package/dist/config/index.js +9 -6
  17. package/dist/config/index.js.map +1 -1
  18. package/dist/config/types.d.ts +5 -0
  19. package/dist/config/types.d.ts.map +1 -1
  20. package/dist/llm/anthropic.d.ts +1 -1
  21. package/dist/llm/anthropic.d.ts.map +1 -1
  22. package/dist/llm/anthropic.js +116 -84
  23. package/dist/llm/anthropic.js.map +1 -1
  24. package/dist/llm/gemini.d.ts +27 -3
  25. package/dist/llm/gemini.d.ts.map +1 -1
  26. package/dist/llm/gemini.js +196 -73
  27. package/dist/llm/gemini.js.map +1 -1
  28. package/dist/llm/index.d.ts +20 -1
  29. package/dist/llm/index.d.ts.map +1 -1
  30. package/dist/llm/index.js +71 -10
  31. package/dist/llm/index.js.map +1 -1
  32. package/dist/llm/openai.d.ts +5 -1
  33. package/dist/llm/openai.d.ts.map +1 -1
  34. package/dist/llm/openai.js +96 -56
  35. package/dist/llm/openai.js.map +1 -1
  36. package/dist/mcp/client.d.ts +84 -34
  37. package/dist/mcp/client.d.ts.map +1 -1
  38. package/dist/mcp/client.js +291 -126
  39. package/dist/mcp/client.js.map +1 -1
  40. package/dist/session/compactor.d.ts +27 -7
  41. package/dist/session/compactor.d.ts.map +1 -1
  42. package/dist/session/compactor.js +78 -23
  43. package/dist/session/compactor.js.map +1 -1
  44. package/dist/session/index.d.ts +96 -33
  45. package/dist/session/index.d.ts.map +1 -1
  46. package/dist/session/index.js +278 -60
  47. package/dist/session/index.js.map +1 -1
  48. package/dist/tools/file.d.ts +10 -2
  49. package/dist/tools/file.d.ts.map +1 -1
  50. package/dist/tools/file.js +54 -5
  51. package/dist/tools/file.js.map +1 -1
  52. package/dist/tools/git.d.ts +16 -2
  53. package/dist/tools/git.d.ts.map +1 -1
  54. package/dist/tools/git.js +61 -18
  55. package/dist/tools/git.js.map +1 -1
  56. package/dist/tools/nwt.d.ts.map +1 -1
  57. package/dist/tools/nwt.js +8 -1
  58. package/dist/tools/nwt.js.map +1 -1
  59. package/dist/tools/search.d.ts +8 -1
  60. package/dist/tools/search.d.ts.map +1 -1
  61. package/dist/tools/search.js +105 -34
  62. package/dist/tools/search.js.map +1 -1
  63. package/dist/tools/shell.d.ts +0 -4
  64. package/dist/tools/shell.d.ts.map +1 -1
  65. package/dist/tools/shell.js +11 -26
  66. package/dist/tools/shell.js.map +1 -1
  67. package/dist/tools/types.d.ts +7 -0
  68. package/dist/tools/types.d.ts.map +1 -1
  69. package/dist/tools/types.js.map +1 -1
  70. package/dist/tui/app.d.ts.map +1 -1
  71. package/dist/tui/app.js +90 -18
  72. package/dist/tui/app.js.map +1 -1
  73. package/dist/tui/components/ChatMessage.d.ts.map +1 -1
  74. package/dist/tui/components/ChatMessage.js +9 -2
  75. package/dist/tui/components/ChatMessage.js.map +1 -1
  76. package/dist/tui/components/ConfirmPrompt.d.ts +21 -0
  77. package/dist/tui/components/ConfirmPrompt.d.ts.map +1 -0
  78. package/dist/tui/components/ConfirmPrompt.js +45 -0
  79. package/dist/tui/components/ConfirmPrompt.js.map +1 -0
  80. package/dist/tui/components/Header.d.ts +10 -5
  81. package/dist/tui/components/Header.d.ts.map +1 -1
  82. package/dist/tui/components/Header.js +13 -5
  83. package/dist/tui/components/Header.js.map +1 -1
  84. package/dist/tui/components/InitWizard.d.ts.map +1 -1
  85. package/dist/tui/components/InitWizard.js +8 -13
  86. package/dist/tui/components/InitWizard.js.map +1 -1
  87. package/dist/tui/components/StatusBar.d.ts +7 -0
  88. package/dist/tui/components/StatusBar.d.ts.map +1 -1
  89. package/dist/tui/components/StatusBar.js +8 -3
  90. package/dist/tui/components/StatusBar.js.map +1 -1
  91. package/dist/tui/components/Thinking.d.ts +5 -0
  92. package/dist/tui/components/Thinking.d.ts.map +1 -1
  93. package/dist/tui/components/Thinking.js +24 -2
  94. package/dist/tui/components/Thinking.js.map +1 -1
  95. package/dist/tui/components/ToolCall.d.ts +6 -1
  96. package/dist/tui/components/ToolCall.d.ts.map +1 -1
  97. package/dist/tui/components/ToolCall.js +50 -22
  98. package/dist/tui/components/ToolCall.js.map +1 -1
  99. package/dist/tui/components/UserInput.d.ts +5 -0
  100. package/dist/tui/components/UserInput.d.ts.map +1 -1
  101. package/dist/tui/components/UserInput.js +9 -13
  102. package/dist/tui/components/UserInput.js.map +1 -1
  103. package/dist/tui/hooks/useChat.d.ts +1 -0
  104. package/dist/tui/hooks/useChat.d.ts.map +1 -1
  105. package/dist/tui/hooks/useChat.js +36 -16
  106. package/dist/tui/hooks/useChat.js.map +1 -1
  107. package/dist/tui/hooks/useCommands.d.ts +7 -1
  108. package/dist/tui/hooks/useCommands.d.ts.map +1 -1
  109. package/dist/tui/hooks/useCommands.js +83 -49
  110. package/dist/tui/hooks/useCommands.js.map +1 -1
  111. package/dist/tui/theme.d.ts +34 -0
  112. package/dist/tui/theme.d.ts.map +1 -0
  113. package/dist/tui/theme.js +34 -0
  114. package/dist/tui/theme.js.map +1 -0
  115. package/dist/tui/welcome.d.ts.map +1 -1
  116. package/dist/tui/welcome.js +9 -7
  117. package/dist/tui/welcome.js.map +1 -1
  118. package/dist/types.d.ts +16 -1
  119. package/dist/types.d.ts.map +1 -1
  120. package/dist/utils/diff.d.ts +5 -0
  121. package/dist/utils/diff.d.ts.map +1 -1
  122. package/dist/utils/diff.js +15 -0
  123. package/dist/utils/diff.js.map +1 -1
  124. package/dist/version.d.ts +12 -0
  125. package/dist/version.d.ts.map +1 -0
  126. package/dist/version.js +29 -0
  127. package/dist/version.js.map +1 -0
  128. package/package.json +19 -7
  129. package/CHANGELOG.md +0 -192
  130. package/DEVELOPMENT.md +0 -286
  131. package/ROADMAP.md +0 -91
  132. package/docs/API_KEY_GUIDE.md +0 -236
  133. package/docs/FAQ.md +0 -182
  134. package/install.bat +0 -63
  135. package/install.ps1 +0 -238
  136. package/install.sh +0 -113
  137. package/src/app/index.ts +0 -197
  138. package/src/cache/fingerprint.ts +0 -101
  139. package/src/cache/index.ts +0 -21
  140. package/src/cache/smartModel.ts +0 -145
  141. package/src/cache/stats.ts +0 -199
  142. package/src/cache/volatile.ts +0 -47
  143. package/src/cmd/index.tsx +0 -288
  144. package/src/config/index.ts +0 -158
  145. package/src/config/providers.ts +0 -234
  146. package/src/config/types.ts +0 -71
  147. package/src/hooks/index.ts +0 -111
  148. package/src/llm/anthropic.ts +0 -413
  149. package/src/llm/gemini.ts +0 -169
  150. package/src/llm/index.ts +0 -302
  151. package/src/llm/openai.ts +0 -243
  152. package/src/llm/provider.ts +0 -71
  153. package/src/mcp/client.ts +0 -330
  154. package/src/prompts/index.ts +0 -260
  155. package/src/session/compactor.ts +0 -103
  156. package/src/session/index.ts +0 -181
  157. package/src/session/message.ts +0 -42
  158. package/src/skills/brainstorming.ts +0 -43
  159. package/src/skills/code-review.ts +0 -45
  160. package/src/skills/executing-plans.ts +0 -27
  161. package/src/skills/frontend-design.ts +0 -35
  162. package/src/skills/git-workflow.ts +0 -36
  163. package/src/skills/improve-architecture.ts +0 -38
  164. package/src/skills/index.ts +0 -136
  165. package/src/skills/neuroweave.ts +0 -47
  166. package/src/skills/playwright.ts +0 -72
  167. package/src/skills/prototype.ts +0 -30
  168. package/src/skills/subagent.ts +0 -28
  169. package/src/skills/supabase.ts +0 -44
  170. package/src/skills/systematic-debugging.ts +0 -44
  171. package/src/skills/tdd.ts +0 -39
  172. package/src/skills/triage.ts +0 -35
  173. package/src/skills/verification.ts +0 -31
  174. package/src/skills/writing-plans.ts +0 -42
  175. package/src/tools/file.ts +0 -143
  176. package/src/tools/git.ts +0 -132
  177. package/src/tools/index.ts +0 -111
  178. package/src/tools/nwt.ts +0 -598
  179. package/src/tools/search.ts +0 -263
  180. package/src/tools/shell.ts +0 -136
  181. package/src/tools/types.ts +0 -122
  182. package/src/tui/app.tsx +0 -159
  183. package/src/tui/components/ChatList.tsx +0 -41
  184. package/src/tui/components/ChatMessage.tsx +0 -54
  185. package/src/tui/components/Header.tsx +0 -66
  186. package/src/tui/components/InitWizard.tsx +0 -251
  187. package/src/tui/components/Markdown.tsx +0 -35
  188. package/src/tui/components/ModelSelector.tsx +0 -107
  189. package/src/tui/components/StatusBar.tsx +0 -30
  190. package/src/tui/components/Thinking.tsx +0 -17
  191. package/src/tui/components/ToolCall.tsx +0 -102
  192. package/src/tui/components/UserInput.tsx +0 -151
  193. package/src/tui/hooks/useChat.ts +0 -287
  194. package/src/tui/hooks/useCommands.ts +0 -273
  195. package/src/tui/index.ts +0 -6
  196. package/src/tui/welcome.ts +0 -177
  197. package/src/types.ts +0 -104
  198. package/src/utils/diff.ts +0 -71
  199. package/src/utils/project.ts +0 -99
  200. package/src/utils/stableStringify.ts +0 -47
  201. package/src/utils/thinking.ts +0 -119
  202. package/tests/cache/fingerprint.test.ts +0 -75
  203. package/tests/cache/providerCatalog.test.ts +0 -31
  204. package/tests/cache/smartModel.test.ts +0 -112
  205. package/tests/cache/stableStringify.test.ts +0 -43
  206. package/tests/cache/stats.test.ts +0 -146
  207. package/tests/cache/volatile.test.ts +0 -75
  208. package/tests/smoke-pollution.mjs +0 -78
  209. package/tests/smoke-thinking.mjs +0 -110
  210. package/tests/smoke-tool-stream.mjs +0 -69
  211. package/tests/smoke-tool.mjs +0 -117
  212. 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.3</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,251 +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.3: prompt-cache strategy step — auto / 5m / 1h / off.
212
- // The wizard used to require two questions (on/off + 5m/1h). Now it
213
- // gives the user a single, clear choice up front, with "自动" as the
214
- // default. The auto path lets the runtime pick the TTL based on the
215
- // conversation length (see cache/smartModel.ts decideTTL).
216
- if (step === 'cache_strategy') {
217
- const items = [
218
- { label: '🤖 自动(推荐:根据会话长度智能选 5m / 1h)', value: 'auto' },
219
- { label: '⏱ 强制 5 分钟(短会话,写入便宜)', value: '5m' },
220
- { label: '🕐 强制 1 小时(长会话,命中率优先)', value: '1h' },
221
- { label: '✗ 关闭(每次请求都重新计算)', value: 'off' },
222
- ];
223
- return (
224
- <Box flexDirection="column" paddingLeft={1}>
225
- <Text color="#06B6D4" bold>Prompt Caching 策略:</Text>
226
- <Text dimColor>对 Anthropic / DeepSeek / Gemini 有效。</Text>
227
- <Text dimColor>自动模式:短会话用 5m(便宜),长会话(&gt;15 轮)自动切 1h。</Text>
228
- <SelectInput
229
- items={items}
230
- onSelect={(item) => {
231
- if (item.value === 'off') {
232
- setCacheChoice({ enabled: false, ttl: '5m' });
233
- } else if (item.value === 'auto') {
234
- setCacheChoice({ enabled: true, ttl: '5m' }); // start at 5m, smartModel will upgrade
235
- } else {
236
- setCacheChoice({ enabled: true, ttl: item.value as '5m' | '1h' });
237
- }
238
- saveConfig(selectedProvider, selectedModel, apiKey, customUrl || undefined);
239
- onComplete(selectedProvider, selectedModel, apiKey, customUrl || undefined);
240
- }}
241
- />
242
- </Box>
243
- );
244
- }
245
-
246
- return null;
247
- }
248
-
249
- // Export steps for external handling
250
- export type { InitStep };
251
- 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
- }