Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 61 additions & 31 deletions components/grain/actions/get-recording/get-recording.mjs
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import {
INTELLIGENCE_NOTES_FORMAT_OPTIONS,
TRANSCRIPT_FORMAT_OPTIONS,
} from "../../common/constants.mjs";
import { parseObject } from "../../common/utils.mjs";
import grain from "../../grain.app.mjs";

export default {
key: "grain-get-recording",
name: "Get Recording",
description: "Fetches a specific recording by its ID from Grain, optionally including the transcript and intelligence notes. [See the documentation](https://grainhq.notion.site/grain-public-api-877184aa82b54c77a875083c1b560de9)",
version: "0.0.2",
description: "Fetches a specific recording by its ID from Grain, returning its metadata (title, times, URL, tags, teams, meeting type)."
+ " Enable the optional include props to add highlights, participants, AI action items, AI summary, calendar event, HubSpot data, or screenshares to the response."
+ " Use **List Recordings** to find recording IDs, and **Get Transcript** to fetch the full transcript."
+ " [See the documentation](https://developers.grain.com)",
version: "1.0.0",
annotations: {
destructiveHint: false,
openWorldHint: true,
Expand All @@ -18,45 +16,77 @@ export default {
type: "action",
props: {
grain,
recordId: {
recordingId: {
propDefinition: [
grain,
"recordId",
"recordingId",
],
},
transcriptFormat: {
type: "string",
label: "Transcript Format",
description: "Format for the transcript",
options: TRANSCRIPT_FORMAT_OPTIONS,
optional: true,
highlights: {
propDefinition: [
grain,
"highlights",
],
},
intelligenceNotesFormat: {
type: "string",
label: "Intelligence Notes Format",
description: "Format for the intelligence notes",
options: INTELLIGENCE_NOTES_FORMAT_OPTIONS,
optional: true,
participants: {
propDefinition: [
grain,
"participants",
],
},
aiActionItems: {
propDefinition: [
grain,
"aiActionItems",
],
},
allowedIntelligenceNotes: {
type: "string[]",
label: "Allowed Intelligence Notes",
description: "Whitelist of intelligence notes section titles",
aiSummary: {
propDefinition: [
grain,
"aiSummary",
],
},
calendarEvent: {
propDefinition: [
grain,
"calendarEvent",
],
},
hubspot: {
propDefinition: [
grain,
"hubspot",
],
},
screenshares: {
type: "boolean",
label: "Include Screenshares",
description: "Include the recording's screenshare ranges in the response",
optional: true,
},
},
async run({ $ }) {
const include = {
highlights: this.highlights,
participants: this.participants,
ai_action_items: this.aiActionItems,
ai_summary: this.aiSummary,
calendar_event: this.calendarEvent,
hubspot: this.hubspot,
screenshares: this.screenshares,
};

const response = await this.grain.fetchRecording({
$,
recordId: this.recordId,
params: {
transcript_format: this.transcriptFormat,
intelligence_notes_format: this.intelligenceNotesFormat,
allowed_intelligence_notes: parseObject(this.allowedIntelligenceNotes),
recordingId: this.recordingId,
data: {
include: Object.fromEntries(Object.entries(include).filter(([
, value,
]) => value)),
},
});

$.export("$summary", `Successfully fetched recording with ID ${this.recordId}`);
$.export("$summary", `Successfully fetched recording with ID ${this.recordingId}`);
return response;
},
};
45 changes: 45 additions & 0 deletions components/grain/actions/get-transcript/get-transcript.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { TRANSCRIPT_FORMAT_OPTIONS } from "../../common/constants.mjs";
import grain from "../../grain.app.mjs";

export default {
key: "grain-get-transcript",
name: "Get Transcript",
description: "Fetches the full transcript of a Grain recording."
+ " The `json` format returns structured segments with speaker, participant ID, start/end times in milliseconds, and text;"
+ " `txt`, `vtt`, and `srt` return plain text or subtitle formats."
+ " Use **List Recordings** to find recording IDs; use **Get Recording** for the recording's metadata instead of its transcript."
+ " [See the documentation](https://developers.grain.com)",
version: "0.0.1",
annotations: {
destructiveHint: false,
openWorldHint: true,
readOnlyHint: true,
},
type: "action",
props: {
grain,
recordingId: {
propDefinition: [
grain,
"recordingId",
],
},
format: {
type: "string",
label: "Format",
description: "Format for the transcript",
options: TRANSCRIPT_FORMAT_OPTIONS,
default: "json",
},
},
async run({ $ }) {
const response = await this.grain.fetchTranscript({
$,
recordingId: this.recordingId,
format: this.format,
});

$.export("$summary", `Successfully fetched transcript for recording ${this.recordingId}`);
return response;
},
};
91 changes: 91 additions & 0 deletions components/grain/actions/list-recordings/list-recordings.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import grain from "../../grain.app.mjs";

export default {
key: "grain-list-recordings",
name: "List Recordings",
description: "Lists Grain recordings, optionally filtered by start datetime range (ISO8601), title search, or participant scope."
+ " Automatically paginates and returns up to Max Results recordings."
+ " Use this to find recording IDs for **Get Recording** and **Get Transcript**."
+ " [See the documentation](https://developers.grain.com)",
version: "0.0.1",
annotations: {
destructiveHint: false,
openWorldHint: true,
readOnlyHint: true,
},
type: "action",
props: {
grain,
beforeDatetime: {
type: "string",
label: "Before Datetime",
description: "Only return recordings that started before this ISO8601 datetime. E.g. `2025-01-01T00:00:00Z`",
optional: true,
},
afterDatetime: {
type: "string",
label: "After Datetime",
description: "Only return recordings that started after this ISO8601 datetime. E.g. `2025-01-01T00:00:00Z`",
optional: true,
},
titleSearch: {
type: "string",
label: "Title Search",
description: "Only return recordings whose title matches this search string",
optional: true,
},
participantScope: {
type: "string",
label: "Participant Scope",
description: "Only return recordings whose participants are all internal, or that include external participants",
options: [
"internal",
"external",
],
optional: true,
},
maxResults: {
type: "integer",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
label: "Max Results",
description: "Maximum number of recordings to return. Must be a positive integer.",
optional: true,
default: 100,
min: 1,
},
},
async run({ $ }) {
const filter = {
before_datetime: this.beforeDatetime,
after_datetime: this.afterDatetime,
title_search: this.titleSearch,
participant_scope: this.participantScope,
};

const recordings = [];
let cursor;
do {
const {
recordings: page, cursor: nextCursor,
} = await this.grain.listRecordings({
$,
data: {
cursor,
filter: Object.fromEntries(Object.entries(filter).filter(([
, value,
]) => value !== undefined)),
},
});
recordings.push(...page);
cursor = nextCursor;
} while (cursor && recordings.length < this.maxResults);

if (recordings.length > this.maxResults) {
recordings.length = this.maxResults;
}

$.export("$summary", `Successfully fetched ${recordings.length} recording${recordings.length === 1
? ""
: "s"}`);
return recordings;
},
};
19 changes: 6 additions & 13 deletions components/grain/common/constants.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,15 @@ export const TRANSCRIPT_FORMAT_OPTIONS = [
value: "json",
},
{
label: "VTT",
value: "vtt",
},
];

export const INTELLIGENCE_NOTES_FORMAT_OPTIONS = [
{
label: "JSON",
value: "json",
label: "Text",
value: "txt",
},
{
label: "Markdown",
value: "md",
label: "VTT",
value: "vtt",
},
{
label: "Text",
value: "text",
label: "SRT",
value: "srt",
},
];
24 changes: 0 additions & 24 deletions components/grain/common/utils.mjs

This file was deleted.

Loading
Loading