Skip to content

Commit b5db30f

Browse files
Sync public snapshot from freebuff-private
Source: CodebuffAI/freebuff-private@bbc7f7a85768f4fea1024fdee1ea3335b8eac777
1 parent 99de3c1 commit b5db30f

12 files changed

Lines changed: 537 additions & 355 deletions

File tree

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,12 @@ Freebuff includes a curated model catalog. The regular picker currently offers:
3434

3535
| Model | Access | Best for |
3636
| --------------------------- | ----------------------- | ----------------------------------------------------------------- |
37-
| **DeepSeek V4 Flash 07/31** | Full access | The default everywhere in full mode; fast coding and tool use |
38-
| **GPT-5.6 Luna** | Full access | Deep reasoning with native images; one session a day |
37+
| **DeepSeek V4 Pro** | Full access | The default everywhere in full mode; deep reasoning, legacy build |
38+
| **DeepSeek V4 Flash 07/31** | Full access | Fast coding and tool use; paused during peak hours |
39+
| **GPT-5.6 Luna** | Full access | Strong all-around with native images; two sessions a day |
3940
| **MiMo 2.5** | Full and limited access | The limited-mode default; balanced performance with image support |
40-
| **DeepSeek V4 Pro** | Full access | Deepest reasoning; one session a day, closed during peak hours |
4141

42-
These limits are **temporary**, and they exist because the providers serving DeepSeek now charge more than free mode can carry. V4 Pro is one session a day and closed during peak hours; GPT-5.6 Luna is one a day; V4 Flash uses your remaining premium sessions; models may serve from a quantized (Q8_0) build. MiMo 2.5 stays unlimited. All of it is intended to be reverted.
42+
These limits are **temporary**, and they exist because the providers serving DeepSeek now charge more than free mode can carry. V4 Flash is paused during peak hours, when V4 Pro — a legacy build, and the cheapest premium row we serve — stays available in its place; GPT-5.6 Luna is two sessions a day; models may serve from a quantized (Q8_0) build. MiMo 2.5 stays unlimited. All of it is intended to be reverted.
4343

4444
Beyond the regular picker:
4545

README.zh-CN.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,12 @@ Freebuff 提供经过筛选的模型目录。常规模型选择器目前包括
3434

3535
| 模型 | 访问范围 | 适用场景 |
3636
| --------------------------- | -------------- | ------------------------------------------------ |
37-
| **DeepSeek V4 Flash 07/31** | 完整访问 | 完整模式下所有平台的默认模型;快速编程和工具调用 |
38-
| **GPT-5.6 Luna** | 完整访问 | 深度推理,原生支持图像;每天一次会话 |
37+
| **DeepSeek V4 Pro** | 完整访问 | 完整模式下所有平台的默认模型;深度推理,旧版本 |
38+
| **DeepSeek V4 Flash 07/31** | 完整访问 | 快速编程和工具调用;高峰时段停用 |
39+
| **GPT-5.6 Luna** | 完整访问 | 综合能力强,原生支持图像;每天两次会话 |
3940
| **MiMo 2.5** | 完整和受限访问 | 受限模式的默认模型;均衡性能并支持图像 |
40-
| **DeepSeek V4 Pro** | 完整访问 | 推理最深入;每天一次会话,高峰时段停用 |
4141

42-
以下限制是**临时**的,原因是为 DeepSeek 提供服务的供应商收费已超出免费模式可承受的范围。V4 Pro 每天一次会话,并在高峰时段停用;GPT-5.6 Luna 每天一次;V4 Flash 占用你剩余的高级会话;模型可能由量化(Q8_0)版本提供服务。MiMo 2.5 保持无限使用。这些调整都计划在之后恢复。
42+
以下限制是**临时**的,原因是为 DeepSeek 提供服务的供应商收费已超出免费模式可承受的范围。V4 Flash 在高峰时段停用,此时由 V4 Pro(旧版本,也是我们提供的最便宜的高级模型)接替使用;GPT-5.6 Luna 每天两次会话;模型可能由量化(Q8_0)版本提供服务。MiMo 2.5 保持无限使用。这些调整都计划在之后恢复。
4343

4444
常规模型选择器之外:
4545

bun.lock

Lines changed: 36 additions & 58 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/src/components/__tests__/freebuff-model-selector.test.tsx

Lines changed: 79 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -186,51 +186,76 @@ describe('FreebuffModelSelector tier layout', () => {
186186
expect(frame).not.toContain('› MiniMax M3')
187187
})
188188

189-
test('shows the switch-to-Flash nudge only on the row the user is on', async () => {
189+
/**
190+
* ARMED, NOT DELETED. The catalog carries NO supersedes notice as of
191+
* 2026-08-21: V4 Pro held the last one ("V4 Flash is what we recommend") and
192+
* it was removed when Pro moved to a flat-priced lane and Flash became the
193+
* row that sleeps at peak — pointing Pro at Flash now steers users to a model
194+
* that is closed for ten hours precisely when Pro is their best option.
195+
*
196+
* The RULE this guards — only the selected row nags, so the list does not
197+
* repeat one notice on every row it applies to — is UI logic that outlives
198+
* any particular pair of models, so it runs again automatically the next time
199+
* a supersedes notice exists rather than being re-derived from a regression.
200+
*/
201+
const allModelIds = FREEBUFF_MODELS.map((m) => m.id)
202+
const supersededModelId = allModelIds.find((id) =>
203+
getFreebuffModelSupersededBy(id, allModelIds),
204+
)
205+
test.if(Boolean(supersededModelId))(
206+
'shows the supersedes nudge only on the row the user is on',
207+
async () => {
208+
useFreebuffSessionStore.getState().setSession({
209+
status: 'none',
210+
accessTier: 'full',
211+
})
212+
// Assert against the real copy rather than a hardcoded fragment, so
213+
// rewording the notice doesn't fail this test for the wrong reason. It
214+
// must still render on ONE line — the width math reserves its length.
215+
const superseded = getFreebuffModelSupersededBy(
216+
supersededModelId!,
217+
allModelIds,
218+
)!
219+
const notice = superseded.notice
220+
const occurrences = (frame: string) => frame.split(notice).length - 1
221+
222+
// On a superseded model: the nudge appears, once, on that model's card.
223+
useFreebuffModelStore.getState().setSelectedModel(supersededModelId!)
224+
const onSuperseded = (await renderSelector()).captureCharFrame()
225+
expect(occurrences(onSuperseded)).toBe(1)
226+
227+
// On a row that is NOT superseded, that notice stays quiet — otherwise
228+
// the list would repeat it on every row it applies to.
229+
const otherId = allModelIds.find((id) => id !== supersededModelId)!
230+
useFreebuffModelStore.getState().setSelectedModel(otherId)
231+
const onOther = (await renderSelector()).captureCharFrame()
232+
expect(occurrences(onOther)).toBe(0)
233+
234+
// And on the replacement itself: no nudge at all.
235+
useFreebuffModelStore
236+
.getState()
237+
.setSelectedModel(superseded.modelId)
238+
const onCurrent = (await renderSelector()).captureCharFrame()
239+
expect(occurrences(onCurrent)).toBe(0)
240+
},
241+
)
242+
243+
test('badges the new builds so a returning user notices they changed', async () => {
244+
// Independent of the supersedes machinery above, which is why it is its own
245+
// test now: `isNew` is a property of the row, and Flash still carries it.
190246
useFreebuffSessionStore.getState().setSession({
191247
status: 'none',
192248
accessTier: 'full',
193249
})
194-
// Assert against the real copy rather than a hardcoded fragment, so
195-
// rewording the notice doesn't fail this test for the wrong reason. It must
196-
// still render on ONE line — the width math reserves exactly its length.
197-
// V4 Pro carries the only supersedes notice left: MiniMax M3's went with
198-
// the model on 2026-08-20, and MiMo's went when Flash became premium.
199-
const notice = getFreebuffModelSupersededBy(
200-
FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID,
201-
FREEBUFF_MODELS.map((m) => m.id),
202-
)!.notice
203-
const occurrences = (frame: string) => frame.split(notice).length - 1
204-
205-
// On a superseded model: the nudge appears, once, on that model's card.
206-
useFreebuffModelStore
207-
.getState()
208-
.setSelectedModel(FREEBUFF_DEEPSEEK_V4_PRO_MODEL_ID)
209-
const onSuperseded = (await renderSelector()).captureCharFrame()
210-
expect(occurrences(onSuperseded)).toBe(1)
211-
// It names the dated build, which is what the row it steers to is labelled.
212-
expect(notice).toContain('DeepSeek V4 Flash 07/31')
213-
// The new builds are badged so a returning user notices they changed.
214-
expect(onSuperseded).toContain('NEW')
215-
216-
// MiMo is superseded too and is on screen here, but only the selected row
217-
// nags — otherwise the list would repeat the same notice on every row it
218-
// applies to. MiMo 2.5 is on screen and, when it is not the selection, its
219-
// own notice stays quiet. (V4 Pro played this part until it was paused on
220-
// 2026-08-18 and left the picker entirely.)
221-
useFreebuffModelStore
222-
.getState()
223-
.setSelectedModel(FREEBUFF_MIMO_V25_MODEL_ID)
224-
const onUnsuperseded = (await renderSelector()).captureCharFrame()
225-
expect(onUnsuperseded).toContain('MiMo 2.5')
226-
227-
// On the replacement itself: no nudge at all. (Picking the recommended
228-
// model also collapses the picker to its hero card.)
250+
// Selected explicitly: `isNew` sits on the V4 Flash row, and the collapsed
251+
// view draws only the card the user is on — which is V4 Pro by default
252+
// since 2026-08-21, and carries no badge.
229253
useFreebuffModelStore
230254
.getState()
231255
.setSelectedModel(FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID)
232-
const onCurrent = (await renderSelector()).captureCharFrame()
233-
expect(occurrences(onCurrent)).toBe(0)
256+
const frame = (await renderSelector()).captureCharFrame()
257+
expect(frame).toContain('DeepSeek V4 Flash 07/31')
258+
expect(frame).toContain('NEW')
234259
})
235260

236261
test('places the exhausted-quota recommendation beneath UNLIMITED', async () => {
@@ -258,17 +283,18 @@ describe('FreebuffModelSelector tier layout', () => {
258283
const frame = setup.captureCharFrame()
259284
const premiumHeaderIndex = frame.indexOf('PREMIUM')
260285
const unlimitedHeaderIndex = frame.indexOf('UNLIMITED')
261-
const recommendedLabelIndex = frame.indexOf('RECOMMENDED')
262-
// The unlimited recommendation is MiMo 2.5 since 2026-08-18 — Flash moved
263-
// into the premium group and can no longer be what a spent user lands on.
264-
const recommendedModelIndex = frame.indexOf(
265-
'MiMo 2.5',
266-
recommendedLabelIndex,
267-
)
286+
// Located by the ROW rather than by a ' RECOMMENDED ' border title, which
287+
// was removed on 2026-08-21 — nothing in the picker is badged as a
288+
// recommendation any more. The property under test is unchanged: when the
289+
// premium pool is spent, the row the user is steered onto sits in the
290+
// UNLIMITED group rather than above the list.
291+
//
292+
// MiMo 2.5 is that row since 2026-08-18 — Flash moved into the premium
293+
// group and can no longer be what a spent user lands on.
294+
const heroModelIndex = frame.indexOf('MiMo 2.5', unlimitedHeaderIndex)
268295

269296
expect(unlimitedHeaderIndex).toBeGreaterThan(premiumHeaderIndex)
270-
expect(recommendedLabelIndex).toBeGreaterThan(unlimitedHeaderIndex)
271-
expect(recommendedModelIndex).toBeGreaterThan(recommendedLabelIndex)
297+
expect(heroModelIndex).toBeGreaterThan(unlimitedHeaderIndex)
272298
})
273299

274300
test('collapses to the unlimited hero when the premium default is spent', async () => {
@@ -282,9 +308,13 @@ describe('FreebuffModelSelector tier layout', () => {
282308
useFreebuffSessionStore.getState().setSession({
283309
status: 'none',
284310
accessTier: 'full',
311+
// Keyed on the CURRENT default rather than on a named model: the default
312+
// moved from Flash to V4 Pro on 2026-08-21, and this fixture has to
313+
// exhaust the pool of whichever row the picker will actually open on, or
314+
// the step-down under test never triggers.
285315
rateLimitsByModel: {
286-
[FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID]: {
287-
model: FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID,
316+
[DEFAULT_FREEBUFF_MODEL_ID]: {
317+
model: DEFAULT_FREEBUFF_MODEL_ID,
288318
limit: 6,
289319
period: 'pacific_day',
290320
resetTimeZone: 'America/Los_Angeles',

cli/src/components/freebuff-model-selector.tsx

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,10 @@ import type {
6464
ScrollBoxRenderable,
6565
} from '@opentui/core'
6666

67-
// The picker opens collapsed to a single recommended hero so a new user can
68-
// start with one Enter press without reading six boxes. The "see all models"
67+
// The picker opens collapsed to a single hero card so a new user can start with
68+
// one Enter press without reading six boxes. The hero is the DEFAULT pick, not
69+
// a recommendation — the ' RECOMMENDED ' badge and every supersedes nudge were
70+
// removed on 2026-08-21, leaving list ORDER as the only steer. The "see all models"
6971
// toggle reveals the rest, grouped into the same product/availability tiers.
7072
//
7173
// Section grouping (expanded view): every model row, including the recommended
@@ -77,10 +79,12 @@ import type {
7779
// amber when exhausted, the moment its rows grey out). When collapsed there's
7880
// no PREMIUM header, so the parent keeps a below-picker counter for the
7981
// collapsed state (and for the limited tier, which has no premium section).
80-
// Since 2026-08-12 the full-access hero is the PREMIUM DeepSeek V4 Pro 08/13
81-
// again (DEFAULT_FREEBUFF_MODEL_ID), so it draws on that same pool and flips to
82-
// the unlimited Flash once the pool empties — the hero must always be joinable.
83-
// The limited tier's hero stays the always-available Flash. UNLIMITED needs no
82+
// The full-access hero is DeepSeek V4 Pro (DEFAULT_FREEBUFF_MODEL_ID) as of
83+
// 2026-08-21, so it draws on the premium pool and flips to the unlimited MiMo
84+
// once that empties — the hero must always be joinable. Pro is also the only
85+
// premium row open at every hour, which is why it holds this slot: V4 Flash now
86+
// closes for the ten-hour peak window. The limited tier's hero is MiMo 2.5,
87+
// which is that tier's entire catalog. UNLIMITED needs no
8488
// annotation. Empty sections are filtered so a model set with no premium (or no
8589
// unlimited) entries doesn't render an orphan header.
8690
//
@@ -112,9 +116,10 @@ const TOGGLE_ID = '__freebuff_toggle__'
112116
* Space) commits the focused row — or, on the toggle, expands/collapses the
113117
* list. Mouse click commits in one step.
114118
*
115-
* Layout: the recommended model renders as a titled "RECOMMENDED" card. When
116-
* expanded, every full-access row is grouped into PREMIUM / UNLIMITED sections
117-
* so the recommended row's tier is explicit without a per-row chip; the shared
119+
* Layout: the collapsed view renders one card — the default starting pick, NOT
120+
* a recommendation; nothing is badged and the catalog names no recommended
121+
* model. When expanded, every full-access row is grouped into PREMIUM /
122+
* UNLIMITED sections so that row's tier is explicit without a per-row chip; the shared
118123
* premium-session quota rides the PREMIUM header. Names align in a column
119124
* so taglines line up across rows, and the secondary details (warning /
120125
* deployment hours) always sit on their own centered line under the name —
@@ -826,10 +831,11 @@ export const FreebuffModelSelector: React.FC<FreebuffModelSelectorProps> = ({
826831
const warningColor = theme.secondary
827832

828833
// Focused row gets the bright primary border (and arrow). Every other row —
829-
// including the recommended card when the cursor has moved elsewhere — stays
834+
// including the collapsed hero when the cursor has moved elsewhere — stays
830835
// quiet (gray border, brightening only on hover) so it never competes with
831-
// the user's current selection. The recommended card still reads as special
832-
// via its "RECOMMENDED" border title, which the border color carries.
836+
// the user's current selection. Since the ' RECOMMENDED ' border title was
837+
// removed on 2026-08-21 the hero has no visual distinction of its own,
838+
// which is the intent: it is where the cursor starts, not a pick we endorse.
833839
const borderColor = isFocused
834840
? theme.primary
835841
: isHovered
@@ -909,8 +915,12 @@ export const FreebuffModelSelector: React.FC<FreebuffModelSelectorProps> = ({
909915
<Button
910916
key={model.id}
911917
id={model.id}
912-
title={recommended ? ' RECOMMENDED ' : undefined}
913-
titleAlignment={recommended ? 'left' : undefined}
918+
// NO ' RECOMMENDED ' title as of 2026-08-21. The collapsed view still
919+
// opens on one card so a new user can start with a single Enter, but
920+
// that card is a STARTING POSITION rather than an endorsement — the
921+
// catalog no longer names a recommended model, and ordering is the only
922+
// steer left. Re-adding a title here re-adds the recommendation.
923+
titleAlignment={undefined}
914924
onClick={() => {
915925
setFocusedId(model.id)
916926
if (canJoin) pick(model.id)

0 commit comments

Comments
 (0)