Get transcript summaries of Web videos.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123 lines
2.9 KiB

  1. import fetchPonyfill from 'fetch-ponyfill';
  2. import Handlebars from 'handlebars';
  3. import { resolve } from 'path';
  4. import { readFile } from 'fs/promises';
  5. import * as config from './config';
  6. import { OpenAiParams } from './common';
  7. export interface MakeAiCallParams {
  8. prompts: string[];
  9. openAiParams: OpenAiParams;
  10. }
  11. export class AiCallError extends Error {
  12. constructor(message: string, public readonly response: Response) {
  13. super(message);
  14. this.name = 'AiCallError';
  15. }
  16. }
  17. const makeAiCall = async (params: MakeAiCallParams): Promise<string> => {
  18. const {
  19. prompts,
  20. openAiParams: {
  21. apiKey,
  22. organizationId,
  23. model = 'gpt-3.5-turbo',
  24. temperature = 0.6,
  25. },
  26. } = params;
  27. const headers: Record<string, string> = {
  28. 'Content-Type': 'application/json',
  29. Accept: 'application/json',
  30. Authorization: `Bearer ${apiKey}`,
  31. };
  32. if (organizationId) {
  33. headers['OpenAI-Organization'] = organizationId;
  34. }
  35. const { fetch } = fetchPonyfill();
  36. const response = await fetch(
  37. new URL('/v1/chat/completions', 'https://api.openai.com'),
  38. {
  39. method: 'POST',
  40. headers,
  41. body: JSON.stringify({
  42. model,
  43. temperature,
  44. messages: [
  45. {
  46. role: 'user',
  47. content: prompts[Math.floor(Math.random() * prompts.length)].trim(),
  48. },
  49. ],
  50. }),
  51. },
  52. );
  53. if (!response.ok) {
  54. const { error } = await response.json();
  55. throw new AiCallError(`OpenAI API call failed with status ${response.status}: ${error.message}`, response);
  56. }
  57. const { choices } = await response.json();
  58. // should we use all the response choices?
  59. return choices[0].message.content;
  60. };
  61. const compilePrompts = async (filename: string, params: Record<string, unknown>): Promise<string[]> => {
  62. const rawPromptText = await readFile(resolve(config.openAi.promptsDir, filename), 'utf-8');
  63. const fill = Handlebars.compile(rawPromptText, { noEscape: true });
  64. const filledText = fill(params);
  65. return filledText.split('---').map((s) => s.trim());
  66. };
  67. export interface NormalizeTranscriptTextParams {
  68. rawTranscriptText: string,
  69. openAiParams: OpenAiParams,
  70. }
  71. export const normalizeTranscriptText = async (params: NormalizeTranscriptTextParams) => {
  72. const {
  73. rawTranscriptText,
  74. openAiParams,
  75. } = params;
  76. const prompts = await compilePrompts(
  77. 'normalize-transcript-text.hbs',
  78. {
  79. transcript: rawTranscriptText,
  80. },
  81. );
  82. return makeAiCall({
  83. prompts,
  84. openAiParams,
  85. });
  86. };
  87. export interface SummarizeTranscriptParams {
  88. normalizedTranscript: string,
  89. openAiParams: OpenAiParams,
  90. }
  91. export const summarizeTranscript = async (params: SummarizeTranscriptParams) => {
  92. const {
  93. normalizedTranscript,
  94. openAiParams,
  95. } = params;
  96. const prompts = await compilePrompts(
  97. 'summarize-transcript.hbs',
  98. {
  99. transcript: normalizedTranscript,
  100. },
  101. );
  102. return makeAiCall({
  103. prompts,
  104. openAiParams,
  105. });
  106. };