feat: search backend (#537)

* docs: transient docs

* chore: cleanup

* webvtt WIP

* webvtt field

* chore: webvtt tests comments

* chore: remove useless tests

* feat: search TASK.md

* feat: full text search by title/webvtt

* chore: search api task

* feat: search api

* feat: search API

* chore: rm task md

* chore: roll back unnecessary validators

* chore: pr review WIP

* chore: pr review WIP

* chore: pr review

* chore: top imports

* feat: better lint + ci

* feat: better lint + ci

* feat: better lint + ci

* feat: better lint + ci

* chore: lint

* chore: lint

* fix: db datetime definitions

* fix: flush() params

* fix: update transcript mutability expectation / test

* fix: update transcript mutability expectation / test

* chore: auto review

* chore: new controller extraction

* chore: new controller extraction

* chore: cleanup

* chore: review WIP

* chore: pr WIP

* chore: remove ci lint

* chore: openapi regeneration

* chore: openapi regeneration

* chore: postgres test doc

* fix: .dockerignore for arm binaries

* fix: .dockerignore for arm binaries

* fix: cap test loops

* fix: cap test loops

* fix: cap test loops

* fix: get_transcript_topics

* chore: remove flow.md docs and claude guidance

* chore: remove claude.md db doc

* chore: remove claude.md db doc

* chore: remove claude.md db doc

* chore: remove claude.md db doc
This commit is contained in:
Igor Loskutov
2025-08-13 10:03:38 -04:00
committed by GitHub
parent a42ed12982
commit 6fb5cb21c2
29 changed files with 3213 additions and 1493 deletions

View File

@@ -209,7 +209,6 @@ export const $GetTranscript = {
},
created_at: {
type: "string",
format: "date-time",
title: "Created At",
},
share_mode: {
@@ -395,7 +394,6 @@ export const $GetTranscriptMinimal = {
},
created_at: {
type: "string",
format: "date-time",
title: "Created At",
},
share_mode: {
@@ -758,8 +756,15 @@ export const $Page_GetTranscriptMinimal_ = {
title: "Items",
},
total: {
type: "integer",
minimum: 0,
anyOf: [
{
type: "integer",
minimum: 0,
},
{
type: "null",
},
],
title: "Total",
},
page: {
@@ -800,7 +805,7 @@ export const $Page_GetTranscriptMinimal_ = {
},
},
type: "object",
required: ["items", "total", "page", "size"],
required: ["items", "page", "size"],
title: "Page[GetTranscriptMinimal]",
} as const;
@@ -814,8 +819,15 @@ export const $Page_Room_ = {
title: "Items",
},
total: {
type: "integer",
minimum: 0,
anyOf: [
{
type: "integer",
minimum: 0,
},
{
type: "null",
},
],
title: "Total",
},
page: {
@@ -856,7 +868,7 @@ export const $Page_Room_ = {
},
},
type: "object",
required: ["items", "total", "page", "size"],
required: ["items", "page", "size"],
title: "Page[Room]",
} as const;
@@ -973,6 +985,136 @@ export const $RtcOffer = {
title: "RtcOffer",
} as const;
export const $SearchResponse = {
properties: {
results: {
items: {
$ref: "#/components/schemas/SearchResult",
},
type: "array",
title: "Results",
},
total: {
type: "integer",
minimum: 0,
title: "Total",
description: "Total number of search results",
},
query: {
type: "string",
minLength: 1,
title: "Query",
description: "Search query text",
},
limit: {
type: "integer",
maximum: 100,
minimum: 1,
title: "Limit",
description: "Results per page",
},
offset: {
type: "integer",
minimum: 0,
title: "Offset",
description: "Number of results to skip",
},
},
type: "object",
required: ["results", "total", "query", "limit", "offset"],
title: "SearchResponse",
} as const;
export const $SearchResult = {
properties: {
id: {
type: "string",
minLength: 1,
title: "Id",
},
title: {
anyOf: [
{
type: "string",
},
{
type: "null",
},
],
title: "Title",
},
user_id: {
anyOf: [
{
type: "string",
},
{
type: "null",
},
],
title: "User Id",
},
room_id: {
anyOf: [
{
type: "string",
},
{
type: "null",
},
],
title: "Room Id",
},
created_at: {
type: "string",
title: "Created At",
},
status: {
type: "string",
minLength: 1,
title: "Status",
},
rank: {
type: "number",
maximum: 1,
minimum: 0,
title: "Rank",
},
duration: {
anyOf: [
{
type: "number",
minimum: 0,
},
{
type: "null",
},
],
title: "Duration",
description: "Duration in seconds",
},
search_snippets: {
items: {
type: "string",
},
type: "array",
title: "Search Snippets",
description: "Text snippets around search matches",
},
},
type: "object",
required: [
"id",
"created_at",
"status",
"rank",
"duration",
"search_snippets",
],
title: "SearchResult",
description: "Public search result model with computed fields.",
} as const;
export const $SourceKind = {
type: "string",
enum: ["room", "live", "file"],
@@ -1397,6 +1539,7 @@ export const $WherebyWebhookEvent = {
title: "Type",
},
data: {
additionalProperties: true,
type: "object",
title: "Data",
},
@@ -1414,11 +1557,15 @@ export const $Word = {
},
start: {
type: "number",
minimum: 0,
title: "Start",
description: "Time in seconds with float part",
},
end: {
type: "number",
minimum: 0,
title: "End",
description: "Time in seconds with float part",
},
speaker: {
type: "integer",

View File

@@ -20,6 +20,8 @@ import type {
V1TranscriptsListResponse,
V1TranscriptsCreateData,
V1TranscriptsCreateResponse,
V1TranscriptsSearchData,
V1TranscriptsSearchResponse,
V1TranscriptGetData,
V1TranscriptGetResponse,
V1TranscriptUpdateData,
@@ -276,6 +278,35 @@ export class DefaultService {
});
}
/**
* Transcripts Search
* Full-text search across transcript titles and content.
* @param data The data for the request.
* @param data.q Search query text
* @param data.limit Results per page
* @param data.offset Number of results to skip
* @param data.roomId
* @returns SearchResponse Successful Response
* @throws ApiError
*/
public v1TranscriptsSearch(
data: V1TranscriptsSearchData,
): CancelablePromise<V1TranscriptsSearchResponse> {
return this.httpRequest.request({
method: "GET",
url: "/v1/transcripts/search",
query: {
q: data.q,
limit: data.limit,
offset: data.offset,
room_id: data.roomId,
},
errors: {
422: "Validation Error",
},
});
}
/**
* Transcript Get
* @param data The data for the request.

View File

@@ -141,7 +141,7 @@ export type MeetingConsentRequest = {
export type Page_GetTranscriptMinimal_ = {
items: Array<GetTranscriptMinimal>;
total: number;
total?: number | null;
page: number | null;
size: number | null;
pages?: number | null;
@@ -149,7 +149,7 @@ export type Page_GetTranscriptMinimal_ = {
export type Page_Room_ = {
items: Array<Room>;
total: number;
total?: number | null;
page: number | null;
size: number | null;
pages?: number | null;
@@ -181,6 +181,47 @@ export type RtcOffer = {
type: string;
};
export type SearchResponse = {
results: Array<SearchResult>;
/**
* Total number of search results
*/
total: number;
/**
* Search query text
*/
query: string;
/**
* Results per page
*/
limit: number;
/**
* Number of results to skip
*/
offset: number;
};
/**
* Public search result model with computed fields.
*/
export type SearchResult = {
id: string;
title?: string | null;
user_id?: string | null;
room_id?: string | null;
created_at: string;
status: string;
rank: number;
/**
* Duration in seconds
*/
duration: number | null;
/**
* Text snippets around search matches
*/
search_snippets: Array<string>;
};
export type SourceKind = "room" | "live" | "file";
export type SpeakerAssignment = {
@@ -272,7 +313,13 @@ export type WherebyWebhookEvent = {
export type Word = {
text: string;
/**
* Time in seconds with float part
*/
start: number;
/**
* Time in seconds with float part
*/
end: number;
speaker?: number;
};
@@ -346,6 +393,24 @@ export type V1TranscriptsCreateData = {
export type V1TranscriptsCreateResponse = GetTranscript;
export type V1TranscriptsSearchData = {
/**
* Results per page
*/
limit?: number;
/**
* Number of results to skip
*/
offset?: number;
/**
* Search query text
*/
q: string;
roomId?: string | null;
};
export type V1TranscriptsSearchResponse = SearchResponse;
export type V1TranscriptGetData = {
transcriptId: string;
};
@@ -633,6 +698,21 @@ export type $OpenApiTs = {
};
};
};
"/v1/transcripts/search": {
get: {
req: V1TranscriptsSearchData;
res: {
/**
* Successful Response
*/
200: SearchResponse;
/**
* Validation Error
*/
422: HTTPValidationError;
};
};
};
"/v1/transcripts/{transcript_id}": {
get: {
req: V1TranscriptGetData;

View File

@@ -23,8 +23,8 @@ export const formatTimeDifference = (seconds: number): string => {
hours > 0
? `${hours < 10 ? "\u00A0" : ""}${hours}h ago`
: minutes > 0
? `${minutes < 10 ? "\u00A0" : ""}${minutes}m ago`
: `<1m ago`;
? `${minutes < 10 ? "\u00A0" : ""}${minutes}m ago`
: `<1m ago`;
return timeString;
};