chenbhao commited on
Commit
278cb48
·
1 Parent(s): 799831d

fix: model not display life-free model

Browse files
src/components/OpenCodeLoginFlow.tsx CHANGED
@@ -1,6 +1,6 @@
1
  import * as React from 'react'
2
  import { useState, useEffect } from 'react'
3
- import { saveOpenCodeApiKey, getOpenCodeApiKey, getOpenCodeModelName } from '../utils/auth.js'
4
  import { Box, Text } from '../ink.js'
5
  import TextInput from './TextInput.js'
6
  import { Select } from './CustomSelect/select.js'
@@ -10,13 +10,7 @@ type OpenCodeLoginFlowProps = {
10
  startingMessage?: string
11
  }
12
 
13
- type LoginMode = 'menu' | 'free_models' | 'api_key'
14
-
15
- type OpencodeModel = {
16
- label: string
17
- value: string
18
- description: string
19
- }
20
 
21
  export function OpenCodeLoginFlow({
22
  onDone,
@@ -24,125 +18,23 @@ export function OpenCodeLoginFlow({
24
  }: OpenCodeLoginFlowProps): React.ReactNode {
25
  const [mode, setMode] = useState<LoginMode>('menu')
26
  const [isBusy, setIsBusy] = useState(false)
27
- const [isLoadingModels, setIsLoadingModels] = useState(false)
28
  const [status, setStatus] = useState<string | null>(null)
29
  const [inputValue, setInputValue] = useState('')
30
  const [cursorOffset, setCursorOffset] = useState(0)
31
- const [selectedModel, setSelectedModel] = useState('')
32
  const [existingApiKey, setExistingApiKey] = useState<string | null>(null)
33
- const [existingModelName, setExistingModelName] = useState<string | null>(null)
34
- const [availableModels, setAvailableModels] = useState<OpencodeModel[]>([])
35
 
36
  useEffect(() => {
37
  const key = getOpenCodeApiKey()
38
  if (key) {
39
  setExistingApiKey(key)
40
  }
41
- const model = getOpenCodeModelName()
42
- if (model) {
43
- setExistingModelName(model)
44
- setSelectedModel(model)
45
- }
46
  }, [])
47
 
48
- const fetchModels = async () => {
49
- setIsLoadingModels(true)
50
- try {
51
- // Use native https module for better reliability in bundled environment
52
- const https = await import('https')
53
- const data = await new Promise<string>((resolve, reject) => {
54
- const req = https.get('https://opencode.ai/zen/v1/models', {
55
- headers: {
56
- 'User-Agent': 'claude-code/2.1.88',
57
- },
58
- timeout: 15000,
59
- }, res => {
60
- let body = ''
61
- res.on('data', chunk => { body += chunk })
62
- res.on('end', () => resolve(body))
63
- res.on('error', reject)
64
- })
65
- req.on('error', reject)
66
- req.on('timeout', () => {
67
- req.destroy()
68
- reject(new Error('Request timed out'))
69
- })
70
- })
71
-
72
- const parsed = JSON.parse(data) as { data?: Array<{ id: string }> }
73
- if (Array.isArray(parsed.data) && parsed.data.length > 0) {
74
- const models = parsed.data.map(m => ({
75
- label: m.id,
76
- value: m.id,
77
- description: m.id.endsWith('-free') ? '限时免费模型' : 'OpenCode Zen 模型',
78
- }))
79
- setAvailableModels(models)
80
- if (!selectedModel && models.length > 0) {
81
- setSelectedModel(models[0].value)
82
- }
83
- } else {
84
- setStatus('API returned empty model list')
85
- }
86
- } catch (err) {
87
- console.error('OpenCode models fetch error:', err)
88
- setStatus(`Failed to fetch models: ${err instanceof Error ? err.message : 'Unknown error'}`)
89
- } finally {
90
- setIsLoadingModels(false)
91
- }
92
- }
93
-
94
- const menuOptions = [
95
- {
96
- label: (
97
- <Text>
98
- Use free models{' '}
99
- <Text dimColor={true}>No API key required, select a model</Text>
100
- {'\n'}
101
- </Text>
102
- ),
103
- value: 'free_models',
104
- },
105
- {
106
- label: (
107
- <Text>
108
- Paste OpenCode Zen API key{' '}
109
- <Text dimColor={true}>Access paid models, pay-as-you-go</Text>
110
- {'\n'}
111
- </Text>
112
- ),
113
- value: 'api_key',
114
- },
115
- ] as const
116
-
117
- async function handleMenuSelection(value: string): Promise<void> {
118
- setStatus(null)
119
-
120
- if (value === 'api_key') {
121
- setInputValue('')
122
- setCursorOffset(0)
123
- setMode('api_key')
124
- return
125
- }
126
-
127
- if (value === 'free_models') {
128
- setMode('free_models')
129
- // Fetch models when entering free models mode
130
- if (availableModels.length === 0) {
131
- await fetchModels()
132
- }
133
- return
134
- }
135
- }
136
-
137
- async function handleFreeModelSelect(modelValue?: string): Promise<void> {
138
- const modelToSave = modelValue || selectedModel
139
- if (!modelToSave) return
140
-
141
  setIsBusy(true)
142
  setStatus(null)
143
  try {
144
- // 免费模型不需要 API Key,直接保存模型名称
145
- await saveOpenCodeApiKey('', modelToSave)
146
  onDone()
147
  } catch (error) {
148
  setStatus(error instanceof Error ? error.message : String(error))
@@ -162,7 +54,7 @@ export function OpenCodeLoginFlow({
162
  setIsBusy(true)
163
  setStatus(null)
164
  try {
165
- await saveOpenCodeApiKey(keyToSave)
166
  onDone()
167
  } catch (error) {
168
  setStatus(error instanceof Error ? error.message : String(error))
@@ -175,81 +67,6 @@ export function OpenCodeLoginFlow({
175
  return (
176
  <Box flexDirection="column" gap={1}>
177
  <Text>Configuring OpenCode Zen for Better-Clawd...</Text>
178
- <Text dimColor={true}>
179
- OpenCode Zen provides free models with no API key required.
180
- </Text>
181
- </Box>
182
- )
183
- }
184
-
185
- if (mode === 'free_models') {
186
- if (isLoadingModels) {
187
- return (
188
- <Box flexDirection="column" gap={1}>
189
- <Text>Loading available models...</Text>
190
- </Box>
191
- )
192
- }
193
-
194
- if (availableModels.length === 0) {
195
- return (
196
- <Box flexDirection="column" gap={1}>
197
- <Text bold={true}>No models available</Text>
198
- <Text dimColor={true}>
199
- {status || 'Failed to fetch models from OpenCode Zen API.'}
200
- </Text>
201
- <Box flexDirection="row" gap={2} marginTop={1}>
202
- <Text color="subtle">Esc to go back</Text>
203
- </Box>
204
- </Box>
205
- )
206
- }
207
-
208
- return (
209
- <Box flexDirection="column" gap={1}>
210
- <Text bold={true}>
211
- {startingMessage ?? 'Select a free model from OpenCode Zen.'}
212
- </Text>
213
- <Text dimColor={true}>
214
- Free models are available from OpenCode Zen API.
215
- </Text>
216
- <Text>Select model:</Text>
217
- <Select
218
- options={availableModels.map(model => ({
219
- label: (
220
- <Text>
221
- {model.label}{' '}
222
- <Text dimColor={true}>{model.description}</Text>
223
- {'\n'}
224
- </Text>
225
- ),
226
- value: model.value,
227
- }))}
228
- onChange={value => {
229
- setSelectedModel(value)
230
- // 选择后自动提交,直接传递 value 避免闭包问题
231
- void handleFreeModelSelect(value)
232
- }}
233
- />
234
- <Box flexDirection="row" gap={2} marginTop={1}>
235
- <Text color="subtle">Select a model to continue</Text>
236
- <Text color="subtle">·</Text>
237
- <Text color="subtle">Esc to cancel</Text>
238
- </Box>
239
- {status ? <Text color="error">{status}</Text> : null}
240
- <Box marginTop={1}>
241
- <TextInput
242
- value=""
243
- onChange={() => {}}
244
- onSubmit={handleFreeModelSelect}
245
- onExit={() => setMode('menu')}
246
- cursorOffset={0}
247
- onChangeCursorOffset={() => {}}
248
- columns={72}
249
- focus={true}
250
- placeholder="Or press Enter to use selected model"
251
- />
252
- </Box>
253
  </Box>
254
  )
255
  }
@@ -297,16 +114,43 @@ export function OpenCodeLoginFlow({
297
  'Better-Clawd can use OpenCode Zen free models or with a Zen API key.'}
298
  </Text>
299
  <Text dimColor={true}>
300
- OpenCode Zen provides free models out of the box no API key required.
301
  {'\n'}
302
- Free models are fetched from the API dynamically.
303
  </Text>
304
  {status ? <Text color="error">{status}</Text> : null}
305
  <Box>
306
  <Select
307
- options={menuOptions}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  onChange={value => {
309
- void handleMenuSelection(value)
 
 
 
 
 
 
310
  }}
311
  />
312
  </Box>
 
1
  import * as React from 'react'
2
  import { useState, useEffect } from 'react'
3
+ import { saveOpenCodeApiKey, getOpenCodeApiKey } from '../utils/auth.js'
4
  import { Box, Text } from '../ink.js'
5
  import TextInput from './TextInput.js'
6
  import { Select } from './CustomSelect/select.js'
 
10
  startingMessage?: string
11
  }
12
 
13
+ type LoginMode = 'menu' | 'api_key'
 
 
 
 
 
 
14
 
15
  export function OpenCodeLoginFlow({
16
  onDone,
 
18
  }: OpenCodeLoginFlowProps): React.ReactNode {
19
  const [mode, setMode] = useState<LoginMode>('menu')
20
  const [isBusy, setIsBusy] = useState(false)
 
21
  const [status, setStatus] = useState<string | null>(null)
22
  const [inputValue, setInputValue] = useState('')
23
  const [cursorOffset, setCursorOffset] = useState(0)
 
24
  const [existingApiKey, setExistingApiKey] = useState<string | null>(null)
 
 
25
 
26
  useEffect(() => {
27
  const key = getOpenCodeApiKey()
28
  if (key) {
29
  setExistingApiKey(key)
30
  }
 
 
 
 
 
31
  }, [])
32
 
33
+ async function handleFreeModels(): Promise<void> {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  setIsBusy(true)
35
  setStatus(null)
36
  try {
37
+ await saveOpenCodeApiKey('', '')
 
38
  onDone()
39
  } catch (error) {
40
  setStatus(error instanceof Error ? error.message : String(error))
 
54
  setIsBusy(true)
55
  setStatus(null)
56
  try {
57
+ await saveOpenCodeApiKey(keyToSave, '')
58
  onDone()
59
  } catch (error) {
60
  setStatus(error instanceof Error ? error.message : String(error))
 
67
  return (
68
  <Box flexDirection="column" gap={1}>
69
  <Text>Configuring OpenCode Zen for Better-Clawd...</Text>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  </Box>
71
  )
72
  }
 
114
  'Better-Clawd can use OpenCode Zen free models or with a Zen API key.'}
115
  </Text>
116
  <Text dimColor={true}>
117
+ Free models require no API key. Use an API key to access paid models.
118
  {'\n'}
119
+ After login, use /model to pick a specific model.
120
  </Text>
121
  {status ? <Text color="error">{status}</Text> : null}
122
  <Box>
123
  <Select
124
+ options={[
125
+ {
126
+ label: (
127
+ <Text>
128
+ Use free models{' '}
129
+ <Text dimColor={true}>No API key required</Text>
130
+ {'\n'}
131
+ </Text>
132
+ ),
133
+ value: 'free_models',
134
+ },
135
+ {
136
+ label: (
137
+ <Text>
138
+ Paste OpenCode Zen API key{' '}
139
+ <Text dimColor={true}>Access paid models, pay-as-you-go</Text>
140
+ {'\n'}
141
+ </Text>
142
+ ),
143
+ value: 'api_key',
144
+ },
145
+ ]}
146
  onChange={value => {
147
+ if (value === 'free_models') {
148
+ void handleFreeModels()
149
+ } else {
150
+ setInputValue('')
151
+ setCursorOffset(0)
152
+ setMode('api_key')
153
+ }
154
  }}
155
  />
156
  </Box>
src/utils/model/model.ts CHANGED
@@ -262,7 +262,7 @@ export function getDefaultMainLoopModelSetting(): ModelName | ModelAlias {
262
  authProvider?: string
263
  openCodeModelName?: string
264
  }
265
- isOpenCodeProvider = config.authProvider === 'opencode' && !!config.openCodeModelName
266
  } catch {
267
  // Ignore errors, fall through to default behavior
268
  }
@@ -273,7 +273,7 @@ export function getDefaultMainLoopModelSetting(): ModelName | ModelAlias {
273
  if (openCodeModelName) {
274
  return openCodeModelName
275
  }
276
- // Fallback to Big Pickle if no model is configured
277
  return 'big-pickle'
278
  }
279
 
 
262
  authProvider?: string
263
  openCodeModelName?: string
264
  }
265
+ isOpenCodeProvider = config.authProvider === 'opencode'
266
  } catch {
267
  // Ignore errors, fall through to default behavior
268
  }
 
273
  if (openCodeModelName) {
274
  return openCodeModelName
275
  }
276
+ // Fallback to big-pickle for free models
277
  return 'big-pickle'
278
  }
279
 
src/utils/model/modelOptions.ts CHANGED
@@ -491,20 +491,31 @@ function getModelOptionsBase(fastMode = false): ModelOption[] {
491
  }
492
 
493
  // OpenCode Zen: Fetch models dynamically from API
 
 
494
  if (getAPIProvider() === 'opencode') {
495
  const defaultOpt = getDefaultOptionForUser(fastMode)
496
  try {
497
  const { getCachedOpencodeModels } = require('../../services/api/opencodeClient.js')
 
498
  const models = getCachedOpencodeModels()
 
 
499
  if (models && Array.isArray(models) && models.length > 0) {
500
- return [
501
- defaultOpt,
502
- ...models.map(m => ({
503
- value: m.id,
504
- label: m.name || m.id,
505
- description: 'OpenCode Zen Model',
506
- })),
507
- ]
 
 
 
 
 
 
508
  }
509
  } catch {
510
  // Ignore errors
 
491
  }
492
 
493
  // OpenCode Zen: Fetch models dynamically from API
494
+ // If no API key, show only free models (ending with -free)
495
+ // If API key is provided, show all models except free ones
496
  if (getAPIProvider() === 'opencode') {
497
  const defaultOpt = getDefaultOptionForUser(fastMode)
498
  try {
499
  const { getCachedOpencodeModels } = require('../../services/api/opencodeClient.js')
500
+ const { getOpenCodeApiKey: getApiKey } = require('../../utils/auth.js')
501
  const models = getCachedOpencodeModels()
502
+ const hasApiKey = !!getApiKey()
503
+
504
  if (models && Array.isArray(models) && models.length > 0) {
505
+ const filtered = hasApiKey
506
+ ? models.filter(m => !m.id.endsWith('-free'))
507
+ : models.filter(m => m.id.endsWith('-free'))
508
+
509
+ if (filtered.length > 0) {
510
+ return [
511
+ defaultOpt,
512
+ ...filtered.map(m => ({
513
+ value: m.id,
514
+ label: m.name || m.id,
515
+ description: hasApiKey ? 'OpenCode Zen Model' : '限时免费模型',
516
+ })),
517
+ ]
518
+ }
519
  }
520
  } catch {
521
  // Ignore errors