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
+81
@@ -0,0 +1,81 @@
|
||||
---
|
||||
title: 5. An AI agent
|
||||
icon: robot
|
||||
description: Nechte agenta generovat dokumenty z chatu, pomocí vašeho nástroje.
|
||||
---
|
||||
|
||||
Protože je `generate-document` vystaven jako **nástroj**, agent AI ho může zavolat.
|
||||
Přidejme agenta a dovednosti, aby uživatelé mohli říci *"vygenerovat návrh
|
||||
Jeffery Griffin"*.
|
||||
|
||||
## Dovednost
|
||||
|
||||
[skill](/l/cs/developers/extend/apps/logic/skills-and-agents) je opakovaně použitelné
|
||||
– znalosti, které připojujete k agentům. Náš model učí jak použít
|
||||
nástroj.
|
||||
|
||||
```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'),
|
||||
});
|
||||
```
|
||||
|
||||
## Zástupce
|
||||
|
||||
[agent](/l/cs/developers/extend/apps/logic/skills-and-agents) spáruje výzvu s modelem
|
||||
. Nastavte `responseFormat` explicitně, abyste se vyhnuli varování sestavení.
|
||||
|
||||
```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>
|
||||
Zástupce může zavolat nástroj pouze v případě, že jej jeho role umožňuje. Již jsme nastavili
|
||||
`canAccessAllTools: true` a `canBeAssignedToAgents: true` na roli aplikace v
|
||||
[Chapter 2](/l/cs/developers/extend/apps/tutorials/document-generator/generating-documents#grant-it-access).
|
||||
</Note>
|
||||
|
||||
## Vyzkoušejte
|
||||
|
||||
Otevřete chat s **asistentem dokumentu** a požádejte jej, aby připravil dokument pro osobu
|
||||
ve vašem CRM. Najde záznam, volání `generate-document`, a nahlásí
|
||||
dokument, který vytvořil – který se nyní zobrazí ve tvém **dokumentu** zobrazení,
|
||||
přesně se líbí příkazové menu a cesty workflow.
|
||||
|
||||
To je výsledek odhalování logiky jako nástroje: **jedna funkce, mnoho předních dveří** —
|
||||
příkaz menu, HTTP, krok workflow a nyní přirozený jazyk.
|
||||
|
||||
**Po tomto kroku:** je aplikace kompletní a opravdu užitečná. Čas do
|
||||
lodi.
|
||||
|
||||
<Card title="Další: publikování →" icon="rocket" href="/l/cs/developers/extend/apps/tutorials/document-generator/publishing">
|
||||
Přidat metadata a publikovat tržiště.
|
||||
</Card>
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
---
|
||||
title: 4. Budování uživatelského rozhraní
|
||||
icon: table-columns
|
||||
description: Zobrazení, postranní navigace, příkaz a přední komponenty.
|
||||
---
|
||||
|
||||
Právě teď jsou objekty dosažitelné pouze v nastavení. Dejme aplikaci
|
||||
skutečnou přítomnost v UI: seznam zobrazení, položek postranního panelu, jedním kliknutím
|
||||
**Generovat příkaz** dokumentu, přední část rekordové stránky do **náhledu** dokumentu
|
||||
a nativní text **editor** pro šablony.
|
||||
|
||||
## Zobrazení a navigace
|
||||
|
||||
[view](/l/cs/developers/extend/apps/layout/views) je uložený seznam daného objektu.
|
||||
[Položka navigačního menu](/l/cs/developers/extend/apps/layout/navigation-menu-items)
|
||||
umístí toto zobrazení do postranního panelu.
|
||||
|
||||
```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,
|
||||
});
|
||||
```
|
||||
|
||||
Přidejte stejný pár šablon. Oba se nyní zobrazí v postranním panelu:
|
||||
|
||||
<Frame caption="Dokumenty a šablony v postranním panelu s vygenerovaným dokumentem.">
|
||||
<img src="/images/docs/developers/extends/apps/document-generator/04-documents-view.png" alt="Zobrazení dokumentů s vygenerovaným dokumentem" />
|
||||
</Frame>
|
||||
|
||||
## Přední část
|
||||
|
||||
[přední komponenta](/l/cs/developers/extend/apps/layout/front-components) je komponenta React
|
||||
boxovaná uvnitř dvaceti. Náš přečte vybraný záznam, načte
|
||||
šablony osob přes `CoreApiClient`, a POSTE na trasu z poslední kapitoly
|
||||
.
|
||||
|
||||
```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>
|
||||
Styl s inline CSS proměnnými (`var(--t-color-blue)`), ne hodnoty importované z
|
||||
`twenty-ui`. SDK mocniny, které balí během sestavení, takže importy
|
||||
téma na úrovni modulů by byly `nedefinované`. Podívejte se na
|
||||
[plnou komponentu](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/front-components/generate-document-form.front-component.tsx).
|
||||
</Warning>
|
||||
|
||||
## Příkaz k otevření
|
||||
|
||||
[položka příkazu](/l/cs/developers/extend/apps/layout/command-menu-items) s
|
||||
`availabilityType: 'RECORD_SELECTION'` se zobrazí při výběru osoby, a
|
||||
otevře komponentu v postranním panelu.
|
||||
|
||||
```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,
|
||||
});
|
||||
```
|
||||
|
||||
## Vyzkoušejte celý tok
|
||||
|
||||
Otevřete **People**, zaškrtněte osobu a stiskněte <kbd>⌘K</kbd> / <kbd>Ctrl K</kbd>.
|
||||
"Generovat doklad" se zobrazí, označeno vaší aplikací:
|
||||
|
||||
<Frame caption="Příkaz se zobrazí při výběru osoby.">
|
||||
<img src="/images/docs/developers/extends/apps/document-generator/06-command-menu.png" alt="Příkazové menu s generováním dokumentu" />
|
||||
</Frame>
|
||||
|
||||
Spusťte - komponenta se otevře v postranním panelu. Vyberte si šablonu, klikněte na
|
||||
**Generovat**, a nový záznam země v **dokumentech**.
|
||||
|
||||
<Frame caption="Přední komponenta, načítání šablon a generování kliknutím.">
|
||||
<img src="/images/docs/developers/extends/apps/document-generator/06b-front-component.png" alt="Generovat boční panel dokumentu" />
|
||||
</Frame>
|
||||
|
||||
Každý vygenerovaný dokument zaznamenává vaši aplikaci jako autora:
|
||||
|
||||
<Frame caption="Vytvořeno generátorem dokumentů, stav vygenerován.">
|
||||
<img src="/images/docs/developers/extends/apps/document-generator/05-document-record.png" alt="Generovaný záznam dokladu" />
|
||||
</Frame>
|
||||
|
||||
## Náhled dokumentu na jeho stránce s záznamem
|
||||
|
||||
Přední komponenta není pouze pro menu příkazů — jeden můžete připojit jako \*\*kartu na stránce
|
||||
záznamu \*\*. Přidejme kartu \*Náhled \* k záznamu dokumentu, která vykreslí
|
||||
Markdown tělo jako leštěnou, vytisknutelnou stránku.
|
||||
|
||||
Komponenta čte aktuální id záznamu z kontextového kontextu, načte
|
||||
dokument a vykreslí jej. Přední komponenty běží v **sandboxu**, který povoluje pouze
|
||||
bílou listinu HTML tagů — syrovou injekci HTML (`dangerouslySetInnerHTML`) a
|
||||
`\<style>` jsou blokovány — takže Markdown vykreslujeme jako React prvky s inline
|
||||
pomocí malého [`Markdown`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/utils/markdown-to-react.tsx)
|
||||
helper.
|
||||
|
||||
```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,
|
||||
});
|
||||
```
|
||||
|
||||
Připojit ji s [page layout](/l/cs/developers/extend/apps/layout/page-layouts). Rozložení
|
||||
`RECORD_PAGE` přidává záložky do zobrazení záznamu objektu; widget `FRONT_COMPONENT`
|
||||
v záložce `CANVAS` hostí komponentu:
|
||||
|
||||
```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,
|
||||
},
|
||||
}],
|
||||
}],
|
||||
});
|
||||
```
|
||||
|
||||
Otevřete jakýkoliv dokument - záložka \*\*Náhled \*\* jej krásně vykreslí s odkazy na
|
||||
sdílitelnou webovou stránku a PDF:
|
||||
|
||||
<Frame caption="Karta Náhled vykresluje dokument s vloženými styly, plus rychlé odkazy.">
|
||||
<img src="/images/docs/developers/extends/apps/document-generator/09-document-viewer.png" alt="Hlavní komponenta prohlížeče dokumentů na záložce záznamu" />
|
||||
</Frame>
|
||||
|
||||
## Upravit šablonu pomocí editoru s bohatým textem
|
||||
|
||||
Šablony vůbec nepotřebují vlastní komponentu. Protože je `body`
|
||||
`RICH_TEXT`, Dvacet již poskytuje plně bohatý textový editor –
|
||||
stejné jako standardní použití objektů poznámky a úkolu. Prostě jsme ji vykreslili na
|
||||
šablonové stránce záznamu.
|
||||
|
||||
Přidejte kartu s `FIELD` widgetem v režimu zobrazení `EDITOR` ukazujícím na pole `body`
|
||||
pomocí `fieldMetadataId`:
|
||||
|
||||
```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',
|
||||
},
|
||||
}],
|
||||
}
|
||||
```
|
||||
|
||||
Pole `RICH_TEXT` ukládá JSON blok editoru a Markdown
|
||||
projekci. Plynovod pro výrobu čte tyto Markdown projekce, takže
|
||||
zástupné symboly, PDF, a sdílitelná webová stránka všechny nefungují beze změny —
|
||||
viz celý
|
||||
[`template-record. age-layout.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/page-layouts/template-record.page-layout.ts).
|
||||
Nyní editoři píší šablony v řádném editoru s bohatým textem:
|
||||
|
||||
<Frame caption="Šablona: Dvacátý nativní textový editor vázaný na pole těla.">
|
||||
<img src="/images/docs/developers/extends/apps/document-generator/10-template-editor.png" alt="Záznam šablony s nativním textovým editorem" />
|
||||
</Frame>
|
||||
|
||||
**Po tomto kroku:** náhled dokumentů krásně a šablony jsou editovatelné
|
||||
v aplikaci. Dále nechte AI agenta generovat je z chatu.
|
||||
|
||||
<Card title="Další: agent AI →" icon="robot" href="/l/cs/developers/extend/apps/tutorials/document-generator/ai-agent">
|
||||
Přidejte agenta a dovednosti, které volají po nástroji.
|
||||
</Card>
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
---
|
||||
title: 1. Datový model
|
||||
icon: database
|
||||
description: Vzor dokumentů a šablon s objekty, polími a vztahem.
|
||||
---
|
||||
|
||||
Naše aplikace potřebuje dva vlastní objekty: **šablony dokumentů** (co psát) a
|
||||
**dokumenty** (generovaný výsledek). Pojďme je definovat.
|
||||
|
||||
Scaffold each entity file with the CLI — has a valid UUID and the right
|
||||
folder for you:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty dev:add object
|
||||
```
|
||||
|
||||
Níže zobrazujeme dokončené soubory.
|
||||
|
||||
<Note>
|
||||
Každý `*_UNIVERSAL_IDENTIFIER` trvale žije v
|
||||
`src/constants/universal-identifiers.ts` a je importován, pokud je použit. snippety
|
||||
níže vynechávají tyto importy pro brevitu - uchovejte je ve svých vlastních souborech.
|
||||
</Note>
|
||||
|
||||
## Objekt šablony
|
||||
|
||||
Šablona má `name`, `body` s `{{placeholders}}`, a `cíl`, který
|
||||
říká, zda je napsán pro osobu nebo společnost. `Těloy` je
|
||||
`RICH_TEXT` pole, takže dvacet jí dává plně bohatý textový editor.
|
||||
|
||||
```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` volba **hodnoty** musí být `UPPER_CASE` (`PERSON`, ne `person`) a
|
||||
`defaultValue` je zabalena do extra uvozovek: `` `'PERSON'` ``. `Štítek` je to, co vidí
|
||||
uživatelé.
|
||||
</Warning>
|
||||
|
||||
## Objekt dokumentu
|
||||
|
||||
Vygenerovaný dokument ukládá vykreslený `content` a `status`. Definujte
|
||||
stejným způsobem s `status` výběrem `DRAFT` / `GENERATED`. Celý soubor:
|
||||
[`document.object.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/objects/document.object.ts).
|
||||
|
||||
## Propojení se vztahem
|
||||
|
||||
Každý dokument by se měl vrátit ke šabloně, odkud pochází. Vztahy jsou
|
||||
**obousměrné** — definujete obě strany, každý ve vlastním souboru polí.
|
||||
|
||||
```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',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Druhá strana (`template-documents-relation.field.ts`) je
|
||||
`RelationType.ONE_TO_MANY` pole `documents`, které ukazuje opačnou cestu.
|
||||
Celý vzor viz [Relations](/l/cs/developers/extend/apps/data/relations).
|
||||
|
||||
## Podívejte se na to ve dvaceti letech
|
||||
|
||||
S spuštěním `příze dvacet dev` otevřete **Nastavení → Datový model**. Oba objekty
|
||||
se zobrazí a označují vaší aplikací.
|
||||
|
||||
<Frame caption="Oba vlastní objekty v aplikaci Generátor dokumentů.">
|
||||
<img src="/images/docs/developers/extends/apps/document-generator/01-data-model.png" alt="Nastavení datového modelu zobrazující dokumenty a šablony dokumentů" />
|
||||
</Frame>
|
||||
|
||||
Vytvořte jednu šablonu pro testování - pojmenujte ji *návrhem prodeje*, nastavte **Cíl** na
|
||||
*Osobní* a vložte tělo s několika zástupnými znaky:
|
||||
|
||||
```text
|
||||
Dear {{name.firstName}} {{name.lastName}},
|
||||
|
||||
As {{jobTitle}} at {{company.name}}, we think you'll love our product.
|
||||
|
||||
Best,
|
||||
The Team
|
||||
```
|
||||
|
||||
<Frame caption="Záznam šablony. Subjekt uchovává své zástupné znaky, dokud není doklad vygenerován.">
|
||||
<img src="/images/docs/developers/extends/apps/document-generator/03-template-record.png" alt="Záznam šablony obchodního návrhu s zástupným tělem" />
|
||||
</Frame>
|
||||
|
||||
**Po tomto kroku:** máte `documentTemplate` a `document` objekty, propojené
|
||||
relací a jednu šablonu, ze které chcete vygenerovat. Dále platí, že logika, která ji naplňuje.
|
||||
|
||||
<Card title="Další: generování dokumentů →" icon="bolt" href="/l/cs/developers/extend/apps/tutorials/document-generator/generating-documents">
|
||||
Napište logickou funkci, která vyplňuje šablonu.
|
||||
</Card>
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
---
|
||||
title: 2. Generování dokumentů
|
||||
icon: bolt
|
||||
description: Jedna logická funkce, zobrazená jako nástroj AI a akce pracovního postupu.
|
||||
---
|
||||
|
||||
Nyní jádro: [logická funkce](/l/cs/developers/extend/apps/logic/logic-functions)
|
||||
, která načte šablonu a záznam, vyplní zástupné symboly a uloží nový
|
||||
dokument.
|
||||
|
||||
Obchodní logiku napíšeme jednou jako **handler**, pak ji vystavíme pomocí
|
||||
několika spouštěčů. Tato kapitola vrací dva z nich – **nástroj AI** a
|
||||
**akce pracovního postupu**.
|
||||
|
||||
## Pomocník pro vykreslování
|
||||
|
||||
Uchovávejte čistou logiku ve svém vlastním souboru, takže je snadné testovat jednotku. Toto zarovná záznam
|
||||
do `{{dot.path}}` tokenů a nahradí je.
|
||||
|
||||
```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>
|
||||
Protože tento soubor nemá žádné vedlejší účinky, můžete jej zakrýt rychlými jednotkovými testy
|
||||
(`yarn test:unit`). Viz [Testing](/l/cs/developers/extend/apps/operations/testing).
|
||||
</Tip>
|
||||
|
||||
## Řidič
|
||||
|
||||
Pracovník používá vygenerovaná [`CoreApiClient`](/l/cs/developers/extend/apps/logic/logic-functions)
|
||||
pro čtení a zápis dat CRM. Načte šablonu, načte cílový záznam, vyplní
|
||||
tělo a vytvoří `dokument`.
|
||||
|
||||
```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` spouští jiný dotaz na Osobu vs. společnost a flattens
|
||||
výsledek — viz
|
||||
[`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).
|
||||
|
||||
## Vystavit ji jako nástroj a akce pracovního postupu
|
||||
|
||||
Jediný `defineLogicFunction` může obsahovat několik spouštěčů. Zde `toolTriggerSettings`
|
||||
dělá volatelné AI agenty a `workflowActionTriggerSettings` jej promění v
|
||||
krok na vizuálním workflow. Obě popisují svůj vstup se schématem 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,
|
||||
});
|
||||
```
|
||||
|
||||
Schéma vstupů je schéma prostého JSON popisující `templateId` a `recordId` —
|
||||
viz [`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).
|
||||
|
||||
## Povolit přístup
|
||||
|
||||
Logické funkce běží jako role aplikace. Potřebuje přečíst šablony a záznamy
|
||||
a vytvořit dokumenty, aby v `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` umožňuje funkci nahrát vygenerovaný PDF v další sekci.
|
||||
Pro jemnozrnná oprávnění viz [Roles](/l/cs/developers/extend/apps/config/roles).
|
||||
|
||||
## Připojit skutečný soubor PDF
|
||||
|
||||
Vykreslené textové pole je užitečné, ale uživatelé chtějí skutečný dokument. Vygenerujme
|
||||
**PDF** a uložme jej do záznamu jako soubor ke stažení.
|
||||
|
||||
Nejprve zadejte objekt `document` pole `FILES` pro držení PDF. Aplikace nahrávají
|
||||
do svých **vlastních** polí souborů, takže toto pole slouží jako trasa nahrávání:
|
||||
|
||||
```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 },
|
||||
}
|
||||
```
|
||||
|
||||
Nyní vytvořte tento PDF. Aplikace je skutečný projekt uzlu, takže můžete přidat libovolný balíček npm
|
||||
, který potřebujete a importovat jako kdekoli jinde. Používáme **[pdf-lib](https://pdf-lib.js.org/)**
|
||||
k nakreslení PDF a **[marked](https://marked.js.org/)** k rozepsání těla Markdown
|
||||
– CLI je nainstaluje do běhu funkce pro vás:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn add pdf-lib marked
|
||||
```
|
||||
|
||||
Úplný pomocník je
|
||||
[`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).
|
||||
Parazuje Markdown do tokenů s `označeným. exer`, pak je stanoví s
|
||||
pdf-lib: skutečné nadpisy, **tučné**/*italic* běhy, odrážky a číslované seznamy,
|
||||
blokuje kotace a pravidla – leštěné, vícestránkové A4 vykreslování šablony
|
||||
samotné, nikoli zeď textu.
|
||||
|
||||
<Frame caption="Vygenerovaný PDF: skutečné typografie a Markdown formátování, vykreslování těla šablony.">
|
||||
<img src="/images/docs/developers/extends/apps/document-generator/07b-generated-pdf.png" alt="Leštěný, tržně generovaný PDF" />
|
||||
</Frame>
|
||||
|
||||
<Note>
|
||||
Vestavěné fonty ve formátu pdf-lib, používají WinAnsi kódování, takže západoevropské akcenty vyřadí
|
||||
z krabice; pomocník mapy chytrých uvozovek a pomlček a kapne znaky, které nemůže kódovat
|
||||
. Vykreslování nelatinských skriptů (čínské, arabské, cyrilice) by znamenalo, že by
|
||||
vložil písmo Unicode.
|
||||
</Note>
|
||||
|
||||
Pak ho nahrajte a uložte odkaz do záznamu. `uploadFile` routy bytů
|
||||
do vašeho pole souborů vlastněných aplikací; vrácený `id` je to, co uložíte:
|
||||
|
||||
```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,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Vygenerovaný dokument má nyní stažitelný PDF:
|
||||
|
||||
<Frame caption="Vygenerovaný PDF uložený v poli Soubor dokumentu.">
|
||||
<img src="/images/docs/developers/extends/apps/document-generator/08-document-with-pdf.png" alt="Záznam dokladu s vygenerovaným souborem PDF" />
|
||||
</Frame>
|
||||
|
||||
<Note>
|
||||
`uploadFile` se zaměřuje pouze na **soubory vlastněné aplikací** (takže nahrávání vždy vyžaduje aplikaci
|
||||
, která toto pole vlastní, plus proměnnou role `UPLOAD_FILE`). To je důvod, proč PDF
|
||||
přistane na vlastním `file` pole záznamu — stejný vzor
|
||||
[call-recorder app](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/public/call-recorder)
|
||||
používá pro nahrávání.
|
||||
</Note>
|
||||
|
||||
**Po tomto kroku:** každý vygenerovaný dokument má skutečný, stahovatelný PDF. Ale
|
||||
nemůže *volat* generátor z uživatelského rozhraní - pro to potřebujeme HTTP trasu.
|
||||
|
||||
<Card title="Další: HTTP trasy →" icon="zeměkoule" href="/l/cs/developers/extend/apps/tutorials/document-generator/http-routes">
|
||||
Zapněte funkci přes HTTP a vykreslete dokumenty jako webové stránky.
|
||||
</Card>
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
---
|
||||
title: 3. HTTP trasy
|
||||
icon: globe
|
||||
description: Spuštění funkce přes HTTP a vykreslení dokumentů jako webových stránek.
|
||||
---
|
||||
|
||||
Stejný handler může také odpovědět na HTTP požadavky. Přidáme dva trasy:
|
||||
|
||||
* **POST** koncový bod uživatelského rozhraní volá, aby vytvořilo dokument, a
|
||||
* veřejný **GET** koncový bod, který vykresluje dokument jako tiskovou webovou stránku.
|
||||
|
||||
Oba použijte `httpRouteTriggerSettings`. Trasy aplikací jsou vedeny pod `/s` na vašem
|
||||
serveru (např. `http://localhost:2020/s/documents/generate`).
|
||||
|
||||
## POST trasa – generovat na požádání
|
||||
|
||||
Toto znovu používá `generateDocumentHandler`, takže není logika opakovat - jen tenký
|
||||
adaptér, který čte tělo požadavku.
|
||||
|
||||
```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,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Sdílený handler vrátí navržený `status` při selhání, takže trasa může
|
||||
odpovědět správným `4xx`/`5xx` kódem. `isAuthRequd: true` znamená, že volající
|
||||
musí prezentovat platný token — přední komponenta v další kapitole automaticky prochází přístupovým tokenem uživatele
|
||||
.
|
||||
|
||||
## Cesta GET – vykreslit jako webovou stránku
|
||||
|
||||
Chcete-li vrátit HTML místo JSON, zabalte tělo do `Response` pomocí
|
||||
`Content-Type` hlavičky. Tato cesta je veřejná (`isAuthRequd: false`), takže
|
||||
generovaný dokument může být sdílen jako odkaz.
|
||||
|
||||
```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` vykresluje Markdown tělo na HTML (s [marked](https://marked.js.org/),
|
||||
zmaskoval) a klesne do čistého, vytisknutelná stránka, která zobrazuje pouze obsah šablony
|
||||
– stejný vzhled jako PDF a náhled v aplikaci.
|
||||
[Viz pomocník] (https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/examples/document-generator/src/utils/render-document.ts).
|
||||
|
||||
## Vyzkoušejte
|
||||
|
||||
Pomocí šablony a osoby ve vašem pracovním prostoru zavolejte na trasu (získejte token z
|
||||
**Nastavení → API a 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, ..."}
|
||||
```
|
||||
|
||||
Otevřete vrácený dokument ve vašem prohlížeči:
|
||||
|
||||
```
|
||||
http://localhost:2020/s/documents/view?id=<documentId>
|
||||
```
|
||||
|
||||
<Frame caption="Veřejná trasa GET vykresluje dokument jako tiskovou stránku.">
|
||||
<img src="/images/docs/developers/extends/apps/document-generator/07-rendered-document.png" alt="Vykreslená webová stránka dokumentu" />
|
||||
</Frame>
|
||||
|
||||
<Tip>
|
||||
Během testování s
|
||||
`yarn twenty dev:function:logs`, nebo vyvolat přímo s
|
||||
`yarn twenty dev:exec`.
|
||||
</Tip>
|
||||
|
||||
**Po tomto kroku:** aplikace může generovat dokumenty přes HTTP a sloužit jako
|
||||
webové stránky. Nyní ho použijeme bez `curl`.
|
||||
|
||||
<Card title="Další: budování UI →" icon="table-columns" href="/l/cs/developers/extend/apps/tutorials/document-generator/building-the-ui">
|
||||
Zobrazení, navigace, příkaz a přední součást.
|
||||
</Card>
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
---
|
||||
title: "Tutoriál: Generátor dokumentů"
|
||||
icon: wand-magic-sparkles
|
||||
description: Vytvořte skutečnou aplikaci Twenty, která generuje personalizované dokumenty z vašich CRM dat.
|
||||
---
|
||||
|
||||
V tomto tutoriálu vytvoříte **Generátor dokumentů** — aplikaci, která převádí znovu použitelná šablonová nastavení na personalizované dokumenty pomocí dat, která už máte ve svém CRM.
|
||||
|
||||
Napište šablonu jednou pomocí `{{placeholders}}` a poté vygenerujte vyplněný dokument pro libovolnou osobu nebo společnost jedním kliknutím — z příkazové nabídky, od AI agenta nebo z workflow.
|
||||
|
||||
<Frame caption="Jeden šablonový dokument, vygenerovaný pro konkrétní osobu, otevřený jako stránka připravená k tisku.">
|
||||
<img src="/images/docs/developers/extends/apps/document-generator/07-rendered-document.png" alt="Vygenerovaný dokument obchodního návrhu" />
|
||||
</Frame>
|
||||
|
||||
## Co se naučíte
|
||||
|
||||
Každá kapitola přidá jednu schopnost. Na konci se dotknete většiny SDK.
|
||||
|
||||
| Kapitola | Schopnost | Referenční dokumentace |
|
||||
| ----------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
||||
| [1. Datový model](/l/cs/developers/extend/apps/tutorials/document-generator/data-model) | Objekty, pole a relace | [Data](/l/cs/developers/extend/apps/data/overview) |
|
||||
| [2. Generování dokumentů](/l/cs/developers/extend/apps/tutorials/document-generator/generating-documents) | Logická funkce (nástroj AI + akce workflow), která vyplní šablonu v Markdownu a připojí vyladěné PDF | [Logické funkce](/l/cs/developers/extend/apps/logic/logic-functions) |
|
||||
| [3. HTTP trasy](/l/cs/developers/extend/apps/tutorials/document-generator/http-routes) | Poskytování JSONu a sdílené HTML stránky z tras | [Logické funkce](/l/cs/developers/extend/apps/logic/logic-functions) |
|
||||
| [4. Vytváření uživatelského rozhraní](/l/cs/developers/extend/apps/tutorials/document-generator/building-the-ui) | Zobrazení, navigace, příkazová nabídka a front komponenty, které zobrazují náhled dokumentu a upravují šablonu | [Rozvržení](/l/cs/developers/extend/apps/layout/overview) |
|
||||
| [5. Agent AI](/l/cs/developers/extend/apps/tutorials/document-generator/ai-agent) | Agent + dovednost | [Dovednosti a agenti](/l/cs/developers/extend/apps/logic/skills-and-agents) |
|
||||
| [6. Publikování](/l/cs/developers/extend/apps/tutorials/document-generator/publishing) | Dodejte ho na marketplace | [Publikování](/l/cs/developers/extend/apps/operations/publishing) |
|
||||
|
||||
## Předpoklady
|
||||
|
||||
Měli byste mít hotový [rychlý start](/l/cs/developers/extend/apps/getting-started/quick-start):
|
||||
lokální server Twenty běžící na portu `2020` a CLI k němu přihlášené.
|
||||
|
||||
Pokud ne, nyní si jeden vytvořte a spusťte:
|
||||
|
||||
```bash filename="Terminal"
|
||||
npx create-twenty-app@latest document-generator
|
||||
cd document-generator
|
||||
yarn twenty dev
|
||||
```
|
||||
|
||||
<Note>
|
||||
Raději byste si přečetli hotový kód? Celá aplikace je v
|
||||
[`packages/twenty-apps/examples/document-generator`](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/examples/document-generator).
|
||||
Každý úryvek níže je z ní zkopírovaný.
|
||||
</Note>
|
||||
|
||||
## Jak do sebe aplikace zapadá
|
||||
|
||||
<Frame>
|
||||
<img src="/images/docs/developers/extends/apps/document-generator/how-it-fits.svg" alt="Šablona se zástupnými symboly je převedena na vyladěný dokument s PDF souborem, spuštěná z příkazové nabídky, agenta AI, workflow nebo sdíleného odkazu" />
|
||||
</Frame>
|
||||
|
||||
**Šablonu** napíšete jednou v rich-text editoru pomocí `{{placeholders}}`. Výběr
|
||||
šablony a CRM záznamu vyplní zástupné symboly a uloží vyladěný
|
||||
**dokument** (s PDF souborem). Všechno ostatní — příkazová nabídka, agent AI,
|
||||
krok workflow, sdílený odkaz — je jen jiný způsob, jak spustit ten
|
||||
jeden generátor.
|
||||
|
||||
## Udržujte tento cyklus v chodu
|
||||
|
||||
Nechte `yarn twenty dev` běžet v terminálu po celý návod. Pokaždé, když
|
||||
přidáte nebo upravíte soubor pod `src/`, během několika sekund se znovu synchronizuje s vaším serverem, takže můžete sledovat, jak se každá schopnost objevuje v uživatelském rozhraní, jak ji vytváříte.
|
||||
|
||||
<Card title="Začněte vytvářet →" icon="database" href="/l/cs/developers/extend/apps/tutorials/document-generator/data-model">
|
||||
Kapitola 1: model dokumentů a šablon.
|
||||
</Card>
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
---
|
||||
title: 6. Publikování
|
||||
icon: rocket
|
||||
description: Přidejte metadata tržiště a publikujte svou aplikaci.
|
||||
---
|
||||
|
||||
Vaše aplikace funguje. Posledním krokem je popsat jej pro tržiště a publikovat.
|
||||
|
||||
## Přidat metadata tržiště
|
||||
|
||||
[application config](/l/cs/developers/extend/apps/config/application) nese
|
||||
identitu, která se objeví v tržišti: autor, kategorie, logo a podpora
|
||||
. Vložte logo do `public/` a odkazujte na `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/cs/developers/extend/apps',
|
||||
termsUrl: 'https://www.twenty.com/terms',
|
||||
emailSupport: 'contact@twenty.com',
|
||||
issueReportUrl: 'https://github.com/twentyhq/twenty/issues',
|
||||
});
|
||||
```
|
||||
|
||||
<Tip>
|
||||
Výchozí role je deklarována s `defineApplicationRole()` ve svém vlastním souboru – vy
|
||||
zde již neprojdete `defaultRoleUniversalIdentifier`.
|
||||
</Tip>
|
||||
|
||||
Přidejte také klíčové slovo `dvacet app` do `package.json` tak, aby byla aplikace nalezena:
|
||||
|
||||
```json filename="package.json"
|
||||
{ "keywords": ["twenty-app"] }
|
||||
```
|
||||
|
||||
## Přidat galerii snímků obrazovky
|
||||
|
||||
tržiště se prodává se snímky obrazovky. Přetáhněte několik PNGů do
|
||||
`public/gallery/` a odkazujte na ně `screenshots` — vykreslují se jako galerie
|
||||
na stránce seznamu.
|
||||
|
||||
```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>
|
||||
Lead with the payoff: make the first screenshot the finished result (generated
|
||||
document), then show how it is triggered and authored. (Automatic Copy) Použít křiklavé, vysoké rozlišení
|
||||
snímky — jsou to první věc, kterou uživatel vidí.
|
||||
</Tip>
|
||||
|
||||
Dejte `README.md` stejné ošetření — je to přední stránka na npm a GitHub.
|
||||
Otevřete pomocí nabídky hodnot a snímku obrazovky, vyberte titulky,
|
||||
ponechte podrobnosti sestavení pod složkou.
|
||||
|
||||
## Zkontrolovat před odesláním
|
||||
|
||||
Spustit tytéž brány 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
|
||||
```
|
||||
|
||||
Suchý běh vypíše přesně to, co by se změnilo na serveru bez jeho použití –
|
||||
je dobrá závěrečná kontrola. Viz
|
||||
[Testing](/l/cs/developers/extend/apps/operations/testing) a
|
||||
[Synchronizace a obnovy](/l/cs/developers/extend/apps/operations/sync-and-recovery).
|
||||
|
||||
## Publikovat
|
||||
|
||||
```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` ve výchozím nastavení sestavuje a publikuje do npm; `--private` nahraje
|
||||
tarball do privátního registru 20 serverů. Chcete-li zobrazit publikovanou aplikaci
|
||||
v obchodě instance, spustí synchronizaci katalogu:
|
||||
|
||||
```bash filename="Terminal"
|
||||
yarn twenty dev:catalog-sync -r <remote>
|
||||
```
|
||||
|
||||
Úplné podrobnosti a kontrolní seznam vydání:
|
||||
[Publishing](/l/cs/developers/extend/apps/operations/publishing).
|
||||
|
||||
## Vytvořili jste aplikaci 🎉
|
||||
|
||||
V šesti kapitolách jste použili většinu povrchu SDK:
|
||||
|
||||
* **Objekty, pole a vztahy** modelovat data
|
||||
* **Logická funkce** obklopená jako **nástroj AI**, **akce pracovního postupu** a **cesty HTTP**
|
||||
* **Zobrazení, navigace, příkaz a přední složka** pro UI
|
||||
* **agent + dovednost** pro generování přirozeného jazyka
|
||||
* **Metadata tržiště** a tok zveřejnění
|
||||
|
||||
Dokončená aplikace je na
|
||||
[`packages/twenty-apps/examples/document-generator`](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/examples/document-generator).
|
||||
|
||||
## Kde jít dál
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Odkaz na údaje" icon="database" href="/l/cs/developers/extend/apps/data/overview">
|
||||
Každý typ pole, vztah a index.
|
||||
</Card>
|
||||
<Card title="Logický odkaz" icon="bolt" href="/l/cs/developers/extend/apps/logic/overview">
|
||||
Cron a databázová událost, klíčový obchod, připojení OAuth.
|
||||
</Card>
|
||||
<Card title="Odkaz na rozložení" icon="table-columns" href="/l/cs/developers/extend/apps/layout/overview">
|
||||
Rozvržení stránky, widgety hlavního panelu a další povrchy uživatelského rozhraní.
|
||||
</Card>
|
||||
<Card title="Operace" icon="rocket" href="/l/cs/developers/extend/apps/operations/overview">
|
||||
CLI, testování, dálkové ovládání a CI.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
Reference in New Issue
Block a user