Skip to content
Open
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
52 changes: 52 additions & 0 deletions components/speak_ai/actions/find-media/find-media.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import app from "../../speak_ai.app.mjs";

export default {
key: "speak_ai-find-media",
name: "Find Media",
description: "Find an existing Speak AI media item by its Media ID. [See the documentation](https://docs.speakai.co/).",
version: "0.0.1",
annotations: {
destructiveHint: false,
openWorldHint: true,
readOnlyHint: true,
},
type: "action",
props: {
app,
folderId: {
propDefinition: [
app,
"folderId",
],
},
mediaId: {
propDefinition: [
app,
"mediaId",
({ folderId }) => ({
folderId,
}),
],
},
},
async run({ $ }) {
const {
app,
mediaId,
} = this;

const results = await app.getInsights({
$,
params: {
mediaId,
pageSize: 1,
},
});
const media = app.firstResult(results);

$.export("$summary", media
? `Found media \`${mediaId}\`.`
: `No media found for \`${mediaId}\`.`);
return media;
},
};
75 changes: 75 additions & 0 deletions components/speak_ai/actions/run-ai-chat/run-ai-chat.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import app from "../../speak_ai.app.mjs";

export default {
key: "speak_ai-run-ai-chat",
name: "Run AI Chat",
description: "Run a Speak AI Chat against a folder and/or specific media. [See the documentation](https://docs.speakai.co/).",
version: "0.0.1",
annotations: {
destructiveHint: false,
openWorldHint: true,
readOnlyHint: false,
},
type: "action",
props: {
app,
prompt: {
propDefinition: [
app,
"prompt",
],
},
folderId: {
propDefinition: [
app,
"folderId",
],
},
assistantType: {
type: "string",
label: "Assistant Type",
description: "The assistant type to use for the prompt (e.g. `researcher`, `marketer`, `sales`, `general`, `recruiter`)",
optional: true,
default: "general",
},
assistantTemplateId: {
type: "string",
label: "Assistant Template ID",
description: "Optional custom assistant template to apply",
optional: true,
},
mediaIds: {
propDefinition: [
app,
"mediaIds",
],
},
},
async run({ $ }) {
const {
app,
prompt,
folderId,
assistantType,
assistantTemplateId,
mediaIds,
} = this;

const response = await app.runPrompt({
$,
data: {
folderId,
prompt,
assistantType,
assistantTemplateId,
mediaIds: mediaIds || [],
},
});

const promptId = response?.data?.promptId;
$.export("$summary", promptId
? `Successfully ran AI Chat \`${promptId}\`.`
: "Successfully ran AI Chat.");
return response;
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
};
2 changes: 1 addition & 1 deletion components/speak_ai/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@pipedream/speak_ai",
"version": "0.2.0",
"version": "0.3.0",
"description": "Pipedream Speak AI Components",
"main": "speak_ai.app.mjs",
"keywords": [
Expand Down
65 changes: 65 additions & 0 deletions components/speak_ai/sources/common/base.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { ConfigurationError } from "@pipedream/platform";
import app from "../../speak_ai.app.mjs";
import constants from "../../common/constants.mjs";

export default {
props: {
app,
db: "$.service.db",
http: "$.interface.http",
},
hooks: {
async activate() {
const { data } = await this.app.subscribeWebhook({
data: {
callbackUrl: this.http.endpoint,
events: this.getEvents(),
},
});
this.setWebhookId(data.webhookId);
},
async deactivate() {
const webhookId = this.getWebhookId();
if (webhookId) {
await this.app.unsubscribeWebhook({
webhookId,
});
}
},
},
methods: {
setWebhookId(value) {
this.db.set(constants.WEBHOOK_ID, value);
},
getWebhookId() {
return this.db.get(constants.WEBHOOK_ID);
},
getEvents() {
throw new ConfigurationError("getEvents is not implemented");
},
getSummary(resource) {
return `New Speak AI event: ${resource.eventType || "delivery"}`;
},
async hydrate(resource) {
return resource;
},
generateMeta(resource, data) {
return {
id: resource.deliveryId || resource.mediaId || resource.messageId || resource.promptId,
summary: this.getSummary(resource, data),
ts: Date.parse(resource.createdAt) || Date.now(),
};
},
Comment on lines +46 to +52

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Speak AI webhook payload documentation event fields timestamp deliveryId

💡 Result:

Speak AI provides webhook functionality to support event-driven architectures, specifically for notifying your application when tasks such as transcription and analysis are complete [1][2]. To integrate with Speak AI webhooks, you must register your endpoint using the Speak AI REST API (specifically POST /v1/webhook) [3]. While Speak AI's developer portal at docs.speakai.co serves as the primary authoritative source for specific payload schemas [4][2], developers typically implement these webhooks to handle events like media.analyzed [3]. General industry practice for webhook payloads—and features often found in such integrations—include the following fields: 1. Event Type: A string identifying the specific event that triggered the webhook (e.g., media.analyzed) [3]. 2. Timestamp: A field indicating when the event occurred, often formatted as an ISO 8601 string [5][6]. 3. DeliveryId: A unique identifier for the specific webhook delivery attempt, which is commonly used to ensure idempotency and handle retries [5][6]. For the most accurate and up-to-date schema definitions, including exact field names and nested data structures for specific events, refer directly to the Speak AI documentation at https://docs.speakai.co [4][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the shared base and the Speak AI sources that extend it
git ls-files 'components/speak_ai/**' | sed -n '1,200p'

echo '--- base outline ---'
ast-grep outline components/speak_ai/sources/common/base.mjs --view expanded

echo '--- source outlines ---'
for f in $(git ls-files 'components/speak_ai/sources/**/*.mjs'); do
  echo "### $f"
  ast-grep outline "$f" --view expanded
done

Repository: PipedreamHQ/pipedream

Length of output: 4199


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the shared base and all Speak AI source files for generateMeta usage and timestamp/id fields
for f in components/speak_ai/sources/common/base.mjs $(git ls-files 'components/speak_ai/sources/**/*.mjs' | grep -v '^components/speak_ai/sources/common/base.mjs$'); do
  echo "===== $f ====="
  wc -l "$f"
  rg -n "generateMeta|getSummary|getEvents|dedupe|emit|timestamp|createdAt|deliveryId|mediaId|messageId|promptId|eventTime|Date\.now" "$f" -n -C 3 || true
  echo
done

Repository: PipedreamHQ/pipedream

Length of output: 13438


🌐 Web query:

Speak AI webhook payload messageId promptId deliveryId mediaId timestamp event schema

💡 Result:

Speak AI webhooks are designed to notify external systems of specific events occurring within the Speak AI platform, most notably the media.analyzed event [1][2]. When a configured event is triggered, Speak AI sends an HTTP POST request containing a JSON payload to your specified callback URL [1]. While the exact structure of the payload can depend on the event type, standard integrations typically utilize the mediaId to retrieve comprehensive details about the analysis through the Speak AI REST API [2]. Core components frequently involved in Speak AI webhook and data integration workflows include: mediaId: A unique identifier for the processed media file, used to fetch full insight data, transcriptions, and sentiment analysis results via the API [2][3][4]. event: Specifies the type of occurrence, such as media.analyzed, which indicates that transcription and AI processing are complete [1][2]. timestamp: Included in the event data to provide the exact time the event occurred or was processed [1]. To implement a robust webhook handler, you should verify the requests using your Speak AI webhook secret to ensure they originate from the platform [5]. Because webhooks often serve as notifications, the recommended pattern for accessing full data is to use the mediaId provided in the webhook payload to make a secondary authenticated request to the Speak AI API [2]. This ensures you receive the complete, structured analysis rather than relying solely on the data contained in the initial webhook event [2][3]. For detailed API documentation regarding endpoints and specific response schemas, developers should refer to the official Speak AI documentation [6][7]. Webhooks can be registered via the API by providing your callback URL and the list of events you wish to subscribe to [2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search the Speak AI app for event-field assumptions and any mention of timestamp/id shapes
rg -n "promptId|messageId|mediaId|deliveryId|eventTime|createdAt|timestamp|generateMeta|getSummary|dedupe" components/speak_ai .github/pipedream-*.md -C 2

Repository: PipedreamHQ/pipedream

Length of output: 36344


🌐 Web query:

site:docs.speakai.co Speak AI webhook timestamp promptId messageId deliveryId

💡 Result:

The available documentation for Speak AI does not explicitly define the specific payload fields promptId, messageId, or deliveryId in relation to their webhook system [1]. While Speak AI supports webhooks to send data to your platform, the official documentation does not provide a standard schema or example payload containing these specific identifiers [1]. If you are implementing webhooks with Speak AI, you should refer directly to the webhook configuration settings within your Speak AI dashboard or the internal API logs to inspect the actual JSON structure being delivered to your endpoint.

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the shared base and source headers to see which sources inherit generateMeta from base vs webhook
sed -n '1,120p' components/speak_ai/sources/common/base.mjs
echo '---'
sed -n '1,120p' components/speak_ai/sources/common/webhook.mjs
echo '---'
for f in \
  components/speak_ai/sources/new-captions/new-captions.mjs \
  components/speak_ai/sources/new-magic-prompt/new-magic-prompt.mjs \
  components/speak_ai/sources/new-recording/new-recording.mjs \
  components/speak_ai/sources/new-sentiment/new-sentiment.mjs \
  components/speak_ai/sources/new-transcription/new-transcription.mjs \
  components/speak_ai/sources/new-media-created-instant/new-media-created-instant.mjs \
  components/speak_ai/sources/new-text-analyzed-instant/new-text-analyzed-instant.mjs
do
  echo "--- $f ---"
  sed -n '1,80p' "$f"
done

Repository: PipedreamHQ/pipedream

Length of output: 12189


Use the event timestamp instead of Date.now(). The shared webhook meta builder should prefer the payload’s own timestamp when present and only fall back to Date.now() if none is available; otherwise delayed deliveries will all look like they happened at ingestion time.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/speak_ai/sources/common/base.mjs` around lines 46 - 52, The shared
meta builder in generateMeta currently stamps every event with Date.now(), which
ignores the payload’s own timing. Update generateMeta in base.mjs to prefer the
event/resource timestamp field from the incoming data (using the payload value
when present) and only fall back to Date.now() when no timestamp exists, keeping
the id and summary behavior unchanged.

Source: Path instructions

async processResource(resource) {
const data = await this.hydrate(resource);
this.$emit(data, this.generateMeta(resource, data));
},
},
async run({ body }) {
if (!body || typeof body !== "object" || Array.isArray(body)) {
console.log("Skipping event: webhook body is missing or not a resource object.");
return;
}
await this.processResource(body);
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
};
47 changes: 47 additions & 0 deletions components/speak_ai/sources/new-ai-chat/new-ai-chat.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import common from "../common/base.mjs";
import events from "../common/events.mjs";
import sampleEmit from "./test-event.mjs";

export default {
...common,
key: "speak_ai-new-ai-chat",
name: "New AI Chat Response (Instant)",
description: "Emit new event when a Speak AI Chat response is ready (`chat.status`). [See the documentation](https://docs.speakai.co/).",
version: "0.0.1",
type: "source",
dedupe: "unique",
methods: {
...common.methods,
getEvents() {
return [
events.CHAT_STATUS,
];
},
getSummary(resource) {
return `New AI Chat response ${resource.messageId || resource.promptId || ""}`.trim();
},
async hydrate(resource) {
const res = await this.app.getPromptsHistory({
headers: {
Accept: "application/json",
},
params: {
pageSize: 100,
},
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
const history = res?.data?.history || [];
const match = history.find((item) =>
(resource.messageId && item.messageId === resource.messageId)
|| (resource.promptId && item.promptId === resource.promptId));
return match || {
promptId: resource.promptId,
messageId: resource.messageId,
prompt: resource.prompt,
answer: resource.answer,
mediaIds: resource.mediaIds,
folderId: resource.folderId,
};
},
},
sampleEmit,
};
39 changes: 39 additions & 0 deletions components/speak_ai/sources/new-ai-chat/test-event.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
export default {
"id": "64c2cfc863d1160cd6c2262a",
"promptId": "64c2cfc863d1160cd6c2262a",
"messageId": "45df1bc7b1b2",
"prompt": "What challenges is the team at Speak AI having with software sales?",
"answer": "From the text, it is mentioned that the team at Speak AI is facing several challenges with software sales, including customer segmentation, competition, and limited resources.",
"createdAt": "2023-07-27T20:12:56.645Z",
"assistantType": "General",
"mediaName": "Speak AI Office Hours",
"mediaIds": [],
"folderId": "27c6e4a90e32",
"state": "completed",
"link": "https://app.speakai.co/media/list?dialog=chat&tab=history",
"references": [
{
"companyId": "5e21c8dd2d77242c64214816",
"createdAt": "2021-08-06T17:39:24.934Z",
"folderId": "27c6e4a90e32",
"mediaId": "a59b93b67685",
"name": "Office Hours 28",
"tags": [
"Office Hours"
],
"type": "video",
"userId": "5d03a9d5d4bca272e9c8cf89",
"sentences": [
{
"speaker": "Vatsal Shah",
"startTime": "0:04:41.38",
"endTime": "0:05:26.46",
"text": "Working on the engineering side building the product and moving to the customer segmentation side.",
"link": "",
"score": 0.84037739
}
],
"score": 0.84037739
}
]
};
51 changes: 51 additions & 0 deletions components/speak_ai/sources/new-captions/new-captions.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import common from "../common/base.mjs";
import events from "../common/events.mjs";
import sampleEmit from "./test-event.mjs";

export default {
...common,
key: "speak_ai-new-captions",
name: "New Captions (Instant)",
description: "Emit new event with caption files (SRT or VTT) when Speak AI finishes analyzing a media file (`media.analyzed`, `media.reanalyzed`). [See the documentation](https://docs.speakai.co/).",
version: "0.0.1",
type: "source",
dedupe: "unique",
props: {
...common.props,
fileType: {
type: "string",
label: "Caption File Type",
description: "The caption format to fetch",
options: [
"SRT",
"VTT",
],
default: "SRT",
},
},
methods: {
...common.methods,
getEvents() {
return [
events.MEDIA_ANALYZED,
events.MEDIA_REANALYZED,
];
},
getSummary(resource) {
return `New ${this.fileType} captions for media ${resource.mediaId}`;
},
async hydrate(resource) {
const results = await this.app.getExport({
headers: {
Accept: "application/json",
},
params: {
mediaId: resource.mediaId,
fileType: this.fileType.toLowerCase(),
},
});
return this.app.firstResult(results, resource);
},
},
sampleEmit,
};
8 changes: 8 additions & 0 deletions components/speak_ai/sources/new-captions/test-event.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default {
"id": "4f42d4179f68",
"mediaId": "4f42d4179f68",
"name": "GMT20211029-100117_Recording_1920x1080",
"description": "testing 123",
"durationInSecond": "185",
"caption": "1\n00:00:11,780 --> 00:00:13,750\nHi, that's all I just wanted to\n\n2\n00:00:15,180 --> 00:00:20,050\nwrite you a message, but it's taking\ntoo long so I think it's maybe it's\njust easier if I.\n"
};
36 changes: 36 additions & 0 deletions components/speak_ai/sources/new-recording/new-recording.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import common from "../common/base.mjs";
import events from "../common/events.mjs";
import sampleEmit from "./test-event.mjs";

export default {
...common,
key: "speak_ai-new-recording",
name: "New Recording Captured (Instant)",
description: "Emit new event when a recording is captured through a Speak AI embed recorder (`embed_recorder.recording_received`). [See the documentation](https://docs.speakai.co/).",
version: "0.0.1",
type: "source",
dedupe: "unique",
methods: {
...common.methods,
getEvents() {
return [
events.EMBED_RECORDER_RECORDING_RECEIVED,
];
},
getSummary(resource) {
return `New recording captured for media ${resource.mediaId}`;
},
async hydrate(resource) {
const results = await this.app.getInsights({
params: {
uploadType: "recorder",
mediaId: resource.mediaId,
page: 0,
pageSize: 1,
},
});
return this.app.firstResult(results, resource);
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
},
sampleEmit,
};
Loading