Skip to content

Commit c2e5650

Browse files
committed
Ads no longer grant credits
1 parent 31c320c commit c2e5650

File tree

9 files changed

+16
-112
lines changed

9 files changed

+16
-112
lines changed

cli/src/chat.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1449,8 +1449,6 @@ export const Chat = ({
14491449
adData?.variant === 'choice' ? (
14501450
<ChoiceAdBanner
14511451
ads={adData.ads}
1452-
onDisableAds={handleDisableAds}
1453-
isFreeMode={IS_FREEBUFF || agentMode === 'FREE'}
14541452
onImpression={recordImpression}
14551453
/>
14561454
) : (

cli/src/commands/ads.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export const handleAdsEnable = (): {
1616
return {
1717
postUserMessage: (messages) => [
1818
...messages,
19-
getSystemMessage('Ads enabled. You will see contextual ads above the input and earn credits from impressions.'),
19+
getSystemMessage('Ads enabled. You will see contextual ads above the input.'),
2020
],
2121
}
2222
}

cli/src/components/ad-banner.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,7 @@ export const AdBanner: React.FC<AdBannerProps> = ({ ad, onDisableAds, isFreeMode
150150
{domain}
151151
</text>
152152
)}
153-
<box style={{ flexGrow: 1 }} />
154-
{!IS_FREEBUFF && ad.credits != null && ad.credits > 0 && (
155-
<text style={{ fg: theme.muted }}>+{ad.credits} credits</text>
156-
)}
153+
157154
</box>
158155
</Button>
159156
{/* Info panel: shown when Ad label is clicked, below the ad */}
@@ -179,7 +176,7 @@ export const AdBanner: React.FC<AdBannerProps> = ({ ad, onDisableAds, isFreeMode
179176
<text style={{ fg: theme.muted, flexShrink: 1 }}>
180177
{IS_FREEBUFF
181178
? 'Ads help keep Freebuff free.'
182-
: 'Ads are optional and earn you credits on each impression. Feel free to hide them anytime.'}
179+
: 'Ads are optional. Feel free to hide them anytime.'}
183180
</text>
184181
<Button
185182
onClick={() => setShowInfoPanel(false)}

cli/src/components/choice-ad-banner.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import React, { useState, useMemo, useEffect } from 'react'
55
import { Button } from './button'
66
import { useTerminalDimensions } from '../hooks/use-terminal-dimensions'
77
import { useTheme } from '../hooks/use-theme'
8+
import { BORDER_CHARS } from '../utils/ui-constants'
89

910
import type { AdResponse } from '../hooks/use-gravity-ad'
1011

@@ -106,6 +107,7 @@ export const ChoiceAdBanner: React.FC<ChoiceAdBannerProps> = ({ ads, onImpressio
106107
height: CARD_HEIGHT,
107108
borderStyle: 'single',
108109
borderColor: isHovered ? hoverBorderColor : theme.muted,
110+
customBorderChars: BORDER_CHARS,
109111
paddingLeft: 1,
110112
paddingRight: 1,
111113
flexDirection: 'column',

cli/src/components/usage-banner.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,6 @@ export const UsageBanner = ({ showTime }: { showTime: number }) => {
110110
}
111111

112112
const colorLevel = getBannerColorLevel(activeData.remainingBalance)
113-
const adCredits = activeData.balanceBreakdown?.ad
114113
const renewalDate = activeData.next_quota_reset ? formatRenewalDate(activeData.next_quota_reset) : null
115114

116115
const activeSubscription = subscriptionData?.hasSubscription ? subscriptionData : null
@@ -152,9 +151,7 @@ export const UsageBanner = ({ showTime }: { showTime: number }) => {
152151
{activeData.remainingBalance?.toLocaleString() ?? '?'} credits
153152
</text>
154153
)}
155-
{adCredits != null && adCredits > 0 && (
156-
<text style={{ fg: theme.muted }}>{`(${adCredits} from ads)`}</text>
157-
)}
154+
158155
{!activeSubscription && renewalDate && (
159156
<>
160157
<text style={{ fg: theme.muted }}>· Renews:</text>

cli/src/data/slash-commands.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,12 @@ const ALL_SLASH_COMMANDS: SlashCommand[] = [
8383
{
8484
id: 'ads:enable',
8585
label: 'ads:enable',
86-
description: 'Enable contextual ads and earn credits',
86+
description: 'Enable contextual ads',
8787
},
8888
{
8989
id: 'ads:disable',
9090
label: 'ads:disable',
91-
description: 'Disable contextual ads and stop earning credits',
91+
description: 'Disable contextual ads',
9292
},
9393
{
9494
id: 'refer-friends',

web/src/app/api/v1/ads/_post.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ export async function postAds(params: {
183183
sessionId: sessionId ?? userId,
184184
placements,
185185
testAd: serverEnv.CB_ENVIRONMENT !== 'prod',
186-
relevancy: 0.1,
186+
relevancy: 0,
187187
...(device ? { device } : {}),
188188
user: {
189189
id: userId,

web/src/app/api/v1/ads/impression/_post.ts

Lines changed: 7 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { createHash } from 'crypto'
2-
31
import { AnalyticsEvent } from '@codebuff/common/constants/analytics-events'
42
import db from '@codebuff/internal/db'
53
import * as schema from '@codebuff/internal/db/schema'
@@ -9,7 +7,6 @@ import { z } from 'zod'
97

108
import { requireUserFromApiKey } from '../../_helpers'
119

12-
import type { processAndGrantCredit as ProcessAndGrantCreditFn } from '@codebuff/billing/grant-credits'
1310
import type { TrackEventFn } from '@codebuff/common/types/contracts/analytics'
1411
import type { GetUserInfoFromApiKeyFn } from '@codebuff/common/types/contracts/database'
1512
import type {
@@ -18,10 +15,6 @@ import type {
1815
} from '@codebuff/common/types/contracts/logger'
1916
import type { NextRequest } from 'next/server'
2017

21-
// Revenue share: users get 75% of payout as credits
22-
const AD_REVENUE_SHARE = 0.75
23-
const MINIMUM_CREDITS_GRANTED = 2
24-
2518
// Rate limiting: max impressions per user per hour
2619
const MAX_IMPRESSIONS_PER_HOUR = 60
2720

@@ -78,22 +71,8 @@ function checkRateLimit(userId: string): boolean {
7871
return true
7972
}
8073

81-
/**
82-
* Generate a deterministic operation ID for deduplication.
83-
* Same user + same impUrl = same operationId, preventing duplicate credits.
84-
*/
85-
function generateImpressionOperationId(userId: string, impUrl: string): string {
86-
const hash = createHash('sha256')
87-
.update(`${userId}:${impUrl}`)
88-
.digest('hex')
89-
.slice(0, 16)
90-
return `ad-imp-${hash}`
91-
}
92-
9374
const bodySchema = z.object({
94-
// Only impUrl needed - we look up the ad data from our database
9575
impUrl: z.url(),
96-
// Mode to determine if credits should be granted (FREE mode gets no credits)
9776
mode: z.string().optional(),
9877
})
9978

@@ -103,22 +82,19 @@ export async function postAdImpression(params: {
10382
logger: Logger
10483
loggerWithContext: LoggerWithContextFn
10584
trackEvent: TrackEventFn
106-
processAndGrantCredit: typeof ProcessAndGrantCreditFn
10785
fetch: typeof globalThis.fetch
10886
}) {
10987
const {
11088
req,
11189
getUserInfoFromApiKey,
11290
loggerWithContext,
11391
trackEvent,
114-
processAndGrantCredit,
11592
fetch,
11693
} = params
11794
const baseLogger = params.logger
11895

11996
// Parse and validate request body
12097
let impUrl: string
121-
let mode: string | undefined
12298
try {
12399
const json = await req.json()
124100
const parsed = bodySchema.safeParse(json)
@@ -129,7 +105,6 @@ export async function postAdImpression(params: {
129105
)
130106
}
131107
impUrl = parsed.data.impUrl
132-
mode = parsed.data.mode
133108
} catch {
134109
return NextResponse.json(
135110
{ error: 'Invalid JSON in request body' },
@@ -203,16 +178,10 @@ export async function postAdImpression(params: {
203178
)
204179
}
205180

206-
// Get payout from the trusted database record
207-
const payout = parseFloat(adRecord.payout)
208-
209-
// Generate deterministic operation ID for deduplication
210-
const operationId = generateImpressionOperationId(userId, impUrl)
211-
212181
// Fire the impression pixel to Gravity
213182
try {
214183
await fetch(impUrl)
215-
logger.info({ userId, operationId, impUrl }, '[ads] Fired impression pixel')
184+
logger.info({ userId, impUrl }, '[ads] Fired impression pixel')
216185
} catch (error) {
217186
logger.warn(
218187
{
@@ -224,82 +193,25 @@ export async function postAdImpression(params: {
224193
},
225194
'[ads] Failed to fire impression pixel',
226195
)
227-
// Continue anyway - we still want to grant credits
196+
// Continue anyway - we still want to record the impression
228197
}
229198

230-
// Calculate credits to grant (75% of payout, converted to credits)
231-
// Payout is in dollars, credits are 1:1 with cents, so multiply by 100
232-
const userShareDollars = payout * AD_REVENUE_SHARE
233-
const creditsToGrant = Math.max(
234-
MINIMUM_CREDITS_GRANTED + Math.floor(3 * Math.random()),
235-
Math.floor(userShareDollars * 100),
236-
)
237-
238-
let creditsGranted = 0
239-
// FREE mode should not grant any credits
240-
if (mode !== 'FREE' && creditsToGrant > 0) {
241-
try {
242-
await processAndGrantCredit({
243-
userId,
244-
amount: creditsToGrant,
245-
type: 'ad',
246-
description: `Ad impression credit (${(userShareDollars * 100).toFixed(1)}¢ from $${payout.toFixed(4)} payout)`,
247-
expiresAt: null, // Ad credits don't expire
248-
operationId,
249-
logger,
250-
})
251-
252-
creditsGranted = creditsToGrant
253-
254-
logger.info(
255-
{
256-
userId,
257-
payout,
258-
creditsGranted,
259-
operationId,
260-
},
261-
'[ads] Granted ad impression credits',
262-
)
263-
264-
trackEvent({
265-
event: AnalyticsEvent.CREDIT_GRANT,
266-
userId,
267-
properties: {
268-
type: 'ad',
269-
amount: creditsGranted,
270-
payout,
271-
},
272-
logger,
273-
})
274-
} catch (error) {
275-
logger.error(
276-
{
277-
userId,
278-
payout,
279-
error:
280-
error instanceof Error
281-
? { name: error.name, message: error.message }
282-
: error,
283-
},
284-
'[ads] Failed to grant ad impression credits',
285-
)
286-
// Don't fail the request - we still want to update the impression record
287-
}
288-
}
199+
// No credits granted for ad impressions
200+
const creditsGranted = 0
289201

290202
// Update the ad_impression record with impression details (for ALL modes)
291203
try {
292204
await db
293205
.update(schema.adImpression)
294206
.set({
295207
impression_fired_at: new Date(),
296-
credits_granted: creditsGranted,
297-
grant_operation_id: creditsGranted > 0 ? operationId : null,
208+
credits_granted: 0,
209+
grant_operation_id: null,
298210
})
299211
.where(eq(schema.adImpression.id, adRecord.id))
300212

301213
logger.info(
302-
{ userId, impUrl, creditsGranted, creditsToGrant },
214+
{ userId, impUrl },
303215
'[ads] Updated ad impression record',
304216
)
305217
} catch (error) {

web/src/app/api/v1/ads/impression/route.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { processAndGrantCredit } from '@codebuff/billing/grant-credits'
21
import { trackEvent } from '@codebuff/common/analytics'
32

43
import { postAdImpression } from './_post'
@@ -15,7 +14,6 @@ export async function POST(req: NextRequest) {
1514
logger,
1615
loggerWithContext,
1716
trackEvent,
18-
processAndGrantCredit,
1917
fetch,
2018
})
2119
}

0 commit comments

Comments
 (0)