Skip to content

Commit 7bdce09

Browse files
Sync public snapshot from freebuff-private
Source: CodebuffAI/freebuff-private@45075206cbbf56aec06c7b9f4b6f7aa1e9dcc8aa
1 parent fc33f9f commit 7bdce09

14 files changed

Lines changed: 287 additions & 947 deletions

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ Freebuff includes a curated model catalog. The regular picker currently offers:
3838
| **GLM 5.3 Flash** | Full access | Deepest reasoning, unmetered |
3939
| **GPT-5.6 Luna** | Full access | Strong all-around with native images |
4040
| **MiMo 2.5** | Full and limited access | Balanced performance with image support |
41-
| **Solar Pro 4** | Full access | Limited-time trial; 524K context, text only |
41+
| **Solar Pro 4** | Full and limited access | Limited-time trial; 524K context, text only; unmetered at full access |
4242
| **Gemini 3.8 Flash** | Full access | 1M context; reads images, audio, video and PDFs; the priciest row per message |
4343

44-
Most models draw on your normal daily sessions rather than a separate limit. GLM 5.3 Flash, DeepSeek V4 Flash 07/31 and MiMo 2.5 are unmetered and cost no session at all. Models may still serve from a quantized (Q8_0) build.
44+
Most models draw on your normal daily sessions rather than a separate limit. GLM 5.3 Flash, DeepSeek V4 Flash 07/31, MiMo 2.5 and Solar Pro 4 are unmetered at full access and cost no session at all. Models may still serve from a quantized (Q8_0) build.
4545

4646
DeepSeek V4 Pro was retired from the catalog; GLM 5.3 Flash replaces it as the deep-reasoning pick.
4747

@@ -64,7 +64,7 @@ Freebuff uses specialized agents instead of sending every task through one model
6464

6565
## Free access
6666

67-
Freebuff is available in every country. Supported regions receive full access; other regions and VPN users receive limited access, currently MiMo 2.5 and DeepSeek V4 Flash 07/31 with six one-hour sessions per day, earnable up to seven.
67+
Freebuff is available in every country. Supported regions receive full access; other regions and VPN users receive limited access, currently DeepSeek V4 Flash 07/31, MiMo 2.5, and Solar Pro 4 with six one-hour sessions per day, earnable up to seven.
6868

6969
Text ads support the included models. Freebuff shows the applicable session limits and any model-specific data-use notice before you start.
7070

README.zh-CN.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ Freebuff 提供经过筛选的模型目录。常规模型选择器目前包括
3838
| **GLM 5.3 Flash** | 完整访问 | 推理最深入,且不消耗会话 |
3939
| **GPT-5.6 Luna** | 完整访问 | 综合能力强,原生支持图像 |
4040
| **MiMo 2.5** | 完整和受限访问 | 均衡性能并支持图像 |
41-
| **Solar Pro 4** | 完整访问 | 限时试用;52.4 万上下文,仅支持文本 |
41+
| **Solar Pro 4** | 完整和受限访问 | 限时试用;52.4 万上下文,仅支持文本;完整访问下不消耗会话 |
4242
| **Gemini 3.8 Flash** | 完整访问 | 100 万上下文;支持图像、音频、视频和 PDF;单条消息成本最高 |
4343

44-
大多数模型使用你的常规每日会话,而不再各自设限。GLM 5.3 Flash、DeepSeek V4 Flash 07/31MiMo 2.5 保持无限使用,完全不消耗会话。模型仍可能由量化(Q8_0)版本提供服务。
44+
大多数模型使用你的常规每日会话,而不再各自设限。GLM 5.3 Flash、DeepSeek V4 Flash 07/31MiMo 2.5 和 Solar Pro 4 在完整访问下保持无限使用,完全不消耗会话。模型仍可能由量化(Q8_0)版本提供服务。
4545

4646
DeepSeek V4 Pro 已从模型目录中下线,由 GLM 5.3 Flash 接替其深度推理的位置。
4747

@@ -64,7 +64,7 @@ Freebuff 使用专业化智能体,而不是把所有任务都交给同一个
6464

6565
## 免费访问
6666

67-
Freebuff 在所有国家和地区均可使用。受支持的地区提供完整访问;其他地区以及使用 VPN 的用户获得受限访问,目前包括 MiMo 2.5 和 DeepSeek V4 Flash 07/31,每天可使用六个一小时会话,最多可赚取七个。
67+
Freebuff 在所有国家和地区均可使用。受支持的地区提供完整访问;其他地区以及使用 VPN 的用户获得受限访问,目前包括 DeepSeek V4 Flash 07/31、MiMo 2.5 和 Solar Pro 4,每天可使用六个一小时会话,最多可赚取七个。
6868

6969
内置模型由文字广告支持。开始前,Freebuff 会显示适用的会话限制以及模型特定的数据使用提示。
7070

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

Lines changed: 6 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -163,21 +163,14 @@ describe('FreebuffModelSelector tier layout', () => {
163163
expect(frame).not.toContain('for small tasks')
164164
})
165165

166-
test('orders the premium rows above UNLIMITED, saved model focused', async () => {
166+
test('orders the premium row above UNLIMITED, saved unlimited model focused', async () => {
167167
useFreebuffSessionStore.getState().setSession({
168168
status: 'none',
169169
accessTier: 'full',
170170
})
171-
// The saved pick has to be a PREMIUM row that is NOT the recommended hero:
172-
// premium or the tier headers it is being ordered against don't apply to
173-
// it, non-hero or the landing picker opens collapsed and there are no tier
174-
// headers at all. The hero is GPT-5.6 Luna since 2026-08-24, which leaves
175-
// exactly one other premium row — Solar Pro 4 today.
176-
//
177-
// The occupants keep leaving downward: V4 Flash left
178-
// FREEBUFF_PREMIUM_MODEL_IDS on 2026-08-24, V4 Pro was withdrawn on 08-26,
179-
// GLM 5.3 Flash was un-premiumed on 08-28 and moved into UNLIMITED — below
180-
// the header this asserts it sits above. Read the list, not this comment.
171+
// Solar Pro 4 moved into UNLIMITED on 2026-09-03. Keeping it as the saved
172+
// pick exercises both section ordering and focus without relying on a
173+
// second premium row that no longer exists.
181174
useFreebuffModelStore
182175
.getState()
183176
.setSelectedModel(FREEBUFF_SOLAR_PRO_4_MODEL_ID)
@@ -191,10 +184,8 @@ describe('FreebuffModelSelector tier layout', () => {
191184

192185
expect(premiumHeaderIndex).toBeGreaterThanOrEqual(0)
193186
expect(recommendedModelIndex).toBeGreaterThan(premiumHeaderIndex)
194-
expect(selectedModelIndex).toBeGreaterThan(recommendedModelIndex)
195-
// MiniMax M3 anchored the tail of this list until it was withdrawn on
196-
// 2026-08-20 and left the picker entirely.
197-
expect(unlimitedHeaderIndex).toBeGreaterThan(selectedModelIndex)
187+
expect(unlimitedHeaderIndex).toBeGreaterThan(recommendedModelIndex)
188+
expect(selectedModelIndex).toBeGreaterThan(unlimitedHeaderIndex)
198189
// The cursor sits on the SAVED pick, not on the recommendation.
199190
expect(frame).toContain('› Solar Pro 4')
200191
expect(frame).not.toContain('› GPT-5.6 Luna')
@@ -471,103 +462,6 @@ describe('FreebuffModelSelector tier layout', () => {
471462
expect(rowOf(frame, 'MiniMax M3')).not.toContain('Reasoning')
472463
})
473464

474-
test('sizes and centres a row around its per-row quota chip', async () => {
475-
// The chip is drawn on a row that answers to a DIFFERENT pool than its
476-
// section header, and it was missing from BOTH the centering math and the
477-
// height estimate — visible only once a user had spent a Luna session,
478-
// until the server began sending unused pool rows and it became every
479-
// full-access picker.
480-
//
481-
// WHICH row wears it is arithmetic, not semantic: getFreebuffSectionQuotas
482-
// gives the header to the pool MOST rows share and breaks ties toward the
483-
// earlier row. The occupant has moved with every premium departure — Flash
484-
// out on 2026-08-24, V4 Pro withdrawn 08-26, GLM 5.3 Flash un-premiumed
485-
// 08-28. The invariant under test — a second line the width and height math
486-
// must both know about — is unchanged; only the row it lands on moves, so
487-
// this drives it from the CURRENT premium list rather than naming a row.
488-
const resetAt = new Date(FIXED_NOW_MS + 60_000).toISOString()
489-
const pool = (
490-
model: string,
491-
poolId: string,
492-
poolLabel: string,
493-
limit: number,
494-
) => ({
495-
model,
496-
pool: poolId,
497-
poolLabel,
498-
limit,
499-
period: 'pacific_day' as const,
500-
resetTimeZone: 'America/Los_Angeles',
501-
resetAt,
502-
windowHours: 24,
503-
recentCount: 0,
504-
})
505-
useFreebuffSessionStore.getState().setSession({
506-
status: 'none',
507-
accessTier: 'full',
508-
// Flash sends no pool row at all since 2026-08-24: it is unmetered.
509-
rateLimitsByModel: {
510-
[FREEBUFF_GPT_5_6_LUNA_MODEL_ID]: pool(
511-
FREEBUFF_GPT_5_6_LUNA_MODEL_ID,
512-
'premium',
513-
'Premium',
514-
4,
515-
),
516-
// A row answering to a pool the section header does NOT speak for, so
517-
// it carries its own chip. SYNTHESISED rather than read from
518-
// FREEBUFF_PER_MODEL_SESSION_CAPS, which is empty since 2026-08-28 —
519-
// this test is about the width and height math around a second line,
520-
// not about which model happens to be capped this week, and tying it to
521-
// a real cap is what made it break every time one moved.
522-
[FREEBUFF_SOLAR_PRO_4_MODEL_ID]: pool(
523-
FREEBUFF_SOLAR_PRO_4_MODEL_ID,
524-
'solar_trial',
525-
'Solar Pro 4',
526-
2,
527-
),
528-
},
529-
})
530-
useFreebuffModelStore
531-
.getState()
532-
// NOT the hero, so the picker opens expanded and the chip under test is
533-
// drawn at all. The expanded list still draws the Flash row, which
534-
// supplies the warning-ONLY second line asserted below.
535-
.setSelectedModel(FREEBUFF_GLM_V53_FLASH_MODEL_ID)
536-
537-
const frame = (await renderSelector()).captureCharFrame()
538-
// Gutters inside the card borders, which is what "centred" means here and
539-
// what a length the math didn't know about throws off. Asserted for the
540-
// ordinary warning line too, so this pins the invariant rather than the
541-
// one string that broke it.
542-
const gutters = (line: string) => {
543-
const inner = line.slice(line.indexOf('│') + 1, line.lastIndexOf('│'))
544-
return [
545-
inner.length - inner.trimStart().length,
546-
inner.length - inner.trimEnd().length,
547-
]
548-
}
549-
const lines = frame.split('\n')
550-
// The second line carrying a per-row chip. Anchored on the chip TEXT, so a
551-
// chip that stops being drawn fails here rather than quietly re-measuring
552-
// some warning-only line instead. A per-row label is longer than the shared
553-
// one, which is the case the width math has to survive.
554-
const chipLine = lines.find((l) => l.includes('Solar Pro 4: 0 of 2 used'))
555-
// Flash carries the training warning with nothing after it — the shape the
556-
// width math already handled, which is the "ordinary warning line" above.
557-
const warningOnlyLine = lines.find(
558-
(l) => l.includes('May use data for AI training') && !l.includes('used'),
559-
)
560-
expect(chipLine).toBeDefined()
561-
expect(warningOnlyLine).toBeDefined()
562-
for (const line of [chipLine!, warningOnlyLine!]) {
563-
const [left, right] = gutters(line)
564-
expect(Math.abs(left - right)).toBeLessThanOrEqual(1)
565-
}
566-
// A row the height estimate does not know has a second line costs the list
567-
// a row on the first frame, which cut the toggle off the bottom.
568-
expect(frame).toContain('Show fewer')
569-
})
570-
571465
test('says nothing about a premium quota the account does not have', async () => {
572466
// Quota-exempt accounts (god/admin) draw on no free pool, so no snapshot
573467
// arrives. The header used to fall back to the static limit and render
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { describe, expect, test } from 'bun:test'
2+
3+
import {
4+
FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID,
5+
FREEBUFF_GEMINI_38_FLASH_MODEL_ID,
6+
FREEBUFF_GLM_V53_FLASH_MODEL_ID,
7+
FREEBUFF_GPT_5_6_LUNA_MODEL_ID,
8+
FREEBUFF_MODELS,
9+
FREEBUFF_SOLAR_PRO_4_MODEL_ID,
10+
FREEBUFF_WEB_PREMIUM_MODEL_IDS,
11+
} from '../constants/freebuff-models'
12+
import {
13+
freebuffDesktopConcurrencyLimits,
14+
getFreebuffDesktopConcurrency,
15+
} from '../constants/freebuff-desktop-sessions'
16+
17+
describe('Freebuff Desktop session concurrency', () => {
18+
test('projects account ceilings', () => {
19+
expect(freebuffDesktopConcurrencyLimits('full', false)).toEqual({
20+
'slot-bound': 1,
21+
'multi-tab': 3,
22+
})
23+
expect(freebuffDesktopConcurrencyLimits('limited', false)).toEqual({
24+
'slot-bound': 1,
25+
'multi-tab': 0,
26+
})
27+
expect(freebuffDesktopConcurrencyLimits('limited', true)).toEqual({
28+
'slot-bound': 3,
29+
'multi-tab': 8,
30+
})
31+
})
32+
33+
test('classifies models by tier and plan', () => {
34+
const cases = [
35+
[
36+
`${FREEBUFF_GPT_5_6_LUNA_MODEL_ID}-20260709`,
37+
'full',
38+
false,
39+
'slot-bound',
40+
],
41+
[FREEBUFF_GEMINI_38_FLASH_MODEL_ID, 'full', false, 'slot-bound'],
42+
[FREEBUFF_GLM_V53_FLASH_MODEL_ID, 'full', false, 'multi-tab'],
43+
[FREEBUFF_SOLAR_PRO_4_MODEL_ID, 'full', false, 'multi-tab'],
44+
[FREEBUFF_SOLAR_PRO_4_MODEL_ID, 'limited', false, 'slot-bound'],
45+
[FREEBUFF_SOLAR_PRO_4_MODEL_ID, 'limited', true, 'multi-tab'],
46+
[FREEBUFF_DEEPSEEK_V4_FLASH_MODEL_ID, 'limited', false, 'slot-bound'],
47+
] as const
48+
for (const [model, tier, paidPlan, expected] of cases) {
49+
expect(getFreebuffDesktopConcurrency(model, tier, paidPlan)).toBe(
50+
expected,
51+
)
52+
}
53+
54+
const desktopModels = new Set<string>(FREEBUFF_MODELS.map(({ id }) => id))
55+
for (const id of FREEBUFF_WEB_PREMIUM_MODEL_IDS) {
56+
if (desktopModels.has(id)) {
57+
expect(getFreebuffDesktopConcurrency(id, 'full')).toBe('slot-bound')
58+
}
59+
}
60+
})
61+
})

0 commit comments

Comments
 (0)