mirror of
https://github.com/sourcebot-dev/sourcebot.git
synced 2025-12-11 20:05:25 +00:00
feat
This commit is contained in:
parent
7fc068f8b2
commit
781b1b3769
15 changed files with 116 additions and 8 deletions
|
|
@ -21,7 +21,7 @@ AUTH_URL="http://localhost:3000"
|
||||||
|
|
||||||
DATA_CACHE_DIR=${PWD}/.sourcebot # Path to the sourcebot cache dir (ex. ~/sourcebot/.sourcebot)
|
DATA_CACHE_DIR=${PWD}/.sourcebot # Path to the sourcebot cache dir (ex. ~/sourcebot/.sourcebot)
|
||||||
SOURCEBOT_PUBLIC_KEY_PATH=${PWD}/public.pem
|
SOURCEBOT_PUBLIC_KEY_PATH=${PWD}/public.pem
|
||||||
# CONFIG_PATH=${PWD}/config.json # Path to the sourcebot config file (if one exists)
|
CONFIG_PATH=${PWD}/config.json # Path to the sourcebot config file (if one exists)
|
||||||
|
|
||||||
# Email
|
# Email
|
||||||
# EMAIL_FROM_ADDRESS="" # The from address for transactional emails.
|
# EMAIL_FROM_ADDRESS="" # The from address for transactional emails.
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ export const search = async (request: SearchRequest): Promise<SearchResponse | S
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Org-Domain': '~',
|
|
||||||
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
|
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
|
||||||
},
|
},
|
||||||
body: JSON.stringify(request)
|
body: JSON.stringify(request)
|
||||||
|
|
@ -26,7 +25,6 @@ export const listRepos = async (): Promise<ListRepositoriesResponse | ServiceErr
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Org-Domain': '~',
|
|
||||||
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
|
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
|
||||||
},
|
},
|
||||||
}).then(response => response.json());
|
}).then(response => response.json());
|
||||||
|
|
@ -43,7 +41,6 @@ export const getFileSource = async (request: FileSourceRequest): Promise<FileSou
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Org-Domain': '~',
|
|
||||||
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
|
...(env.SOURCEBOT_API_KEY ? { 'X-Sourcebot-Api-Key': env.SOURCEBOT_API_KEY } : {})
|
||||||
},
|
},
|
||||||
body: JSON.stringify(request)
|
body: JSON.stringify(request)
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ server.tool(
|
||||||
contextLines: env.DEFAULT_CONTEXT_LINES,
|
contextLines: env.DEFAULT_CONTEXT_LINES,
|
||||||
isRegexEnabled: true,
|
isRegexEnabled: true,
|
||||||
isCaseSensitivityEnabled: caseSensitive,
|
isCaseSensitivityEnabled: caseSensitive,
|
||||||
|
source: 'mcp'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isServiceError(response)) {
|
if (isServiceError(response)) {
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ export const searchOptionsSchema = z.object({
|
||||||
|
|
||||||
export const searchRequestSchema = z.object({
|
export const searchRequestSchema = z.object({
|
||||||
query: z.string(), // The zoekt query to execute.
|
query: z.string(), // The zoekt query to execute.
|
||||||
|
source: z.string().optional(), // The source of the search request.
|
||||||
...searchOptionsSchema.shape,
|
...searchOptionsSchema.shape,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,7 @@
|
||||||
"openai": "^4.98.0",
|
"openai": "^4.98.0",
|
||||||
"parse-diff": "^0.11.1",
|
"parse-diff": "^0.11.1",
|
||||||
"posthog-js": "^1.161.5",
|
"posthog-js": "^1.161.5",
|
||||||
|
"posthog-node": "^5.15.0",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
"psl": "^1.15.0",
|
"psl": "^1.15.0",
|
||||||
"react": "^19.2.1",
|
"react": "^19.2.1",
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ export const useSuggestionsData = ({
|
||||||
query: `file:${suggestionQuery}`,
|
query: `file:${suggestionQuery}`,
|
||||||
matches: 15,
|
matches: 15,
|
||||||
contextLines: 1,
|
contextLines: 1,
|
||||||
|
source: 'search-bar-file-suggestions'
|
||||||
}),
|
}),
|
||||||
select: (data): Suggestion[] => {
|
select: (data): Suggestion[] => {
|
||||||
if (isServiceError(data)) {
|
if (isServiceError(data)) {
|
||||||
|
|
@ -75,6 +76,7 @@ export const useSuggestionsData = ({
|
||||||
query: `sym:${suggestionQuery.length > 0 ? suggestionQuery : ".*"}`,
|
query: `sym:${suggestionQuery.length > 0 ? suggestionQuery : ".*"}`,
|
||||||
matches: 15,
|
matches: 15,
|
||||||
contextLines: 1,
|
contextLines: 1,
|
||||||
|
source: 'search-bar-symbol-suggestions'
|
||||||
}),
|
}),
|
||||||
select: (data): Suggestion[] => {
|
select: (data): Suggestion[] => {
|
||||||
if (isServiceError(data)) {
|
if (isServiceError(data)) {
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,8 @@ export const useStreamedSearch = ({ query, matches, contextLines, whole, isRegex
|
||||||
whole,
|
whole,
|
||||||
isRegexEnabled,
|
isRegexEnabled,
|
||||||
isCaseSensitivityEnabled,
|
isCaseSensitivityEnabled,
|
||||||
}),
|
source: 'sourcebot-web-client'
|
||||||
|
} satisfies SearchRequest),
|
||||||
signal: abortControllerRef.current.signal,
|
signal: abortControllerRef.current.signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { search, searchRequestSchema } from "@/features/search";
|
||||||
import { isServiceError } from "@/lib/utils";
|
import { isServiceError } from "@/lib/utils";
|
||||||
import { NextRequest } from "next/server";
|
import { NextRequest } from "next/server";
|
||||||
import { schemaValidationError, serviceErrorResponse } from "@/lib/serviceError";
|
import { schemaValidationError, serviceErrorResponse } from "@/lib/serviceError";
|
||||||
|
import { captureEvent } from "@/lib/posthog";
|
||||||
|
|
||||||
export const POST = async (request: NextRequest) => {
|
export const POST = async (request: NextRequest) => {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
|
|
@ -16,8 +17,14 @@ export const POST = async (request: NextRequest) => {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
query,
|
query,
|
||||||
|
source,
|
||||||
...options
|
...options
|
||||||
} = parsed.data;
|
} = parsed.data;
|
||||||
|
|
||||||
|
await captureEvent('api_code_search_request', {
|
||||||
|
source: source ?? 'unknown',
|
||||||
|
type: 'blocking',
|
||||||
|
});
|
||||||
|
|
||||||
const response = await search({
|
const response = await search({
|
||||||
queryType: 'string',
|
queryType: 'string',
|
||||||
|
|
@ -28,5 +35,6 @@ export const POST = async (request: NextRequest) => {
|
||||||
if (isServiceError(response)) {
|
if (isServiceError(response)) {
|
||||||
return serviceErrorResponse(response);
|
return serviceErrorResponse(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.json(response);
|
return Response.json(response);
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
import { streamSearch, searchRequestSchema } from '@/features/search';
|
import { streamSearch, searchRequestSchema } from '@/features/search';
|
||||||
|
import { captureEvent } from '@/lib/posthog';
|
||||||
import { schemaValidationError, serviceErrorResponse } from '@/lib/serviceError';
|
import { schemaValidationError, serviceErrorResponse } from '@/lib/serviceError';
|
||||||
import { isServiceError } from '@/lib/utils';
|
import { isServiceError } from '@/lib/utils';
|
||||||
import { NextRequest } from 'next/server';
|
import { NextRequest } from 'next/server';
|
||||||
|
|
@ -15,9 +16,15 @@ export const POST = async (request: NextRequest) => {
|
||||||
|
|
||||||
const {
|
const {
|
||||||
query,
|
query,
|
||||||
|
source,
|
||||||
...options
|
...options
|
||||||
} = parsed.data;
|
} = parsed.data;
|
||||||
|
|
||||||
|
await captureEvent('api_code_search_request', {
|
||||||
|
source: source ?? 'unknown',
|
||||||
|
type: 'streamed',
|
||||||
|
});
|
||||||
|
|
||||||
const stream = await streamSearch({
|
const stream = await streamSearch({
|
||||||
queryType: 'string',
|
queryType: 'string',
|
||||||
query,
|
query,
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ export const useSuggestionsData = ({
|
||||||
query: query.join(' '),
|
query: query.join(' '),
|
||||||
matches: 10,
|
matches: 10,
|
||||||
contextLines: 1,
|
contextLines: 1,
|
||||||
|
source: 'chat-file-suggestions'
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
select: (data): FileSuggestion[] => {
|
select: (data): FileSuggestion[] => {
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,12 @@ import { sew } from "@/actions";
|
||||||
import { getRepoPermissionFilterForUser } from "@/prisma";
|
import { getRepoPermissionFilterForUser } from "@/prisma";
|
||||||
import { withOptionalAuthV2 } from "@/withAuthV2";
|
import { withOptionalAuthV2 } from "@/withAuthV2";
|
||||||
import { PrismaClient, UserWithAccounts } from "@sourcebot/db";
|
import { PrismaClient, UserWithAccounts } from "@sourcebot/db";
|
||||||
import { createLogger, env, hasEntitlement } from "@sourcebot/shared";
|
import { env, hasEntitlement } from "@sourcebot/shared";
|
||||||
import { QueryIR } from './ir';
|
import { QueryIR } from './ir';
|
||||||
import { parseQuerySyntaxIntoIR } from './parser';
|
import { parseQuerySyntaxIntoIR } from './parser';
|
||||||
import { SearchOptions } from "./types";
|
import { SearchOptions } from "./types";
|
||||||
import { createZoektSearchRequest, zoektSearch, zoektStreamSearch } from './zoektSearcher';
|
import { createZoektSearchRequest, zoektSearch, zoektStreamSearch } from './zoektSearcher';
|
||||||
|
|
||||||
const logger = createLogger("searchApi");
|
|
||||||
|
|
||||||
type QueryStringSearchRequest = {
|
type QueryStringSearchRequest = {
|
||||||
queryType: 'string';
|
queryType: 'string';
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,7 @@ export type SearchOptions = z.infer<typeof searchOptionsSchema>;
|
||||||
|
|
||||||
export const searchRequestSchema = z.object({
|
export const searchRequestSchema = z.object({
|
||||||
query: z.string(), // The zoekt query to execute.
|
query: z.string(), // The zoekt query to execute.
|
||||||
|
source: z.string().optional(), // The source of the search request.
|
||||||
...searchOptionsSchema.shape,
|
...searchOptionsSchema.shape,
|
||||||
});
|
});
|
||||||
export type SearchRequest = z.infer<typeof searchRequestSchema>;
|
export type SearchRequest = z.infer<typeof searchRequestSchema>;
|
||||||
|
|
|
||||||
65
packages/web/src/lib/posthog.ts
Normal file
65
packages/web/src/lib/posthog.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import { PostHog } from 'posthog-node'
|
||||||
|
import { env } from '@sourcebot/shared'
|
||||||
|
import { RequestCookies } from 'next/dist/compiled/@edge-runtime/cookies';
|
||||||
|
import * as Sentry from "@sentry/nextjs";
|
||||||
|
import { PosthogEvent, PosthogEventMap } from './posthogEvents';
|
||||||
|
import { cookies } from 'next/headers';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @note: This is a subset of the properties stored in the
|
||||||
|
* ph_phc_<id>_posthog cookie.
|
||||||
|
*/
|
||||||
|
export type PostHogCookie = {
|
||||||
|
distinct_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPostHogCookie = (cookie: unknown): cookie is PostHogCookie => {
|
||||||
|
return typeof cookie === 'object' &&
|
||||||
|
cookie !== null &&
|
||||||
|
'distinct_id' in cookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to retrieve the PostHog cookie from the given cookie store, returning
|
||||||
|
* undefined if the cookie is not found or is invalid.
|
||||||
|
*/
|
||||||
|
const getPostHogCookie = (cookieStore: Pick<RequestCookies, 'get'>): PostHogCookie | undefined => {
|
||||||
|
const phCookieKey = `ph_${env.POSTHOG_PAPIK}_posthog`;
|
||||||
|
const cookie = cookieStore.get(phCookieKey);
|
||||||
|
|
||||||
|
if (!cookie) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedCookie = (() => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(cookie.value);
|
||||||
|
} catch (e) {
|
||||||
|
Sentry.captureException(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (isPostHogCookie(parsedCookie)) {
|
||||||
|
return parsedCookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function captureEvent<E extends PosthogEvent>(event: E, properties: PosthogEventMap[E]) {
|
||||||
|
const cookieStore = await cookies();
|
||||||
|
const cookie = getPostHogCookie(cookieStore);
|
||||||
|
|
||||||
|
const posthog = new PostHog(env.POSTHOG_PAPIK, {
|
||||||
|
host: 'https://us.i.posthog.com',
|
||||||
|
flushAt: 1,
|
||||||
|
flushInterval: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
posthog.capture({
|
||||||
|
event,
|
||||||
|
properties,
|
||||||
|
distinctId: cookie?.distinct_id ?? '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -313,6 +313,11 @@ export type PosthogEventMap = {
|
||||||
durationMs: number,
|
durationMs: number,
|
||||||
// Whether or not the user is searching all repositories.
|
// Whether or not the user is searching all repositories.
|
||||||
isGlobalSearchEnabled: boolean,
|
isGlobalSearchEnabled: boolean,
|
||||||
}
|
},
|
||||||
|
//////////////////////////////////////////////////////////////////
|
||||||
|
api_code_search_request: {
|
||||||
|
source: string;
|
||||||
|
type: 'streamed' | 'blocking';
|
||||||
|
},
|
||||||
}
|
}
|
||||||
export type PosthogEvent = keyof PosthogEventMap;
|
export type PosthogEvent = keyof PosthogEventMap;
|
||||||
19
yarn.lock
19
yarn.lock
|
|
@ -4599,6 +4599,15 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@posthog/core@npm:1.6.0":
|
||||||
|
version: 1.6.0
|
||||||
|
resolution: "@posthog/core@npm:1.6.0"
|
||||||
|
dependencies:
|
||||||
|
cross-spawn: "npm:^7.0.6"
|
||||||
|
checksum: 10c0/28aa907bb21b18587bc5f47c44349ebc834b37d9a4cedb1a18d7b673d4d7cdad2120dda80deceaee707b2f52333e9a08a8e591e1fc4066521ce05e820b76309f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@prisma/client@npm:6.2.1":
|
"@prisma/client@npm:6.2.1":
|
||||||
version: 6.2.1
|
version: 6.2.1
|
||||||
resolution: "@prisma/client@npm:6.2.1"
|
resolution: "@prisma/client@npm:6.2.1"
|
||||||
|
|
@ -8245,6 +8254,7 @@ __metadata:
|
||||||
parse-diff: "npm:^0.11.1"
|
parse-diff: "npm:^0.11.1"
|
||||||
postcss: "npm:^8"
|
postcss: "npm:^8"
|
||||||
posthog-js: "npm:^1.161.5"
|
posthog-js: "npm:^1.161.5"
|
||||||
|
posthog-node: "npm:^5.15.0"
|
||||||
pretty-bytes: "npm:^6.1.1"
|
pretty-bytes: "npm:^6.1.1"
|
||||||
psl: "npm:^1.15.0"
|
psl: "npm:^1.15.0"
|
||||||
react: "npm:^19.2.1"
|
react: "npm:^19.2.1"
|
||||||
|
|
@ -17235,6 +17245,15 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"posthog-node@npm:^5.15.0":
|
||||||
|
version: 5.15.0
|
||||||
|
resolution: "posthog-node@npm:5.15.0"
|
||||||
|
dependencies:
|
||||||
|
"@posthog/core": "npm:1.6.0"
|
||||||
|
checksum: 10c0/7db929453cefc9b2d0017e1f7c9ffe7e6ecd2a495ee9861bd5e8f3873f72fa29a318dd7bccf10eb15639e1860aab396d4be502af88afba96ed15ac8b46d57e9d
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"preact-render-to-string@npm:6.5.11":
|
"preact-render-to-string@npm:6.5.11":
|
||||||
version: 6.5.11
|
version: 6.5.11
|
||||||
resolution: "preact-render-to-string@npm:6.5.11"
|
resolution: "preact-render-to-string@npm:6.5.11"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue