i18n - docs translations (#22617)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22617?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> Co-authored-by: github-actions <github-actions@twenty.com>
This commit is contained in:
committed by
GitHub
parent
07a921f8ca
commit
18ca89bcdd
+80
@@ -0,0 +1,80 @@
|
||||
---
|
||||
title: 5. An AI agent
|
||||
icon: robot
|
||||
description: السماح لوكيل بتوليد مستندات من محادثة، باستخدام أداتك.
|
||||
---
|
||||
|
||||
لأن `وثيقة توليد الطاقة` مكشوفة كـ **أداة**، يمكن لوكيل الذكاء الاصطناعي أن يطلق عليها.
|
||||
دعونا نضيف وكيلا ومهارة حتى يمكن للمستخدمين أن يقولوا فقط *"إنشاء اقتراح لـ
|
||||
Jeffery Griffin"*.
|
||||
|
||||
## المهارة
|
||||
|
||||
A [skill](/l/ar/developers/extend/apps/logic/skills-and-agents) قابل لإعادة الاستخدام- المعرفة التي تربطها بالوكلاء. علمنا النموذج كيفية استخدام
|
||||
الأداة.
|
||||
|
||||
```ts filename="src/skills/document-drafting.skill.ts"
|
||||
import { defineSkill } from 'twenty-sdk/define';
|
||||
|
||||
export default defineSkill({
|
||||
universalIdentifier: DOCUMENT_SKILL_UNIVERSAL_IDENTIFIER,
|
||||
name: 'document-drafting',
|
||||
label: 'Document drafting',
|
||||
icon: 'IconFileText',
|
||||
content: [
|
||||
'To generate a document, call the `generate-document` tool with:',
|
||||
'- `templateId`: the id of the document template to use.',
|
||||
'- `recordId`: the id of the Person or Company the document is for.',
|
||||
'',
|
||||
'If the user names a template or person instead of an id, find the record first,',
|
||||
'then pass its id. Make sure the template target matches the record type.',
|
||||
].join('\n'),
|
||||
});
|
||||
```
|
||||
|
||||
## الوكيل
|
||||
|
||||
[agent](/l/ar/developers/extend/apps/logic/skills-and-agents) يزوج موجه مع نموذج
|
||||
. تعيين 'استمارة الاستجابة' بشكل صريح لتجنب تحذير البناء.
|
||||
|
||||
```ts filename="src/agents/document-assistant.agent.ts"
|
||||
import { defineAgent } from 'twenty-sdk/define';
|
||||
|
||||
export default defineAgent({
|
||||
universalIdentifier: DOCUMENT_AGENT_UNIVERSAL_IDENTIFIER,
|
||||
name: 'document-assistant',
|
||||
label: 'Document Assistant',
|
||||
description: 'Generates documents from your templates and CRM records.',
|
||||
icon: 'IconFileText',
|
||||
responseFormat: { type: 'text' },
|
||||
prompt: [
|
||||
'You are the Document Assistant for a CRM.',
|
||||
'You help users generate personalized documents from reusable templates',
|
||||
'and the data already in their CRM. Use the generate-document tool, and',
|
||||
'always confirm what you created.',
|
||||
].join(' '),
|
||||
});
|
||||
```
|
||||
|
||||
<Note>
|
||||
ولا يمكن للوكيل الاتصال بالأداة إلا إذا كان دوره يسمح بذلك. لقد قمنا بالفعل بتعيين
|
||||
'cancessAllTools: true' و 'canBeAssignedToAgents: true' على دور التطبيق في
|
||||
[الفصل 2](/l/ar/developers/extend/apps/tutorials/document-generator/generating-documents#grant-it-access).
|
||||
</Note>
|
||||
|
||||
## جرب ذلك
|
||||
|
||||
افتح محادثة مع **مساعد مستندات** واطلب منها إعداد وثيقة لشخص
|
||||
في إدارة علاقات العملاء الخاصة بك. يجد السجل، يستدعي `generate-document`، ويُرجِع
|
||||
المستند الذي أنشأه — الذي يظهر الآن في عرض **Documents** لديك،
|
||||
تمامًا مثل مسارات قائمة الأوامر وسير العمل.
|
||||
|
||||
هذه هي ثمرة كشف المنطق كأداة: **دالة واحدة، والعديد من نقاط الدخول** —
|
||||
قائمة الأوامر، وHTTP، وخطوة سير العمل، والآن اللغة الطبيعية.
|
||||
|
||||
**بعد هذه الخطوة:** التطبيق كامل الميزة ومفيد حقا. حان الوقت
|
||||
لإطلاقه.
|
||||
|
||||
<Card title="التالي: النشر →" icon="rocket" href="/l/ar/developers/extend/apps/tutorials/document-generator/النشر">
|
||||
إضافة بيانات التعريف للسوق والنشر.
|
||||
</Card>
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
---
|
||||
title: 4. بناء واجهة المستخدم
|
||||
icon: table-columns
|
||||
description: المشاهدة، الملاحة الجانبية، الأوامر، والمكونات الأمامية.
|
||||
---
|
||||
|
||||
الآن يمكن الوصول إلى الكائنات فقط من خلال الإعدادات. لنمنح التطبيق حضورًا حقيقيًا في واجهة المستخدم: عروض قائمة، وعناصر في الشريط الجانبي، وأمر **Generate document** بنقرة واحدة، ومكوّن واجهة أمامية في صفحة السجل لِـ**preview** مستند، وعلامة تبويب **editor** نصيّة منسّقة (rich-text) أصلية للقوالب.
|
||||
|
||||
## وجهات النظر والملاحة
|
||||
|
||||
A [view](/l/ar/developers/extend/apps/layout/views) هي قائمة محفوظة لكائن معين.
|
||||
ويضع [عنصر قائمة التنقل](/l/ar/developers/extend/apps/layout/navigation-menu-items)
|
||||
هذا العرض في الشريط الجانبي.
|
||||
|
||||
```ts filename="src/views/documents.view.ts"
|
||||
import { defineView, ViewKey } from 'twenty-sdk/define';
|
||||
|
||||
export default defineView({
|
||||
universalIdentifier: DOCUMENTS_VIEW_UNIVERSAL_IDENTIFIER,
|
||||
name: 'All documents',
|
||||
objectUniversalIdentifier: DOCUMENT_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
icon: 'IconFile',
|
||||
key: ViewKey.INDEX,
|
||||
position: 0,
|
||||
fields: [
|
||||
{ universalIdentifier: DOCUMENTS_VIEW_NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
fieldMetadataUniversalIdentifier: DOCUMENT_NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
position: 0, isVisible: true, size: 280 },
|
||||
{ universalIdentifier: DOCUMENTS_VIEW_STATUS_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
fieldMetadataUniversalIdentifier: DOCUMENT_STATUS_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
position: 1, isVisible: true, size: 120 },
|
||||
{ universalIdentifier: DOCUMENTS_VIEW_TEMPLATE_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
fieldMetadataUniversalIdentifier: DOCUMENT_TEMPLATE_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
position: 2, isVisible: true, size: 200 },
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
```ts filename="src/navigation-menu-items/documents.navigation-menu-item.ts"
|
||||
import { defineNavigationMenuItem, NavigationMenuItemType } from 'twenty-sdk/define';
|
||||
|
||||
export default defineNavigationMenuItem({
|
||||
universalIdentifier: DOCUMENTS_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER,
|
||||
name: 'Documents',
|
||||
icon: 'IconFile',
|
||||
color: 'green',
|
||||
position: 1,
|
||||
type: NavigationMenuItemType.VIEW,
|
||||
viewUniversalIdentifier: DOCUMENTS_VIEW_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
أضف نفس زوج للنماذج. ويظهر كلاهما الآن في الشريط الجانبي:
|
||||
|
||||
<Frame caption="المستندات والقوالب في الشريط الجانبي، مع إدراج الوثيقة التي تم إنشاؤها.">
|
||||
<img src="/images/docs/developers/extends/apps/document-Generator/04-documents-view.png" alt="المستندات المعروضة مع مستند تم إنشاؤه" />
|
||||
</Frame>
|
||||
|
||||
## مكون أمامي
|
||||
|
||||
[مكون أمامي](/l/ar/developers/extend/apps/layout/front-components) هو مكون React
|
||||
مربع رملي داخل عشرين. يقرأ السجل المحدد، يحمّل قوالب الشخص
|
||||
عبر `CoreApiClient`، و POSTs إلى المسار من الفصل
|
||||
الأخير.
|
||||
|
||||
```tsx filename="src/front-components/generate-document-form.front-component.tsx"
|
||||
import { useEffect, useState } from 'react';
|
||||
import { CoreApiClient } from 'twenty-client-sdk/core';
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import { enqueueSnackbar, useSelectedRecordIds } from 'twenty-sdk/front-component';
|
||||
|
||||
const GenerateDocumentForm = () => {
|
||||
const selectedRecordIds = useSelectedRecordIds();
|
||||
const recordId = selectedRecordIds.length === 1 ? selectedRecordIds[0] : null;
|
||||
const [templates, setTemplates] = useState<{ id: string; name: string }[]>([]);
|
||||
const [templateId, setTemplateId] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
new CoreApiClient()
|
||||
.query({ documentTemplates: {
|
||||
__args: { filter: { target: { eq: 'PERSON' } }, first: 100 },
|
||||
edges: { node: { id: true, name: true } } } })
|
||||
.then(({ documentTemplates }) => {
|
||||
const list = documentTemplates?.edges?.map((e) => e.node) ?? [];
|
||||
setTemplates(list);
|
||||
if (list[0]) setTemplateId(list[0].id);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const generate = async () => {
|
||||
const apiBaseUrl = process.env.TWENTY_API_URL;
|
||||
const token = process.env.TWENTY_APP_ACCESS_TOKEN ?? process.env.TWENTY_API_KEY;
|
||||
const res = await fetch(`${apiBaseUrl}/s/documents/generate`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
|
||||
body: JSON.stringify({ templateId, recordId }),
|
||||
}).then((r) => r.json());
|
||||
await enqueueSnackbar({
|
||||
message: res.success ? 'Document generated.' : 'Generation failed.',
|
||||
variant: res.success ? 'success' : 'error',
|
||||
});
|
||||
};
|
||||
|
||||
// ...render a <select> of templates and a Generate button
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: GENERATE_DOCUMENT_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
||||
name: 'generate-document-form',
|
||||
component: GenerateDocumentForm,
|
||||
});
|
||||
```
|
||||
|
||||
<Warning>
|
||||
النمط مع متغيرات CSS المضمنة (`var(--t-color-blue)`)، ليس القيم المستوردة من
|
||||
`XXui`. تمزق SDK تلك الحزمة أثناء البناء، لذا فإن واردات مستوى الوحدة النمطية من ثوابت السمة
|
||||
ستكون 'غير محددة\`. انظر
|
||||
[الكامل المكون](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/front-components/generate-document-form.front-component.tsx).
|
||||
</Warning>
|
||||
|
||||
## أمر لفتحه
|
||||
|
||||
[أمر قائمة](/l/ar/developers/extend/apps/layout/command-menu-items) مع
|
||||
\`available ityType: 'RECORD_SELECTION'' يظهر عند اختيار شخص، و
|
||||
يفتح المكون في اللوحة الجانبية.
|
||||
|
||||
```ts filename="src/command-menu-items/generate-document.command-menu-item.ts"
|
||||
import { defineCommandMenuItem, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS } from 'twenty-sdk/define';
|
||||
|
||||
export default defineCommandMenuItem({
|
||||
universalIdentifier: GENERATE_DOCUMENT_COMMAND_UNIVERSAL_IDENTIFIER,
|
||||
label: 'Generate document',
|
||||
availabilityType: 'RECORD_SELECTION',
|
||||
availabilityObjectUniversalIdentifier:
|
||||
STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
|
||||
frontComponentUniversalIdentifier:
|
||||
GENERATE_DOCUMENT_FORM_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
||||
});
|
||||
```
|
||||
|
||||
## جرب التدفق بأكمله
|
||||
|
||||
افتح **People**، وحدِّد شخصًا، ثم اضغط <kbd>⌘K</kbd> / <kbd>Ctrl K</kbd>.
|
||||
يظهر "إنشاء مستند"، وسم مع التطبيق الخاص بك:
|
||||
|
||||
<Frame caption="يظهر الأمر عندما يتم اختيار شخص.">
|
||||
<img src="/images/docs/developers/extends/apps/document-generator/06-command-menu.png" alt="قائمة الأوامر مع توليد المستند" />
|
||||
</Frame>
|
||||
|
||||
قم بتشغيله – يفتح مكونك في اللوحة الجانبية. اختيار قالب، انقر
|
||||
**إنشاء**، وسجل جديد في **الوثائق**.
|
||||
|
||||
<Frame caption="المكون الأمامي، تحميل القوالب والتوليد على النقرة.">
|
||||
<img src="/images/docs/developers/extends/apps/document-Generator/06b-front-component.png" alt="إنشاء لوحة وثيقة جانبية" />
|
||||
</Frame>
|
||||
|
||||
يسجل كل مستند تم إنشاؤه التطبيق الخاص بك كمؤلف:
|
||||
|
||||
<Frame caption="تم إنشاؤها من قبل مولد المستندات، الحالة التي تم إنشاؤها.">
|
||||
<img src="/images/docs/developers/extends/apps/document-generator/05-document-record.png" alt="سجل مستند تم إنشاؤه" />
|
||||
</Frame>
|
||||
|
||||
## معاينة مستند في صفحة السجل الخاصة به
|
||||
|
||||
المكون الأمامي ليس فقط لقوائم الأوامر - يمكنك تحميل واحد كعلامة تبويب \*\*على صفحة تسجيل
|
||||
\*\*. دعونا نضيف علامة تبويب *Preview* إلى سجل المستند الذي يجعل جسم
|
||||
Markdown كصفحة مصقولة قابلة للطباعة.
|
||||
|
||||
ويقرأ المكون معرف السجل الحالي من سياق التنفيذ، ويحمل المستند
|
||||
ويضعه. تعمل المكوّنات الأمامية ضمن **sandbox** لا يسمح إلا بقائمة محددة من علامات HTML — يتم حظر حقن HTML الخام (`dangerouslySetInnerHTML`) وعنصر `\<style>` — لذلك نقوم بعرض Markdown كعناصر React مع أنماط مضمنة عبر مساعد [`Markdown`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/utils/markdown-to-react.tsx) صغير.
|
||||
|
||||
```tsx filename="src/front-components/document-viewer.front-component.tsx"
|
||||
import { CoreApiClient } from 'twenty-client-sdk/core';
|
||||
import { defineFrontComponent } from 'twenty-sdk/define';
|
||||
import { useFrontComponentExecutionContext } from 'twenty-sdk/front-component';
|
||||
import { Markdown } from 'src/utils/markdown-to-react';
|
||||
|
||||
const DocumentViewer = () => {
|
||||
const recordId = useFrontComponentExecutionContext((c) => c.recordId ?? null);
|
||||
// ...load { content, file } for recordId, then derive the links:
|
||||
const pdfUrl = document.file?.[0]?.url;
|
||||
const webUrl = `${process.env.TWENTY_API_URL ?? ''}/s/documents/view?id=${recordId}`;
|
||||
|
||||
// Render the template body, plus quick links to the web page and the PDF.
|
||||
// Links open in a new tab so they don't navigate the embedded component.
|
||||
return (
|
||||
<div style={styles.scroll}>
|
||||
<div style={styles.actions}>
|
||||
<a style={styles.actionLink} href={webUrl} target="_blank" rel="noopener noreferrer">
|
||||
Open web page
|
||||
</a>
|
||||
{pdfUrl ? (
|
||||
<a style={styles.actionLink} href={pdfUrl} target="_blank" rel="noopener noreferrer">
|
||||
Download PDF
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
<div style={styles.paper}>
|
||||
<div style={styles.body}>
|
||||
<Markdown content={document.content} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default defineFrontComponent({
|
||||
universalIdentifier: DOCUMENT_VIEWER_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
||||
name: 'document-viewer',
|
||||
component: DocumentViewer,
|
||||
});
|
||||
```
|
||||
|
||||
قم بتحميلها مع [تخطيط الصفحة](/l/ar/developers/extend/apps/layout/page-layouts). يضيف تخطيط
|
||||
`RECORD_PAGE` علامات تبويب إلى طريقة عرض تسجيل الكائن؛ ويستضيف عنصر التحكم 'FRONT_COMPONENT`
|
||||
في علامة تبويب 'CANVAS` المكون:
|
||||
|
||||
```ts filename="src/page-layouts/document-record.page-layout.ts"
|
||||
import { definePageLayout, PageLayoutTabLayoutMode } from 'twenty-sdk/define';
|
||||
|
||||
export default definePageLayout({
|
||||
universalIdentifier: DOCUMENT_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER,
|
||||
name: 'Document record page',
|
||||
type: 'RECORD_PAGE',
|
||||
objectUniversalIdentifier: DOCUMENT_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
tabs: [{
|
||||
universalIdentifier: DOCUMENT_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER,
|
||||
title: 'Preview',
|
||||
icon: 'IconEye',
|
||||
position: 50,
|
||||
layoutMode: PageLayoutTabLayoutMode.CANVAS,
|
||||
widgets: [{
|
||||
universalIdentifier: DOCUMENT_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIER,
|
||||
title: 'Document preview',
|
||||
type: 'FRONT_COMPONENT',
|
||||
configuration: {
|
||||
configurationType: 'FRONT_COMPONENT',
|
||||
frontComponentUniversalIdentifier: DOCUMENT_VIEWER_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
||||
},
|
||||
}],
|
||||
}],
|
||||
});
|
||||
```
|
||||
|
||||
افتح أي مستند – علامة تبويب **المعاينة** تجعله جميلا، مع روابط إلى صفحة ويب
|
||||
القابلة للمشاركة و PDF:
|
||||
|
||||
<Frame caption="علامة التبويب المعاينة تجعل المستند بأنماط مضمنة، بالإضافة إلى روابط سريعة.">
|
||||
<img src="/images/docs/developers/extends/apps/document-generator/09-document-viewer.png" alt="عنصر واجهة عارض المستند في علامة تبويب صفحة التسجيل" />
|
||||
</Frame>
|
||||
|
||||
## تحرير قالب مع محرر النص الغني
|
||||
|
||||
القوالب لا تحتاج إلى عنصر مخصص على الإطلاق. لأن `body' هو حقل
|
||||
`RICH_TEXT' ، يقدم عشرون محرر نص ثري كامل له -
|
||||
نفس المحرر القياسي للمذكرة وعناصر المهمة. نحن فقط نسطحه على صفحة سجل القالب
|
||||
|
||||
إضافة علامة تبويب مع عنصر واجهة المستخدم 'FIELD' في وضع العرض 'EDITOR' ، مع الإشارة إلى حقل 'body'
|
||||
عبر 'field MetadataId':
|
||||
|
||||
```ts filename="src/page-layouts/template-record.page-layout.ts"
|
||||
{
|
||||
universalIdentifier: TEMPLATE_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER,
|
||||
title: 'Template',
|
||||
position: 1,
|
||||
layoutMode: PageLayoutTabLayoutMode.GRID,
|
||||
widgets: [{
|
||||
universalIdentifier: TEMPLATE_PAGE_LAYOUT_WIDGET_UNIVERSAL_IDENTIFIER,
|
||||
title: 'Template',
|
||||
type: 'FIELD',
|
||||
gridPosition: { row: 0, column: 0, rowSpan: 6, columnSpan: 12 },
|
||||
configuration: {
|
||||
configurationType: 'FIELD',
|
||||
fieldMetadataId: TEMPLATE_BODY_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
fieldDisplayMode: 'EDITOR',
|
||||
},
|
||||
}],
|
||||
}
|
||||
```
|
||||
|
||||
يخزن حقل "RICH_TEXT" كتلة المحرر JSON وإسقاط Markdown
|
||||
على حد سواء. تقوم قناة التوليد بقراءة إسقاط Markdown هذا، بحيث تظل العناصر البديلة placeholders وملف PDF وصفحة الويب القابلة للمشاركة تعمل دون أي تغيير — اطّلع على الملف الكامل [`template-record.page-layout.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/page-layouts/template-record.page-layout.ts).
|
||||
الآن المحررون يكتبون قوالب في محرر نص غني مناسب:
|
||||
|
||||
<Frame caption="علامة التبويب النموذج: محرر النص الغني الأصلي 20 مرتبط بحقل الجسم.">
|
||||
<img src="/images/docs/developers/extends/apps/document-Generator/10-template-editor.png" alt="سجل القالب مع علامة تبويب محرر النص الغني الأصلي" />
|
||||
</Frame>
|
||||
|
||||
**بعد هذه الخطوة:** معاينة الوثائق بشكل جميل والقوالب قابلة للتحرير
|
||||
في التطبيق. ثم دعنا وكيل الذكاء الاصطناعي يولدها من محادثة.
|
||||
|
||||
<Card title="التالي : وكيل AI →" icon="robot" href="/l/ar/developers/extend/apps/tutorials/document-Generator/ai-agent">
|
||||
إضافة وكيل ومهارة يتصلان بأداتك.
|
||||
</Card>
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
---
|
||||
title: ١. نموذج البيانات
|
||||
icon: database
|
||||
description: وثائق نموذجية ونماذج ذات كائنات وحقول وعلاقة.
|
||||
---
|
||||
|
||||
يحتاج تطبيقنا إلى عنصرين مخصصين: **قوالب وثيقة** (ما يجب كتابته) و
|
||||
**وثائق** (النتيجة المنشأة). دعونا نعرفهم.
|
||||
|
||||
سكاف كل ملف كيان مع CLI - يخلق مجلد UUID صالح و
|
||||
الصحيح لك:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty dev:add object
|
||||
```
|
||||
|
||||
نعرض أدناه الملفات المكتملة.
|
||||
|
||||
<Note>
|
||||
كل `*_UNIVERSAL_IDENTIFIER` يعيش باستمرار في
|
||||
`src/constants/universal-Identiers.ts` ويتم استيراده حيثما استخدم. كتل الكود
|
||||
أسفل أغفل تلك الواردات للإيجاز، أبقيها في ملفاتك.
|
||||
</Note>
|
||||
|
||||
## عنصر القالب
|
||||
|
||||
يحتوي القالب على `name`، و `body` يتضمن `{{placeholders}}`، و `target` يحدد ما إذا كان مكتوبًا لشخص أو لشركة. 'body' هو حقل
|
||||
'RICH_TEXT' لذا فإن عشرون يمنحه محرر نص ثري كامل.
|
||||
|
||||
```ts filename="src/objects/document-template.object.ts"
|
||||
import { defineObject, FieldType } from 'twenty-sdk/define';
|
||||
|
||||
export default defineObject({
|
||||
universalIdentifier: DOCUMENT_TEMPLATE_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
nameSingular: 'documentTemplate',
|
||||
namePlural: 'documentTemplates',
|
||||
labelSingular: 'Document template',
|
||||
labelPlural: 'Document templates',
|
||||
icon: 'IconFileText',
|
||||
labelIdentifierFieldMetadataUniversalIdentifier:
|
||||
TEMPLATE_NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
fields: [
|
||||
{ universalIdentifier: TEMPLATE_NAME_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
type: FieldType.TEXT, name: 'name', label: 'Name', icon: 'IconAbc' },
|
||||
{ universalIdentifier: TEMPLATE_BODY_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
type: FieldType.RICH_TEXT, name: 'body', label: 'Body', icon: 'IconFileText',
|
||||
description: 'Use {{placeholders}} like {{name.firstName}} or {{jobTitle}}.' },
|
||||
{ universalIdentifier: TEMPLATE_TARGET_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
type: FieldType.SELECT, name: 'target', label: 'Target', icon: 'IconTarget',
|
||||
defaultValue: `'PERSON'`,
|
||||
options: [
|
||||
{ id: TEMPLATE_TARGET_OPTION_PERSON_UNIVERSAL_IDENTIFIER,
|
||||
value: 'PERSON', label: 'Person', color: 'blue', position: 0 },
|
||||
{ id: TEMPLATE_TARGET_OPTION_COMPANY_UNIVERSAL_IDENTIFIER,
|
||||
value: 'COMPANY', label: 'Company', color: 'green', position: 1 },
|
||||
] },
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
<Warning>
|
||||
يجب أن يكون خيار `SELECT` **القيم** `UPER_CASE` (`PERSON`، وليس `person`)، و
|
||||
`defaultValue` مغلقاً في اقتباسات إضافية: ` `PERSON`". إن `label\` هو ما يراه المستخدمون.
|
||||
</Warning>
|
||||
|
||||
## كائن المستند
|
||||
|
||||
الوثيقة التي تم إنشاؤها تخزن "المحتوى" و "الحالة". قم بتعريفه
|
||||
بنفس الطريقة، مع اختيار 'الحالة' من 'DRAFT` / 'GENERATED`. الملف الكامل:
|
||||
[`document.object.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/objects/document.object.ts).
|
||||
|
||||
## ربطهم بعلاقة
|
||||
|
||||
يجب أن يشير كل مستند مرة أخرى إلى النموذج الذي أتى منه. العلاقات هي
|
||||
**ثنائية الاتجاه** - أنت تحدد كلا الجانبين، كل منهما في ملف الحقل الخاص به.
|
||||
|
||||
```ts filename="src/fields/document-template-relation.field.ts"
|
||||
import { defineField, FieldType, OnDeleteAction, RelationType } from 'twenty-sdk/define';
|
||||
|
||||
// The "many" side: each document belongs to one template.
|
||||
export default defineField({
|
||||
universalIdentifier: DOCUMENT_TEMPLATE_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
objectUniversalIdentifier: DOCUMENT_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
type: FieldType.RELATION,
|
||||
name: 'template',
|
||||
label: 'Template',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
DOCUMENT_TEMPLATE_OBJECT_UNIVERSAL_IDENTIFIER,
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
TEMPLATE_DOCUMENTS_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
universalSettings: {
|
||||
relationType: RelationType.MANY_TO_ONE,
|
||||
onDelete: OnDeleteAction.SET_NULL,
|
||||
joinColumnName: 'templateId',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
الجانب الآخر (`template-documents-relation.field.ts`) هو حقل
|
||||
`RelationType.ONE_TO_MANY` يسمى `documents` الذي يشير إلى الاتجاه المعاكس.
|
||||
انظر [Relations](/l/ar/developers/extend/apps/data/relations) للاطلاع على النمط الكامل.
|
||||
|
||||
## مشاهدته في العشرين
|
||||
|
||||
مع تشغيل 'yarn 20 inenty dev'، افتح **الإعدادات → البيانات النموذجية**. يظهر كلا العنصرين
|
||||
، وسم مع التطبيق الخاص بك.
|
||||
|
||||
<Frame caption="كلا العنصرين المخصصين، مملوكين لتطبيق مولد المستندات.">
|
||||
<img src="/images/docs/developers/extends/apps/document-Generator/01-data-model.png" alt="إعدادات نموذج البيانات التي تظهر قوالب المستندات والمستندات" />
|
||||
</Frame>
|
||||
|
||||
قم بإنشاء قالب واحد لاختباره - اسمه *اقتراح المبيعات*، وقم بتعيين **الهدف** إلى
|
||||
*Person*، وقم بلصق جسم مع بعض العناصر النائبة:
|
||||
|
||||
```text
|
||||
Dear {{name.firstName}} {{name.lastName}},
|
||||
|
||||
As {{jobTitle}} at {{company.name}}, we think you'll love our product.
|
||||
|
||||
Best,
|
||||
The Team
|
||||
```
|
||||
|
||||
<Frame caption="سجل قالب. وتحافظ هذه الهيئة على الجهات الناقلة لها إلى حين إصدار وثيقة.">
|
||||
<img src="/images/docs/developers/extends/apps/document-Generator/03-template-record.png" alt="سجل قالب مقترح المبيعات مع الهيئة النائبة" />
|
||||
</Frame>
|
||||
|
||||
**بعد هذه الخطوة:** لديك كائنات 'documentTemplate' و 'document'، مرتبطة بـ
|
||||
علاقة، و قالب واحد لتوليد منها. وبعد ذلك، المنطق الذي يعبئه
|
||||
|
||||
<Card title="التالي: توليد الوثائق →" icon="bolt" href="/l/ar/developers/extend/apps/tutorials/document-Generator/التوليد-المستندات">
|
||||
اكتب الدالة المنطقية التي تملأ القالب.
|
||||
</Card>
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
---
|
||||
title: ٢. إصدار الوثائق
|
||||
icon: bolt
|
||||
description: وظيفة منطقية واحدة معرّفة كأداة الذكاء الاصطناعي وعملية سير العمل.
|
||||
---
|
||||
|
||||
الآن النواة الأساسية: [دالة المنطقية](/l/ar/developers/extend/apps/logic/logic-functions)
|
||||
التي تحمّل قالب وسجل، تملأ العناصر النائبة، وتحفظ وثيقة
|
||||
جديدة.
|
||||
|
||||
سنقوم بكتابة منطق العمل مرة واحدة ك\*\*معالج \*\*، ثم نكشف عنه من خلال
|
||||
عدة مشغلات. هذا الفصل يربط اثنين منهم - أداة **AI** و
|
||||
**عمل سير العمل**.
|
||||
|
||||
## مساعد التقديم
|
||||
|
||||
حافظ على المنطق الخالص في ملفه الخاص بحيث أنه من السهل إجراء اختبار الوحدة. هذا يربط سجل
|
||||
في `{{dot.path}}` رموز ويبدلها.
|
||||
|
||||
```ts filename="src/logic-functions/utils/render-template.ts"
|
||||
const PLACEHOLDER_PATTERN = /\{\{\s*([\w.]+)\s*\}\}/g;
|
||||
|
||||
export const renderTemplate = (body: string, values: Record<string, string>) => {
|
||||
const missingTokens = new Set<string>();
|
||||
const content = body.replace(PLACEHOLDER_PATTERN, (_m, token: string) => {
|
||||
const value = values[token];
|
||||
if (value === undefined || value === '') { missingTokens.add(token); return ''; }
|
||||
return value;
|
||||
});
|
||||
return { content, missingTokens: [...missingTokens] };
|
||||
};
|
||||
```
|
||||
|
||||
<Tip>
|
||||
لأن هذا الملف ليس له تأثيرات جانبية، يمكنك تغطيته عن طريق اختبارات الوحدة السريعة
|
||||
('اختبار yarn:unit\`). انظر [Testing](/l/ar/developers/extend/apps/operations/testing).
|
||||
</Tip>
|
||||
|
||||
## المعالج
|
||||
|
||||
يستخدم المعالج [`CoreApiClient`](/l/ar/developers/extend/apps/logic/logic-functions)
|
||||
لقراءة وكتابة بيانات CRM. يحمّل القالب، ويحمّل السجل المستهدف، ويملأ
|
||||
الجسم، وينشئ `وثيقة`.
|
||||
|
||||
```ts filename="src/logic-functions/handlers/generate-document-handler.ts"
|
||||
import { CoreApiClient } from 'twenty-client-sdk/core';
|
||||
import { loadRecordValues } from 'src/logic-functions/utils/load-record-values';
|
||||
import { renderTemplate } from 'src/logic-functions/utils/render-template';
|
||||
|
||||
export const generateDocumentHandler = async (
|
||||
input: { templateId: string; recordId: string },
|
||||
) => {
|
||||
const client = new CoreApiClient();
|
||||
|
||||
// Use a filtered list query, not the singular lookup: the singular query
|
||||
// throws when nothing matches, which would become a 500 instead of a 404.
|
||||
const { documentTemplates } = await client.query({
|
||||
documentTemplates: {
|
||||
__args: { filter: { id: { eq: input.templateId } }, first: 1 },
|
||||
edges: { node: { id: true, name: true, body: true, target: true } },
|
||||
},
|
||||
});
|
||||
const documentTemplate = documentTemplates?.edges?.[0]?.node;
|
||||
if (!documentTemplate?.id) return { success: false, status: 404, message: 'Template not found.' };
|
||||
|
||||
const record = await loadRecordValues(client, documentTemplate.target, input.recordId);
|
||||
if (!record.found) return { success: false, status: 404, message: 'Record not found.' };
|
||||
|
||||
const { content, missingTokens } = renderTemplate(documentTemplate.body ?? '', record.values);
|
||||
|
||||
const { createDocument } = await client.mutation({
|
||||
createDocument: {
|
||||
__args: { data: {
|
||||
name: `${documentTemplate.name} — ${record.displayName}`,
|
||||
content, status: 'GENERATED', templateId: documentTemplate.id,
|
||||
} },
|
||||
id: true, name: true,
|
||||
},
|
||||
});
|
||||
|
||||
return { success: true, documentId: createDocument.id, content, missingTokens };
|
||||
};
|
||||
```
|
||||
|
||||
'loadRecordValues' يدير استفسارا مختلفا لشخص ضد شركة ومسطحات
|
||||
النتيجة - انظر
|
||||
[\`load-record-values.ts'](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/logic-functions/utils/load-record-values.ts).
|
||||
|
||||
## الكشف عنه كأداة وإجراءات سير العمل
|
||||
|
||||
يمكن لـ `defineLogicFunction' أن يحمل عدة مشغلات. هنا، "إعدادات الأدوات"
|
||||
تجعلها قابلة للاستدعاء من قبل عملاء AI ، و `workflowtionTriggertings\` تحولها إلى خطوة
|
||||
في منشئ سير العمل البصري. ويصف كلاهما إسهاماتهما بمخطط "JSON".
|
||||
|
||||
```ts filename="src/logic-functions/generate-document.ts"
|
||||
import { defineLogicFunction } from 'twenty-sdk/define';
|
||||
import { jsonSchemaToInputSchema } from 'twenty-sdk/logic-function';
|
||||
import { GENERATE_DOCUMENT_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
|
||||
import { generateDocumentHandler } from 'src/logic-functions/handlers/generate-document-handler';
|
||||
import { generateDocumentInputSchema } from 'src/logic-functions/schemas/generate-document-input.schema';
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: GENERATE_DOCUMENT_LOGIC_FUNCTION_UNIVERSAL_IDENTIFIER,
|
||||
name: 'generate-document',
|
||||
description: 'Generate a document from a template and a CRM record.',
|
||||
timeoutSeconds: 30,
|
||||
toolTriggerSettings: {
|
||||
inputSchema: generateDocumentInputSchema,
|
||||
},
|
||||
workflowActionTriggerSettings: {
|
||||
label: 'Generate Document',
|
||||
icon: 'IconFileText',
|
||||
inputSchema: jsonSchemaToInputSchema(generateDocumentInputSchema),
|
||||
outputSchema: [{ type: 'object', properties: {
|
||||
success: { type: 'boolean' }, documentId: { type: 'string' },
|
||||
} }],
|
||||
},
|
||||
handler: generateDocumentHandler,
|
||||
});
|
||||
```
|
||||
|
||||
مخطط المدخلات هو مخطط JSON بسيط يصف `templateId` و`recordId` -
|
||||
أنظر [`generate-document-input.schema.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/logic-functions/schemas/generate-document-input.schema.ts).
|
||||
|
||||
## امنح حق الوصول
|
||||
|
||||
الوظائف المنطقية تعمل كدور للتطبيق. تحتاج إلى قراءة القوالب والسجلات
|
||||
وإنشاء المستندات، لذلك اسمح بذلك في `src/roles/default-role.ts`:
|
||||
|
||||
```ts
|
||||
export default defineApplicationRole({
|
||||
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
||||
label: 'Document Generator default role',
|
||||
canReadAllObjectRecords: true,
|
||||
canUpdateAllObjectRecords: true,
|
||||
canAccessAllTools: true,
|
||||
canBeAssignedToAgents: true,
|
||||
permissionFlagUniversalIdentifiers: [SystemPermissionFlag.UPLOAD_FILE],
|
||||
});
|
||||
```
|
||||
|
||||
يتيح "UPLOAD_FILE" للوظيفة تحميل PDF التي تم إنشاؤها في القسم التالي.
|
||||
انظر [Roles](/l/ar/developers/extend/apps/config/rolesللحصول على أذونات مأخوذة من الحبوب الأكثر.
|
||||
|
||||
## إرفاق ملف PDF حقيقي
|
||||
|
||||
حقل نص محرر مفيد، لكن المستخدمين يريدون وثيقة حقيقية. دعونا ننشئ
|
||||
**PDF** ونخزنه في السجل كملف قابل للتنزيل.
|
||||
|
||||
أولا، اعطي الكائن 'document' حقل 'FILES' للاحتفاظ بـ PDF. تقوم التطبيقات برفع
|
||||
في حقول الملفات الخاصة بها **الخاصة**، لذلك هذا الحقل هو مسارات الرفع:
|
||||
|
||||
```ts filename="src/objects/document.object.ts"
|
||||
{
|
||||
universalIdentifier: DOCUMENT_FILE_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
type: FieldType.FILES,
|
||||
name: 'file',
|
||||
label: 'File',
|
||||
icon: 'IconFileTypePdf',
|
||||
universalSettings: { maxNumberOfValues: 1 },
|
||||
}
|
||||
```
|
||||
|
||||
الآن اجعل قوات الدفاع الشعبي تلك. التطبيق هو مشروع عقدة حقيقي، بحيث يمكنك إضافة أي npm
|
||||
حزمة تحتاج إليها واستيرادها مثل أي مكان آخر. نحن نستخدم **[pdf-lib](https://pdf-lib.js.org/)**
|
||||
لرسم PDF و **[marked](https://marked.js.org/)** لتحليل جسم Markdown- يقوم CLI بتثبيته في وقت تشغيل الوظيفة لك:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn add pdf-lib marked
|
||||
```
|
||||
|
||||
المساعد الكامل هو
|
||||
[`generate-document-pdf.ts'](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/logic-functions/utils/generate-document-pdf.ts).
|
||||
إنه يحلل علامة الرموز المميزة مع علامة `علامة'. ممارسة '، ثم تضعها باستخدام
|
||||
pdf-lib: العناوين الحقيقية، **الجريئة**/*italic*، تدير الرصاصات والقوائم المرقمة،
|
||||
الكتل والقواعد-عبارة عن مقتطفات متعددة الصفحات A4 لتقديم القالب
|
||||
نفسه، وليس جدار نص.
|
||||
|
||||
<Frame caption="PDF المولدة: الطباعة الحقيقية وتنسيق Markdown، وتقديم نموذج الجسم.">
|
||||
<img src="/images/docs/developers/extends/apps/document-generator/07b-generated-pdf.png" alt="تم توليد PDF مصقول قابل للتسويق" />
|
||||
</Frame>
|
||||
|
||||
<Note>
|
||||
خطوط pdf-lib المدمجة تستخدم تشفير WinAnsi ، لذا لهجات غرب أوروبا تقدم
|
||||
خارج الصندوق؛ خرائط المساعد مقتبسات ذكية والشرطات وتسقط الشخصيات
|
||||
لا يمكن ترميزها. إن عرض النصوص غير اللاتينية (الصينية، العربية، السيريلية) سيعني
|
||||
تضمين خط Unicode.
|
||||
</Note>
|
||||
|
||||
ثم قم بتحميله وتخزين المرجع في السجل. مسارات 'uploadFile' بايت
|
||||
إلى حقل الملفات الذي تملكه التطبيق؛ و\`id' المعاد هو ما قمت بحفظه:
|
||||
|
||||
```ts filename="src/logic-functions/handlers/generate-document-handler.ts"
|
||||
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
|
||||
import { generateDocumentPdf } from 'src/logic-functions/utils/generate-document-pdf';
|
||||
|
||||
const documentName = `${documentTemplate.name} — ${record.displayName}`;
|
||||
const bytes = await generateDocumentPdf(documentName, content);
|
||||
const fileName = 'proposal.pdf';
|
||||
|
||||
const uploaded = await new MetadataApiClient().uploadFile(
|
||||
Buffer.from(bytes),
|
||||
fileName,
|
||||
'application/pdf',
|
||||
DOCUMENT_FILE_FIELD_UNIVERSAL_IDENTIFIER,
|
||||
);
|
||||
|
||||
await client.mutation({
|
||||
updateDocument: {
|
||||
__args: {
|
||||
id: documentId,
|
||||
data: { file: [{ fileId: uploaded.id, label: fileName }] },
|
||||
},
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
الوثيقة التي تم إنشاؤها تحمل الآن PDF قابل للتنزيل:
|
||||
|
||||
<Frame caption="PDF التي تم إنشاؤها، مخزنة في حقل ملف المستند.">
|
||||
<img src="/images/docs/developers/extends/apps/document-generator/08-document-with-pdf.png" alt="سجل مستند مع ملف PDF الذي تم إنشاؤه" />
|
||||
</Frame>
|
||||
|
||||
<Note>
|
||||
'uploadFile' يستهدف فقط حقول الملفات **التي تملكها التطبيقات** (لذلك يتطلب التحميل دائمًا تطبيق
|
||||
يمتلك الحقل، بالإضافة إلى علم الدور 'UPLOAD_FILE\`). لهذا السبب يهبط PDF
|
||||
في الحقل 'file' الخاص بالسجل - نفس النمط الذي يستخدمه
|
||||
[call-record](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/public/call-recorder)
|
||||
للتسجيلات.
|
||||
</Note>
|
||||
|
||||
**بعد هذه الخطوة:** كل مستند تم إنشاؤه لديه PDF حقيقي، قابل للتنزيل. ولكن
|
||||
لا يوجد ما يمكنه *استدعاء* المولّد من واجهة المستخدم بعد — ولأجل ذلك نحتاج إلى مسار HTTP.
|
||||
|
||||
<Card title="التالي: طرق HTTP →" icon="الكرة الأرضية" href="/l/ar/developers/extend/apps/tutorials/document-Generator/http-routes">
|
||||
خدمة الوظيفة عبر HTTP وتقديم الوثائق كصفحات على الويب.
|
||||
</Card>
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
---
|
||||
title: ٣. مسارات HTTP
|
||||
icon: globe
|
||||
description: قم بتفعيل الوظيفة على HTTP وتقديم الوثائق كصفحات ويب.
|
||||
---
|
||||
|
||||
نفس المعالج يمكنه أيضا الإجابة على طلبات HTTP. سوف نضيف مسارين:
|
||||
|
||||
* نقطة النهاية **POST** مكالمات واجهة المستخدم لإنشاء وثيقة، و
|
||||
* نقطة نهاية عامة **GET** تجعل الوثيقة صفحة ويب قابلة للطباعة.
|
||||
|
||||
وكلاهما يستخدم `httpRouteTriggerSettings`. طرق التطبيق تقدم تحت `/s` على خادم
|
||||
20 (على سبيل المثال 'http://localhost:2020/s/documents/generate\`).
|
||||
|
||||
## مسار POST - توليد حسب الطلب
|
||||
|
||||
هذا يعيد استخدام `GenerateDocumentHandler`، لذلك لا يوجد منطق لتكراره - مجرد محول رقيق
|
||||
الذي يقرأ الجسم المطلوب.
|
||||
|
||||
```ts filename="src/logic-functions/generate-document-route.ts"
|
||||
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define';
|
||||
import { Response } from 'twenty-sdk/logic-function';
|
||||
import { generateDocumentHandler } from 'src/logic-functions/handlers/generate-document-handler';
|
||||
|
||||
const handler = async (event: RoutePayload): Promise<Response> => {
|
||||
const body = event.body as Record<string, unknown> | null;
|
||||
|
||||
const result = await generateDocumentHandler({
|
||||
templateId: (body?.templateId as string) ?? '',
|
||||
recordId: (body?.recordId as string) ?? '',
|
||||
});
|
||||
|
||||
// Map the handler's failure reason onto a real HTTP status (400/404/500)
|
||||
// instead of always returning 200.
|
||||
return new Response(JSON.stringify(result), {
|
||||
status: result.success ? 200 : (result.status ?? 400),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: GENERATE_DOCUMENT_ROUTE_UNIVERSAL_IDENTIFIER,
|
||||
name: 'generate-document-route',
|
||||
timeoutSeconds: 30,
|
||||
handler,
|
||||
httpRouteTriggerSettings: {
|
||||
path: '/documents/generate',
|
||||
httpMethod: 'POST',
|
||||
isAuthRequired: true,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
يقوم المعالج المشترك بإرجاع 'حالة' مقترحة عند الفشل، بحيث يمكن للطريق
|
||||
الإجابة مع رمز `4xx`/`5xx`. `isAuthRerequirered: true` يعني أنه يجب على المتصل
|
||||
أن يقدم رمزا صالحا - المكون الأمامي في الفصل التالي يمر رمز الوصول إلى
|
||||
المستخدم تلقائيا.
|
||||
|
||||
## مسار GET - يقدم كصفحة على الشبكة
|
||||
|
||||
لإرجاع HTML بدلاً من JSON، قم بتدوين الجسم في 'استجابة` مع رأس
|
||||
'محتوى - نوع`. هذا المسار عام ('isAuthRerequirered: false\`) بحيث يمكن مشاركة مستند تم إنشاؤه
|
||||
كرابط .
|
||||
|
||||
```ts filename="src/logic-functions/view-document.ts"
|
||||
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define';
|
||||
import { Response } from 'twenty-sdk/logic-function';
|
||||
import { CoreApiClient } from 'twenty-client-sdk/core';
|
||||
import { documentHtmlPage } from 'src/utils/render-document';
|
||||
|
||||
const htmlResponse = (html: string, status = 200): Response =>
|
||||
new Response(html, { status, headers: { 'Content-Type': 'text/html; charset=utf-8' } });
|
||||
|
||||
const handler = async (event: RoutePayload): Promise<Response> => {
|
||||
const documentId = event.queryStringParameters?.id;
|
||||
|
||||
if (!documentId) {
|
||||
return htmlResponse(documentHtmlPage('Missing document id', 'Provide ?id=<documentId>.'), 400);
|
||||
}
|
||||
|
||||
// Filtered list query so an unknown id renders a clean 404 page instead of throwing.
|
||||
const { documents } = await new CoreApiClient().query({
|
||||
documents: {
|
||||
__args: { filter: { id: { eq: documentId } }, first: 1 },
|
||||
edges: { node: { id: true, name: true, content: true } },
|
||||
},
|
||||
});
|
||||
|
||||
const document = documents?.edges?.[0]?.node;
|
||||
if (!document?.id) {
|
||||
return htmlResponse(documentHtmlPage('Document not found', `No document with id ${documentId}.`), 404);
|
||||
}
|
||||
|
||||
return htmlResponse(documentHtmlPage(document.name ?? 'Document', document.content ?? ''));
|
||||
};
|
||||
|
||||
export default defineLogicFunction({
|
||||
universalIdentifier: VIEW_DOCUMENT_ROUTE_UNIVERSAL_IDENTIFIER,
|
||||
name: 'view-document',
|
||||
timeoutSeconds: 15,
|
||||
handler,
|
||||
httpRouteTriggerSettings: {
|
||||
path: '/documents/view',
|
||||
httpMethod: 'GET',
|
||||
isAuthRequired: false,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
'documentHtmlPage' يجعل جسم Markdown إلى HTML (مع [marked](https://marked.js.org/)،
|
||||
محسوسة) ويسقط في نظيف، صفحة قابلة للطباعة تعرض فقط محتوى القالب- نفس مظهر PDF والمعاينة داخل التطبيق.
|
||||
[انظر المساعد](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/utils/render-document.ts).
|
||||
|
||||
## جرب ذلك
|
||||
|
||||
مع قالب وشخص في مساحة العمل الخاصة بك، اتصل بالمسار (التقط الرمز المميز من
|
||||
**الإعدادات → APIs & Webhooks**):
|
||||
|
||||
```bash filename="Terminal"
|
||||
curl -X POST http://localhost:2020/s/documents/generate \
|
||||
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
|
||||
-d '{"templateId":"<templateId>","recordId":"<personId>"}'
|
||||
# → {"success":true,"documentId":"...","content":"Dear Jeffery Griffin, ..."}
|
||||
```
|
||||
|
||||
افتح المستند الذي تم إرجاعه في المتصفح الخاص بك:
|
||||
|
||||
```
|
||||
http://localhost:2020/s/documents/view?id=<documentId>
|
||||
```
|
||||
|
||||
<Frame caption="ويجعل مسار الهيئة العامة للتكنولوجيا من الوثيقة صفحة قابلة للطباعة.">
|
||||
<img src="/images/docs/developers/extends/apps/document-generator/07-rendered-document.png" alt="صفحة وثيقة تم إصدارها" />
|
||||
</Frame>
|
||||
|
||||
<Tip>
|
||||
يمكنك أيضًا بث سجلات الدالة أثناء اختبارها باستخدام
|
||||
`yarn 20 dev:function:logs`، أو استدعاء ذلك مباشرة باستخدام
|
||||
`yarn 20 dev:function:exec`.
|
||||
</Tip>
|
||||
|
||||
**بعد هذه الخطوة:** يمكن للتطبيق إنشاء مستندات عبر HTTP وخدمتها كصفحات ويب
|
||||
الآن دعونا نجعله قابلا للاستخدام بدون "تعطيل".
|
||||
|
||||
<Card title="التالي : بناء واجهة المستخدم →" icon="table-columns" href="/l/ar/developers/extend/apps/tutorials/document-Generator/building-the-ui">
|
||||
المشاهدة، الملاحة، الأوامر، والعنصر الأمامي.
|
||||
</Card>
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: "دليل تعليمي: مولِّد المستندات"
|
||||
icon: wand-magic-sparkles
|
||||
description: أنشئ تطبيق Twenty حقيقيًا يُولِّد مستندات مخصَّصة من بيانات نظام إدارة علاقات العملاء (CRM) الخاصة بك.
|
||||
---
|
||||
|
||||
في هذا الدليل التعليمي ستقوم بإنشاء **مولِّد المستندات** — تطبيق يحوِّل القوالب القابلة لإعادة الاستخدام إلى مستندات مخصَّصة باستخدام البيانات الموجودة مسبقًا في نظام إدارة علاقات العملاء (CRM) لديك.
|
||||
|
||||
اكتب قالبًا مرة واحدة باستخدام `{{placeholders}}`، ثم أنشئ مستندًا مكتمل الحقول لأي شخص أو شركة بنقرة واحدة — من قائمة الأوامر، أو من وكيل ذكاء اصطناعي، أو من سير عمل.
|
||||
|
||||
<Frame caption="قالب واحد، يتم إنشاؤه لشخص محدد، وفتحه كصفحة قابلة للطباعة.">
|
||||
<img src="/images/docs/developers/extends/apps/document-generator/07-rendered-document.png" alt="مستند عرض مبيعات تم إنشاؤه" />
|
||||
</Frame>
|
||||
|
||||
## ما ستتعلّمه
|
||||
|
||||
يُضيف كل فصل قدرة واحدة. بنهاية الدليل ستكون قد تعاملت مع معظم حزمة تطوير البرمجيات (SDK).
|
||||
|
||||
| الفصل | القدرة | مرجع |
|
||||
| ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------- |
|
||||
| [١. نموذج البيانات](/l/ar/developers/extend/apps/tutorials/document-generator/data-model) | الكائنات، الحقول، وعلاقة واحدة | [البيانات](/l/ar/developers/extend/apps/data/overview) |
|
||||
| [٢. إنشاء المستندات](/l/ar/developers/extend/apps/tutorials/document-generator/generating-documents) | دالة منطقية (أداة ذكاء اصطناعي + خطوة سير عمل) تملأ قالب Markdown وتُرفِق ملف PDF مصقولًا | [الوظائف المنطقية](/l/ar/developers/extend/apps/logic/logic-functions) |
|
||||
| [٣. مسارات HTTP](/l/ar/developers/extend/apps/tutorials/document-generator/http-routes) | تقديم JSON وصفحة HTML قابلة للمشاركة من المسارات | [الوظائف المنطقية](/l/ar/developers/extend/apps/logic/logic-functions) |
|
||||
| [4. بناء واجهة المستخدم](/l/ar/developers/extend/apps/tutorials/document-generator/building-the-ui) | العروض، التنقل، قائمة الأوامر، ومكوّنات الواجهة الأمامية التي تُعاين مستندًا وتحرّر قالبًا | [التخطيط](/l/ar/developers/extend/apps/layout/overview) |
|
||||
| [5. وكيل ذكاء اصطناعي](/l/ar/developers/extend/apps/tutorials/document-generator/ai-agent) | وكيل + مهارة | [المهارات والوكلاء](/l/ar/developers/extend/apps/logic/skills-and-agents) |
|
||||
| [6. النشر](/l/ar/developers/extend/apps/tutorials/document-generator/publishing) | إطلاقه في السوق | [النشر](/l/ar/developers/extend/apps/operations/publishing) |
|
||||
|
||||
## المتطلبات الأساسية
|
||||
|
||||
يجب أن تكون قد أنهيت قسم [البدء السريع](/l/ar/developers/extend/apps/getting-started/quick-start):
|
||||
خادم Twenty محلي يعمل على المنفذ `2020` وواجهة سطر أوامر (CLI) موثَّقة عليه.
|
||||
|
||||
إذا لم تكن قد فعلت ذلك، فقم بتهيئة واحد وتشغيله الآن:
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx create-twenty-app@latest document-generator
|
||||
cd document-generator
|
||||
yarn twenty dev
|
||||
```
|
||||
|
||||
<Note>
|
||||
تفضّل قراءة الكود النهائي؟ يوجد التطبيق الكامل في
|
||||
[`packages/twenty-apps/examples/document-generator`](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/examples/document-generator).
|
||||
كل مقتطف أدناه منسوخ منه.
|
||||
</Note>
|
||||
|
||||
## كيف تتكامل مكوّنات التطبيق معًا
|
||||
|
||||
<Frame>
|
||||
<img src="/images/docs/developers/extends/apps/document-generator/how-it-fits.svg" alt="يُحوَّل قالب يحتوي على عناصر نائبة إلى مستند مصقول مع ملف PDF، ويتم تشغيل ذلك من قائمة الأوامر، أو وكيل ذكاء اصطناعي، أو سير عمل، أو رابط قابل للمشاركة" />
|
||||
</Frame>
|
||||
|
||||
تكتب **قالبًا** مرة واحدة في محرّر نص منسّق، مع `{{placeholders}}`. يؤدي اختيار قالب وسجل في نظام إدارة علاقات العملاء إلى ملء العناصر النائبة وتخزين مستند مصقول (مع ملف PDF). كل ما عدا ذلك — قائمة الأوامر، وكيل الذكاء الاصطناعي،
|
||||
خطوة سير العمل، الرابط القابل للمشاركة — ما هو إلا طريقة مختلفة لتشغيل هذا
|
||||
المولّد الواحد.
|
||||
|
||||
## أبقِ هذه الحلقة قيد التشغيل
|
||||
|
||||
اترك الأمر `yarn twenty dev` يعمل في أحد الطرفيات طوال الدرس التطبيقي. في كل مرة تُضيف أو تعدّل ملفًا ضمن `src/`، تُعاد مزامنته مع خادمك خلال بضع ثوانٍ، لكي تتمكّن من مشاهدة كل قدرة تظهر في واجهة المستخدم أثناء بنائها.
|
||||
|
||||
<Card title="ابدأ البناء →" icon="قاعدة البيانات" href="/l/ar/developers/extend/apps/tutorials/document-generator/data-model">
|
||||
الفصل 1: نمذجة المستندات والقوالب.
|
||||
</Card>
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
---
|
||||
title: 6. النشر
|
||||
icon: rocket
|
||||
description: إضافة بيانات التعريف للسوق ونشر التطبيق الخاص بك.
|
||||
---
|
||||
|
||||
تطبيقك يعمل. والخطوة الأخيرة هي وصف ذلك للسوق والنشر.
|
||||
|
||||
## إضافة بيانات التعريف للسوق
|
||||
|
||||
[config](/l/ar/developers/extend/apps/config/application) يحمل الهوية
|
||||
التي تظهر في السوق: روابط المؤلف والفئة والشعار والدعم
|
||||
. ضع الشعار في `public/` وقم بالرجوع إليه مع `logoUrl`.
|
||||
|
||||
```ts filename="src/application-config.ts"
|
||||
import { defineApplication } from 'twenty-sdk/define';
|
||||
|
||||
export default defineApplication({
|
||||
universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
displayName: 'Document Generator',
|
||||
description:
|
||||
'Create reusable document templates and generate personalized documents from your CRM records.',
|
||||
logoUrl: 'public/document-generator.svg',
|
||||
author: 'Twenty',
|
||||
category: 'Productivity',
|
||||
websiteUrl: 'https://docs.twenty.com/l/ar/developers/extend/apps',
|
||||
termsUrl: 'https://www.twenty.com/terms',
|
||||
emailSupport: 'contact@twenty.com',
|
||||
issueReportUrl: 'https://github.com/twentyhq/twenty/issues',
|
||||
});
|
||||
```
|
||||
|
||||
<Tip>
|
||||
يتم الإعلان عن الدور الافتراضي مع 'defineApplicationRole()' في ملفه الخاص - أنت
|
||||
لا تتجاوز 'defaultRoleUniversalIdentifier' هنا بعد الآن.
|
||||
</Tip>
|
||||
|
||||
أضف أيضا الكلمة المفتاحية `٢٢app` إلى `package.json` حتى يكون التطبيق قابلا للاكتشاف:
|
||||
|
||||
```json filename="package.json"
|
||||
{ "keywords": ["twenty-app"] }
|
||||
```
|
||||
|
||||
## إضافة لقطات شاشة معرض الصور
|
||||
|
||||
قائمة السوق تبيع نفسها مع لقطات شاشة. إسقاط عدد قليل من PNGs في
|
||||
`public/gallery/` والإشارة إليهم بـ \`screenshots' - يتم عرضهم كمعرض
|
||||
في صفحة الإدراج في القائمة.
|
||||
|
||||
```ts filename="src/application-config.ts"
|
||||
export default defineApplication({
|
||||
// ...identity from above
|
||||
screenshots: [
|
||||
'public/gallery/01-generated-document.png',
|
||||
'public/gallery/02-command-menu.png',
|
||||
'public/gallery/03-template-editor.png',
|
||||
'public/gallery/04-documents.png',
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
<Tip>
|
||||
الرصاص مع الدفع: اجعل أول لقطة للشاشة النتيجة النهائية (وثيقة
|
||||
تم إنشاؤها)، ثم اظهر كيف تم تشغيلها وكتابتها. استخدم التقاط
|
||||
عالي الدقة - إنها أول شيء يراه المستخدم.
|
||||
</Tip>
|
||||
|
||||
إعطاء العلاج نفسه 'README.md' - إنها الصفحة الأولى على npm و GitHub.
|
||||
افتح مع اقتراح القيمة و لقطة شاشة، قائمة بميزات العنوان،
|
||||
ثم ابقي تفاصيل البناء تحت الطبع.
|
||||
|
||||
## تحقق قبل الشحن
|
||||
|
||||
تشغيل نفس البوابات CI :
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn lint # oxlint
|
||||
yarn typecheck # tsgo
|
||||
yarn test:unit # unit tests
|
||||
yarn twenty dev --once --dry-run # preview the metadata diff
|
||||
```
|
||||
|
||||
يُظهر التشغيل التجريبي dry run بالضبط ما سيتغيّر على الخادم بدون تطبيقه —
|
||||
وهو فحص أخير جيّد للتأكّد من سلامة كل شيء. انظر
|
||||
[Testing](/l/ar/developers/extend/apps/operations/testing) و
|
||||
[المزامنة والاسترداد](/l/ar/developers/extend/apps/operations/sync-and-recovery).
|
||||
|
||||
## النشر
|
||||
|
||||
```bash filename="Terminal"
|
||||
# Public app → npm (default)
|
||||
yarn twenty app:publish
|
||||
|
||||
# Or deploy privately to a specific server's registry
|
||||
yarn twenty app:publish --private -r <remote>
|
||||
```
|
||||
|
||||
'app:publish' يبني وينشر إلى npm بشكل افتراضي؛ '--private' يرفع قاعدة
|
||||
tarball إلى السجل الخاص لخادم 20 بدلا من ذلك. لتظهر تطبيق منشور
|
||||
في سوق مثيل ما، قم بتفعيل مزامنة الكتالوج:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty dev:catalog-sync -r <remote>
|
||||
```
|
||||
|
||||
التفاصيل الكاملة وقائمة التحقق من الإصدار:
|
||||
[Publishing](/l/ar/developers/extend/apps/operations/publishing).
|
||||
|
||||
## قمت ببناء تطبيق 🎉
|
||||
|
||||
في ستة فصول استخدمت معظم سطح SDK:
|
||||
|
||||
* **الكائنات، الحقول و العلاقة** لنموذج البيانات
|
||||
* **دالة منطقية** مكشوفة كأداة \*\*AI \*\*، و **سير العمل**، و **مسارات HTTP**
|
||||
* **مشاهدات, تصفح, أمر و مكون أمامي** لواجهة المستخدم
|
||||
* **وكيل + مهارة** لتوليد اللغة الطبيعية
|
||||
* \*\*البيانات الوصفية للسوق \*\* وتدفق النشر
|
||||
|
||||
التطبيق النهائي هو في
|
||||
[`packages/XXapps/examples/document-Generator`](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/examples/document-generator).
|
||||
|
||||
## من أين تذهب بعد
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="مرجع البيانات" icon="database" href="/l/ar/developers/extend/apps/data/overview">
|
||||
كل نوع من أنواع الحقول والعلاقة وخيارات الفهرس.
|
||||
</Card>
|
||||
<Card title="مرجع منطقي" icon="bolt" href="/l/ar/developers/extend/apps/logic/overview">
|
||||
مشغلات أحداث Cron وقاعدة البيانات، ومتجر القيمة المفتاح، واتصالات OAuth.
|
||||
</Card>
|
||||
<Card title="مرجع التخطيط" icon="table-columns" href="/l/ar/developers/extend/apps/layout/overview">
|
||||
مخططات الصفحة، ودويدات لوحة المعلومات، ومزيد من أسطح واجهة المستخدم.
|
||||
</Card>
|
||||
<Card title="العمليات" icon="rocket" href="/l/ar/developers/extend/apps/operations/overview">
|
||||
() CLI، والاختبار، وعمليات الإزالة، وCI.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
Reference in New Issue
Block a user