From ed4b05906223a59ffbc0be54b0da29ffed580ae1 Mon Sep 17 00:00:00 2001 From: Archer <545436317@qq.com> Date: Tue, 14 Jul 2026 20:57:07 +0800 Subject: [PATCH] feat: preserve attribution source in fastgpt sem --- packages/global/support/marketing/type.ts | 26 ++++++++++++++++++- projects/app/src/web/context/useInitApp.ts | 24 ++++++++++++++--- .../app/src/web/support/marketing/utils.ts | 14 ++++++++++ .../test/web/support/marketing/utils.test.ts | 21 +++++++++++++++ 4 files changed, 80 insertions(+), 5 deletions(-) diff --git a/packages/global/support/marketing/type.ts b/packages/global/support/marketing/type.ts index 617086e85914..540ea0ed1b49 100644 --- a/packages/global/support/marketing/type.ts +++ b/packages/global/support/marketing/type.ts @@ -7,10 +7,34 @@ export const ShortUrlSchema = z.object({ }); export type ShortUrlParams = z.infer; +export const FastGPTSourceSchema = z + .object({ + visitor_id: z.string().optional(), + first_touch_channel: z.string().optional(), + first_touch_source: z.string().optional(), + first_landing_url: z.string().optional(), + first_touch_at: z.string().optional(), + last_touch_channel: z.string().optional(), + last_touch_source: z.string().optional(), + channel_l1: z.string().optional(), + channel_l2: z.string().optional(), + is_paid: z.boolean().optional(), + utm_source: z.string().optional(), + utm_medium: z.string().optional(), + utm_campaign: z.string().optional(), + utm_term: z.string().optional(), + utm_content: z.string().optional(), + click_id: z.string().optional(), + referrer_url: z.string().optional(), + fastgpt_source: z.string().optional() + }) + .passthrough(); +export type FastGPTSourceType = z.infer; + export const FastGPT_SEM_Schema = ShortUrlSchema.extend({ keyword: z.string().optional(), search: z.string().optional(), - source: z.string().optional(), + source: z.union([z.string(), FastGPTSourceSchema]).optional(), sourceDomain: z.string().optional() }); export type FastGPTSemType = z.infer; diff --git a/projects/app/src/web/context/useInitApp.ts b/projects/app/src/web/context/useInitApp.ts index 9233534793cc..b766a97bf960 100644 --- a/projects/app/src/web/context/useInitApp.ts +++ b/projects/app/src/web/context/useInitApp.ts @@ -11,6 +11,7 @@ import { setBdVId, setFastGPTSem, initFastGPTSemSourceDomain, + parseFastGPTSource, setInviterId, setMsclkid, setUtmParams, @@ -26,6 +27,7 @@ type MarketingQueryParams = { msclkid?: string; k?: string; search?: string; + source?: string; fastgpt_source?: string; sourceDomain?: string; utm_source?: string; @@ -40,6 +42,7 @@ const MARKETING_PARAMS: (keyof MarketingQueryParams)[] = [ 'bd_vid', 'msclkid', 'k', + 'source', 'fastgpt_source', 'sourceDomain', 'utm_source', @@ -57,6 +60,7 @@ export const useInitApp = () => { msclkid, k, search, + source, fastgpt_source, sourceDomain, utm_source, @@ -157,18 +161,30 @@ export const useInitApp = () => { setUtmWorkflow(utm_workflow); initFastGPTSemSourceDomain(sourceDomain); + const sourceObject = parseFastGPTSource(source); + const sourceUtmSource = utm_source || sourceObject?.utm_source; + const sourceUtmMedium = utm_medium || sourceObject?.utm_medium; + const sourceUtmContent = utm_content || sourceObject?.utm_content; const utmParams: ShortUrlParams = { - ...(utm_source && { shortUrlSource: utm_source }), - ...(utm_medium && { shortUrlMedium: utm_medium }), - ...(utm_content && { shortUrlContent: utm_content }) + ...(sourceUtmSource && { shortUrlSource: sourceUtmSource }), + ...(sourceUtmMedium && { shortUrlMedium: sourceUtmMedium }), + ...(sourceUtmContent && { shortUrlContent: sourceUtmContent }) }; if (utm_workflow) { setUtmParams(utmParams); } + + const sourceValue = sourceObject + ? { + ...sourceObject, + ...(fastgpt_source ? { fastgpt_source } : {}) + } + : fastgpt_source; + setFastGPTSem({ keyword: k, search, - source: fastgpt_source, + source: sourceValue, ...utmParams }); diff --git a/projects/app/src/web/support/marketing/utils.ts b/projects/app/src/web/support/marketing/utils.ts index 7a671a63c943..a1a203409616 100644 --- a/projects/app/src/web/support/marketing/utils.ts +++ b/projects/app/src/web/support/marketing/utils.ts @@ -1,4 +1,5 @@ import { + FastGPTSourceSchema, type ShortUrlParams, type TrackRegisterParams } from '@fastgpt/global/support/marketing/type'; @@ -68,6 +69,19 @@ export const getFastGPTSem = () => { return undefined; } }; + +export const parseFastGPTSource = (source?: string | string[]) => { + const sourceValue = Array.isArray(source) ? source[0] : source; + if (!sourceValue) return undefined; + + try { + const result = FastGPTSourceSchema.safeParse(JSON.parse(sourceValue)); + return result.success ? result.data : undefined; + } catch { + return undefined; + } +}; + export const setFastGPTSem = (fastgptSem?: TrackRegisterParams['fastgpt_sem']) => { if (!fastgptSem) return; diff --git a/projects/app/test/web/support/marketing/utils.test.ts b/projects/app/test/web/support/marketing/utils.test.ts index 0100b20c7ad7..494970075f20 100644 --- a/projects/app/test/web/support/marketing/utils.test.ts +++ b/projects/app/test/web/support/marketing/utils.test.ts @@ -2,6 +2,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { getFastGPTSem, initFastGPTSemSourceDomain, + parseFastGPTSource, removeFastGPTSem } from '@/web/support/marketing/utils'; @@ -38,4 +39,24 @@ describe('marketing utils', () => { expect(getFastGPTSem()?.sourceDomain).toBe('https://example.com'); }); + + it('should parse the source attribution object from the URL', () => { + expect( + parseFastGPTSource( + JSON.stringify({ + visitor_id: 'visitor-1', + first_touch_source: 'ChatGPT', + is_paid: false + }) + ) + ).toMatchObject({ + visitor_id: 'visitor-1', + first_touch_source: 'ChatGPT', + is_paid: false + }); + }); + + it('should ignore an invalid source attribution object', () => { + expect(parseFastGPTSource('{invalid')).toBeUndefined(); + }); });