Skip to content

Commit 6147be1

Browse files
committed
feat: persist no-ai feed preference
Store the No AI preference in user settings and apply it in the v2 feed path without persisting large blocked filter lists. Made-with: Cursor
1 parent 1b330b9 commit 6147be1

7 files changed

Lines changed: 120 additions & 3 deletions

File tree

__tests__/__snapshots__/settings.ts.snap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Object {
1111
"flags": Object {
1212
"clickbaitShieldEnabled": null,
1313
"defaultWriteTab": null,
14+
"noAiFeedEnabled": null,
1415
"sidebarBookmarksExpanded": null,
1516
"sidebarCustomFeedsExpanded": null,
1617
"sidebarOtherExpanded": null,
@@ -48,6 +49,7 @@ Object {
4849
"flags": Object {
4950
"clickbaitShieldEnabled": null,
5051
"defaultWriteTab": null,
52+
"noAiFeedEnabled": null,
5153
"sidebarBookmarksExpanded": null,
5254
"sidebarCustomFeedsExpanded": null,
5355
"sidebarOtherExpanded": null,
@@ -87,6 +89,7 @@ Object {
8789
"flags": Object {
8890
"clickbaitShieldEnabled": null,
8991
"defaultWriteTab": null,
92+
"noAiFeedEnabled": null,
9093
"sidebarBookmarksExpanded": null,
9194
"sidebarCustomFeedsExpanded": null,
9295
"sidebarOtherExpanded": null,
@@ -126,6 +129,7 @@ Object {
126129
"flags": Object {
127130
"clickbaitShieldEnabled": null,
128131
"defaultWriteTab": null,
132+
"noAiFeedEnabled": null,
129133
"sidebarBookmarksExpanded": null,
130134
"sidebarCustomFeedsExpanded": null,
131135
"sidebarOtherExpanded": null,

__tests__/feeds.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
PostTag,
2121
PostType,
2222
SharePost,
23+
Settings,
2324
Source,
2425
SourceMember,
2526
SourceType,
@@ -728,6 +729,52 @@ describe('query feed', () => {
728729
expect(res.data.feed.edges.length).toEqual(2);
729730
});
730731

732+
it('should include no-ai blocked tags and title words when the saved setting is enabled', async () => {
733+
loggedUser = '1';
734+
await saveFeedFixtures();
735+
await saveFixtures(con, Settings, [
736+
{
737+
userId: '1',
738+
flags: {
739+
noAiFeedEnabled: true,
740+
},
741+
},
742+
]);
743+
nock('http://localhost:6002')
744+
.post('/config')
745+
.reply(200, {
746+
user_id: '1',
747+
config: {
748+
providers: {},
749+
},
750+
});
751+
nock('http://localhost:6000')
752+
.post('/feed.json', (body) => {
753+
expect(body.blocked_tags).toEqual(
754+
expect.arrayContaining(['golang', 'ai', 'openai']),
755+
);
756+
expect(body.blocked_title_words).toEqual(
757+
expect.arrayContaining(['Claude', 'Elon Musk']),
758+
);
759+
760+
return true;
761+
})
762+
.reply(200, {
763+
data: [{ post_id: 'p1' }, { post_id: 'p4' }],
764+
cursor: 'b',
765+
});
766+
767+
const res = await client.query(QUERY, {
768+
variables: {
769+
...variables,
770+
version: 20,
771+
},
772+
});
773+
774+
expect(res.errors).toBeFalsy();
775+
expect(res.data.feed.edges.length).toEqual(2);
776+
});
777+
731778
describe('youtube content', () => {
732779
beforeEach(async () => {
733780
await saveFixtures(con, YouTubePost, videoPostsFixture);

__tests__/settings.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ describe('mutation updateUserSettings', () => {
121121
sidebarSquadExpanded
122122
sidebarBookmarksExpanded
123123
clickbaitShieldEnabled
124+
noAiFeedEnabled
124125
defaultWriteTab
125126
}
126127
}
@@ -157,6 +158,7 @@ describe('mutation updateUserSettings', () => {
157158
sidebarSquadExpanded: null,
158159
sidebarBookmarksExpanded: null,
159160
clickbaitShieldEnabled: null,
161+
noAiFeedEnabled: null,
160162
defaultWriteTab: null,
161163
});
162164
});
@@ -176,6 +178,7 @@ describe('mutation updateUserSettings', () => {
176178
sidebarSquadExpanded: null,
177179
sidebarBookmarksExpanded: null,
178180
clickbaitShieldEnabled: null,
181+
noAiFeedEnabled: null,
179182
defaultWriteTab: null,
180183
});
181184

@@ -312,6 +315,7 @@ describe('mutation updateUserSettings', () => {
312315
sidebarSquadExpanded: null,
313316
sidebarBookmarksExpanded: null,
314317
clickbaitShieldEnabled: null,
318+
noAiFeedEnabled: null,
315319
defaultWriteTab: null,
316320
});
317321
});
@@ -347,10 +351,51 @@ describe('mutation updateUserSettings', () => {
347351
sidebarSquadExpanded: null,
348352
sidebarBookmarksExpanded: null,
349353
clickbaitShieldEnabled: null,
354+
noAiFeedEnabled: null,
350355
defaultWriteTab: null,
351356
});
352357
});
353358

359+
it('should update the no ai feed flag', async () => {
360+
loggedUser = '1';
361+
362+
const repo = con.getRepository(Settings);
363+
await repo.save(
364+
repo.create({
365+
userId: '1',
366+
flags: {
367+
noAiFeedEnabled: false,
368+
},
369+
}),
370+
);
371+
372+
const res = await client.mutate(MUTATION, {
373+
variables: {
374+
data: {
375+
flags: {
376+
noAiFeedEnabled: true,
377+
},
378+
},
379+
},
380+
});
381+
382+
expect(res.data.updateUserSettings.flags).toEqual({
383+
sidebarCustomFeedsExpanded: null,
384+
sidebarOtherExpanded: null,
385+
sidebarResourcesExpanded: null,
386+
sidebarSquadExpanded: null,
387+
sidebarBookmarksExpanded: null,
388+
clickbaitShieldEnabled: null,
389+
noAiFeedEnabled: true,
390+
defaultWriteTab: null,
391+
});
392+
393+
const updated = await repo.findOneByOrFail({ userId: '1' });
394+
expect(updated.flags).toEqual({
395+
noAiFeedEnabled: true,
396+
});
397+
});
398+
354399
it('should update opt out companion settings', async () => {
355400
loggedUser = '1';
356401

src/common/flags.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const transformSettingFlags = ({ flags }: Pick<Settings, 'flags'>) => {
88
sidebarResourcesExpanded: flags?.sidebarResourcesExpanded ?? true,
99
sidebarBookmarksExpanded: flags?.sidebarBookmarksExpanded ?? true,
1010
clickbaitShieldEnabled: flags?.clickbaitShieldEnabled ?? true,
11+
noAiFeedEnabled: flags?.noAiFeedEnabled ?? false,
1112
...flags,
1213
};
1314
};

src/entity/Settings.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export type SettingsFlags = Partial<{
3333
sidebarResourcesExpanded: boolean;
3434
sidebarBookmarksExpanded: boolean;
3535
clickbaitShieldEnabled: boolean;
36+
noAiFeedEnabled: boolean;
3637
prompt: object;
3738
timezoneMismatchIgnore: string;
3839
lastPrompt: string;
@@ -47,6 +48,7 @@ export type SettingsFlagsPublic = Pick<
4748
| 'sidebarResourcesExpanded'
4849
| 'sidebarBookmarksExpanded'
4950
| 'clickbaitShieldEnabled'
51+
| 'noAiFeedEnabled'
5052
| 'prompt'
5153
| 'timezoneMismatchIgnore'
5254
| 'lastPrompt'
@@ -163,6 +165,7 @@ export const SETTINGS_DEFAULT = {
163165
sidebarResourcesExpanded: true,
164166
sidebarBookmarksExpanded: true,
165167
clickbaitShieldEnabled: true,
168+
noAiFeedEnabled: false,
166169
defaultWriteTab: DefaultWriteTab.NewPost,
167170
},
168171
};

src/schema/feeds.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
FeedType,
1010
Post,
1111
PostType,
12+
Settings,
1213
Source,
1314
UserPost,
1415
} from '../entity';
@@ -1392,6 +1393,14 @@ const wrapGeneratorWithNoAi = (generator: FeedGenerator): FeedGenerator =>
13921393
},
13931394
}));
13941395

1396+
const isSavedNoAiEnabled = async (ctx: AuthContext): Promise<boolean> => {
1397+
const settings = await ctx.con.getRepository(Settings).findOneBy({
1398+
userId: ctx.userId,
1399+
});
1400+
1401+
return settings?.flags?.noAiFeedEnabled ?? false;
1402+
};
1403+
13951404
const feedResolverV1: IFieldResolver<unknown, Context, ConfiguredFeedArgs> =
13961405
feedResolver(
13971406
(
@@ -1591,28 +1600,34 @@ export const resolvers: IResolvers<unknown, BaseContext> = {
15911600
}
15921601
return anonymousFeedResolverV1(source, args, ctx, info);
15931602
},
1594-
feed: (source, args: ConfiguredFeedArgs, ctx: Context, info) => {
1603+
feed: async (source, args: ConfiguredFeedArgs, ctx: AuthContext, info) => {
15951604
if (args.version >= 2 && args.ranking === Ranking.POPULARITY) {
15961605
const generator = versionToFeedGenerator(args.version);
1606+
const shouldApplyNoAi = args.noAi || (await isSavedNoAiEnabled(ctx));
15971607

15981608
return feedResolverCursor(
15991609
source,
16001610
{
16011611
...(args as FeedArgs),
1602-
generator: args.noAi ? wrapGeneratorWithNoAi(generator) : generator,
1612+
generator: shouldApplyNoAi
1613+
? wrapGeneratorWithNoAi(generator)
1614+
: generator,
16031615
},
16041616
ctx,
16051617
info,
16061618
);
16071619
}
16081620
if (args.version >= 2 && args.ranking === Ranking.TIME) {
16091621
const generator = versionToTimeFeedGenerator(args.version);
1622+
const shouldApplyNoAi = args.noAi || (await isSavedNoAiEnabled(ctx));
16101623

16111624
return feedResolverCursor(
16121625
source,
16131626
{
16141627
...(args as FeedArgs),
1615-
generator: args.noAi ? wrapGeneratorWithNoAi(generator) : generator,
1628+
generator: shouldApplyNoAi
1629+
? wrapGeneratorWithNoAi(generator)
1630+
: generator,
16161631
},
16171632
ctx,
16181633
info,

src/schema/settings.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export const typeDefs = /* GraphQL */ `
7272
sidebarResourcesExpanded: Boolean
7373
sidebarBookmarksExpanded: Boolean
7474
clickbaitShieldEnabled: Boolean
75+
noAiFeedEnabled: Boolean
7576
timezoneMismatchIgnore: String
7677
lastPrompt: String
7778
defaultWriteTab: DefaultWriteTab
@@ -84,6 +85,7 @@ export const typeDefs = /* GraphQL */ `
8485
sidebarResourcesExpanded: Boolean
8586
sidebarBookmarksExpanded: Boolean
8687
clickbaitShieldEnabled: Boolean
88+
noAiFeedEnabled: Boolean
8789
prompt: JSONObject
8890
timezoneMismatchIgnore: String
8991
lastPrompt: String

0 commit comments

Comments
 (0)