لقد أجريت الكثير من التجارب مؤخرًا مع منصات مختلفة توفر ربط الخطوات معًا، وسأشارك واحدة أثارت حماسي: التعرف الضوئي على الحروف للكشف عن النص في صورة، وإلحاقه بمنشور Discourse!
يُظهر موضوع الاختبار الخاص بي كيفية عمل ذلك: https://notes.maiki.interi.org/t/testing-ocr-calls/403
لقد قمت بإجراء الأول على Pipedream، وسأشرح كيف فعلت ذلك باتباع الخطوات. توجد ميزة تجريبية لمشاركة سير العمل على هذه الخدمة، ويتم مشاركة ما يلي على Pipedream - Connect APIs, AI, databases and more.
بينما قد يكون هذا دليلًا معمقًا لاستخدام Pipedream مع Discourse، سأبقى خفيفًا في التفاصيل، حيث أنني انتقلت بالفعل من هذه المنصة، وسأشارك المزيد عن ذلك لاحقًا. 
الخطوات
المخطط التقريبي هو: إرسال عنوان URL للصورة إلى Google Cloud Vision API، وتشغيل النتائج عبر ChatGPT، ثم إلحاق النتائج بالمنشور في Discourse.
المشغل

يوفر هذا نقطة نهاية webhook لإرسال البيانات إليها. في Discourse، قمت بإنشاء webhook بإعدادين محددين:

يعني التشغيل فقط على أحداث المنشور أن موضوعي الأولي لن يؤدي إلى تشغيل العملية؛ هذا مفيد لي حتى أتمكن من التخطيط لاستخدام الموضوعات كحاملات لتطبيق وظائف خارجية (أسميها “ملاحظات وظيفية” (functional notes)).
يعني التشغيل فقط على هذا الوسم أنني أستطيع استخدام الوسوم للتحكم في أي webhooks يتم إنتاجها لكل موضوع؛ بشكل عام، سيكون لدي وسم “وظائف” واحد فقط، للحفاظ على منطق العملية بسيطًا.
يرسل webhook حمولة تحتوي على الكثير من المعلومات، وسنستخدم معرفات الموضوع والمنشور لاحقًا في العملية.
النهاية بناءً على شرط
هذه خطوة للتحقق مما إذا كان سبب التعديل مضمنًا. إذا كان الأمر كذلك، فإنه يوقف سير العمل.
في الخطوة الأخيرة، أقوم بتحديث المنشور وتضمين سبب التعديل، وهذا التحقق يضمن عدم استمراري في تحديث المنشور… 
أحد أسباب توقفي عن استخدام Pipedream هو أن عمليات التحقق من webhook الخاصة بي كانت تستهلك أرصدة على الخدمة. لا أعتقد أنه يجب علي الدفع لمعالجة webhook بشكل شرطي، ومن هنا الانتقال…
استخراج عنوان URL للصورة
قررت في هذا الاختبار أن كل منشور سيكون به صورة واحدة تم تحميلها إليه. تتحقق هذه الخطوة من القيمة “cooked” وتستخدم التعبير العادي التالي لالتقاط عنوان URL:
/https?:\/\/[^\s\"]+/
استدعاء Google Cloud Vision API
هذه خطوة رمز مخصص على Pipedream. المكونات المعدة مسبقًا لم تفعل ما أردته، كما أن الخدمة لديها مساعد رمز يمكنه كتابة رمز من مطالبة؛ نظرًا لأن استدعاءات API هذه مباشرة، كان من السهل إنتاجها بهذه الطريقة.
تأخذ قيمة الخطوة السابقة ({{steps.extract_by_regular_expression.$return_value[0].match}}) وهذا هو الرمز:
import { axios } from "@pipedream/platform";
export default defineComponent({
props: {
imageUrl: {
type: "string",
label: "Image URL",
description: "URL of the image to be processed by Google Vision API",
},
apiKey: {
type: "string",
label: "API Key",
description: "Your Google Cloud API Key",
secret: true,
},
},
async run() {
const url = `https://vision.googleapis.com/v1/images:annotate?key=${this.apiKey}`;
const body = {
requests: [
{
image: {
source: {
imageUri: this.imageUrl,
},
},
features: [
{
type: "TEXT_DETECTION",
},
],
},
],
};
const config = {
method: "POST",
url,
data: body,
};
const response = await axios(this, config);
return response;
},
});
ChatGPT للتحرير
تأخذ المخرجات من الخطوة السابقة ({{steps.google_cloud.$return_value.responses[0].fullTextAnnotation.text}}) وتمررها كرسالة المستخدم. بالنسبة لرسالة النظام لدي:
أنت تقرأ مخرجات من واجهة برمجة تطبيقات الرؤية التي اكتشفت نصًا في صورة. راجع الرسالة وقم بتدقيقها لزيادة الوضوح. أعد النص المعدل فقط بدون تعليق.
الإلحاق بالمنشور في Discourse
قسم رمز مخصص آخر، حيث أن إجراءات Discourse المعدة مسبقًا في Pipedream تغطي فقط بضعة سيناريوهات (إنشاء موضوع أو منشور)، وأريد إلحاق النص بالمنشور.
أولاً، إليك الرمز:
import { axios } from "@pipedream/platform";
export default defineComponent({
props: {
discourse: {
type: "app",
app: "discourse",
},
postId: {
type: "string",
label: "Post ID",
description: "The ID of the post to append text to",
},
text: {
type: "string",
label: "Text",
description: "The text to append to the post",
},
editReason: {
type: "string",
label: "Edit Reason",
description: "The reason for editing the post",
optional: true,
},
},
async run({ steps, $ }) {
const url = `https://${this.discourse.$auth.domain}/posts/${this.postId}.json`;
const response = await axios($, {
method: "GET",
url: url,
headers: {
"Api-Username": `${this.discourse.$auth.api_username}`,
"Api-Key": `${this.discourse.$auth.api_key}`,
},
});
const updatedText = `${response.raw} ${this.text}`;
return await axios($, {
method: "PUT",
url: url,
headers: {
"Api-Username": `${this.discourse.$auth.api_username}`,
"Api-Key": `${this.discourse.$auth.api_key}`,
},
data: {
post: {
raw: updatedText,
edit_reason: this.editReason,
},
},
});
},
});
يتم ملء خصائص الخطوة هذه على النحو التالي:
معرف المنشور
يلتقط معرف المنشور من الحمولة الأصلية: {{steps.trigger.event.body.post.id}}
يتم استخدامه لتعديل هذا المنشور مباشرة.
النص
---
<blockquote>
{{steps.chat.$return_value.generated_message.content}}
</blockquote>
[details="Detected text"]
{{steps.google_cloud.$return_value.responses[0].textAnnotations[0].description}}
[/details]
بشكل أساسي، أريد إضافة خط أفقي أسفل كل صورة، مع اقتباس للنص المعدل، وتفاصيل للتحقق من المخرجات الأولية.
نظرًا لأن كل منشور سيحتوي على صورة واحدة فيه، فإن هذا يعمل بسهولة شديدة. أتساءل كيف يمكن القيام بذلك مع صور متعددة في وقت واحد؟ 
سبب التعديل
OCR Text Detection
يتم إضافة هذا كسبب التعديل لتحديث المنشور، والذي سيمنع حلقة تحديث المنشور بسبب الخطوة في البداية.

أجد أنه من المفيد جدًا دائمًا تضمين سبب تعديل، خاصة عند التعامل مع خدمات خارجية.
وهذا كل شيء! كما ترى من مساحة الاختبار الخاصة بي، إنها تعمل بشكل جيد جدًا!
لدي رحلة قادمة، وأخطط لضبط تحرير OpenAI لترجمة إلى الإنجليزية أيضًا إذا لزم الأمر، وهذا خيار واحد كنت سأضيفه إلى رسالة النظام لسير العمل هذا.