CodeGym /Kurslar /ChatGPT Apps /Şablon necə qurulub: layihə strukturu və əsas fayllar

Şablon necə qurulub: layihə strukturu və əsas fayllar

ChatGPT Apps
Səviyyə , Dərs
Mövcuddur

1. Giriş

ChatGPT App HelloWorld layihəsi — “CodeGym‑in toxunulmaz sehrli qara qutusu” deyil. Bu, sıravi bir Next.js layihəsidir və sadəcə burada eyni anda bir neçə hissə “yaşayır”:

  • ChatGPT daxilində render olunan frontend,
  • alət (tools) çağırışlarına cavab verən MCP serveri,
  • bütün bunları ChatGPT ilə birləşdirən sazlamalar.

Harada nə olduğunu anlamayanda adətən üç klassik ssenari baş verir:

  1. Tərtibatçı təsadüfən server faylında window yazır, düşməni tutur və bütün stekdən nifrət etməyə başlayır.
  2. UI‑a düymə əlavə etmək istəyir, amma səhv page.tsx faylını (məsələn, vidcet deyil, tətbiqin kök səhifəsini) redaktə edir və ChatGPT‑də dəyişiklikləri görmür.
  3. Təsadüfən OPENAI_API_KEY‑i müştəri hissəsinə qoyur və açar brauzerə sızır.

Buna görə də bugünkü məqsəd — xəritəni çəkmək: UI haradadır, MCP haradadır, konfiqlər haradadır və siz aşağıdakıları etmək istəyəndə hara baxmalı:

  • vidcetin görünüşünü dəyişmək;
  • yeni tool əlavə etmək;
  • hansısa platforma sazlamasını (CORS, assetPrefix və s.) tənzimləmək.

2. Layihənin yüksək səviyyəli anatomiyası

ChatGPT App HelloWorld Next.js layihəsi App Router istifadə edir və app/ qovluğu ətrafında təşkil olunub. Orada eyni səhifə ağacında aşağıdakılar yanaşı yerləşir:

  • ChatGPT daxilində render olunacaq vidcetin UI‑ı,
  • tool çağırışlarını emal edəcək MCP endpoint‑i.

Tipik ağac (sadələşdirilmişdir, qovluq adları şablonunuzda fərqli ola bilər, amma pattern eyni qalır):

my-chatgpt-app/
├─ app/
│  ├─ api/                          // REST API
│  │  └─ time/                      // GET /api/time server vaxtını qaytarır
│  │     └─ route.ts
│  ├─ hooks/                        // Rəsmi Apps SDK‑dan hook dəsti
│  │  ├─ use-call-tool.ts
│  │  ├─ use-display-mode.ts
│  │  └─ use-open-external.ts
│  ├─ mcp/                          // MCP server: ChatGPT alətlər (tools) çağıranda bura müraciət edir
│  │  └─ route.ts
│  ├─ globals.css                   // Tətbiqin kök globals.css faylı
│  ├─ layout.tsx                    // Tətbiqin kök layout‑u
│  └─ page.tsx                      // ChatGPT daxilində vidcet səhifəsi
├─ public/                          // Statika: ikonlar, manifest və s.
├─ next.config.ts                   // Next.js konfiqi və Apps‑ə xas sazlamalar (assetPrefix və s.)
├─ proxy.ts                         // iframe daxilində işləmək üçün CORS/başlıqlar (əvvəlki middleware.ts)
├─ package.json                     // Layihə asılılıqları
├─ tsconfig.json                    // TypeScript konfiqurasiyası
└─ .env.local                       // Sirlər: OPENAI_API_KEY və s.

Əgər bir neçə vidcetiniz varsa, adətən onları app/page.tsx‑də yox, app/widget/page.tsx‑də saxlayırlar. Amma məntiq dəyişmir: yenə də bir vidcet səhifəsi və MCP serveri rolunu oynayan bir endpoint var.

Belə düşünmək rahatdır: repozitoriyanız “ikiüzlü Yanus” kimidir:

  • bir “üz” — /mcp yolu: ChatGPT aləti çağırmaq istəyəndə buraya gəlir;
  • digəri — /widget (və ya /) yolu: model UI‑nizi göstərməyə qərar verəndə iframe içində yüklənir.

Qarışıqlıq olmasın deyə, beynimizdə üç fayl qrupunu fiks edək:

  1. UI qatı — React/Next səhifələri ilə bağlı hər şey (app/widget, komponentlər, stillər).
  2. MCP qatıapp/mcp/route.ts və onun istifadə etdiyi fayllar.
  3. “Yapışdırıcı” qat və konfiqlərnext.config.ts, proxy.ts, .env.local, package.json, tsconfig.json.

Bir azdan bu qatların hər birini ayrıca nəzərdən keçirəcəyik.

3. Vidcet harada “yaşayır”: app/widget və/və ya app/page.tsx qovluğu

Ən çox toxunacağınız yerdən başlayaq — vidcet, yəni ChatGPT daxilində görünəcək UI.

Ən aktual layihələrin çoxunda ya:

  • app/widget/page.tsx qovluğu var — vidcet ayrıca /widget prefiksində yaşayır,
  • ya da kök app/page.tsx — vidcet kök səhifə ilə üst‑üstə düşür.

Vidcet olan faylın əsas əlamətləri:

  • ən yuxarıda 'use client' direktivi var, çünki komponent brauzerdə işləyir, window və Apps SDK ilə ünsiyyət qurur;
  • bu, sıravi React komponentidir, markup render edir və (kursun bir az sonra hissəsində) window.openai ilə danışır.

Sadə bir tədris vidceti nümunəsi (çox bənzərini layihənizdə artıq görə bilərsiniz):

// app/widget/page.tsx
'use client';

import React from 'react';

export default function WidgetPage() {
  return (
    <main className="p-4">
      <h1 className="text-xl font-semibold">
        HelloWorld — ChatGPT App
      </h1>
      <p className="text-sm text-gray-500">
        Burada vidcetimizin UI‑sini quracağıq.
      </p>
    </main>
  );
}

Əgər şablonunuzda vidcet birbaşa app/page.tsx daxilindədirsə, kod təxminən eyni olacaq, sadəcə widget ara qovluğu olmadan.

Bir neçə məqama diqqət yetirin.

Birincisi, 'use client' direktivi mütləqdir: vidcet window.openai üzərindən oxuyur/yazır, hadisələrə qulaq asır və s.; bunlar yalnız müştəri komponentində mümkündür. Onu çıxarsanız, Next səhifəni server komponenti kimi işlətməyə çalışacaq və “window is not defined” tipli xətalar alacaqsınız.

İkincisi, bu, tamamilə qeyri‑sehrli, adi bir React komponentidir. Siz:

  • onu components/ içində alt‑komponentlərə bölə bilərsiniz,
  • Tailwind və ya istənilən başqa CSS sistemindən istifadə edə bilərsiniz,
  • kontekstlər, hook‑lar və s. qoşa bilərsiniz.

Üçüncüsü, sonradan məhz burada:

  • real məlumatları göstərmək üçün window.openai.toolInputwindow.openai.toolOutput oxuyacaqsınız,
  • widgetState‑i window.openai.setWidgetState vasitəsilə saxlayacaqsınız,
  • openExternal, callTool və runtime‑ın digər metodlarını çağıracaqsınız.

Hələlik bunu bilmək kifayətdir: vizual interfeysi dəyişmək istəyirsinizsə — demək olar ki, mütləq app/widget/page.tsx və ya app/page.tsx faylına baxın.

4. Kök layout: bütün tətbiq üçün “çərçivə” kimi app/layout.tsx

Növbəti vacib fayl — app/layout.tsx. O:

  • HTML strukturunu təyin edir (<html>, <body>),
  • qlobal stilləri qoşur (globals.css),
  • tez‑tez Apps SDK üçün “bootstrap” qurur (React ilə window.openai arasında əlaqə quran və məlumatları ötürən örtük).

Simplifikasiya olunmuş nümunə:

// app/layout.tsx
import './globals.css';
import type { ReactNode } from 'react';
import { OpenAIAppProvider } from '@/lib/openai-app-provider';

export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="en" suppressHydrationWarning>
      <head>
        <NextChatSDKBootstrap baseUrl={baseURL} />
      </head>
      <body className={`${geistSans.variable} ${geistMono.variable} antialiased h-full overflow-hidden`}>
        {children}
      </body>
    </html>
  );
}

Buradakı NextChatSDKBootstrap adı şərtidir; şablonunuzda bu, OpenAIAppProvider və ya başqa komponent ola bilər. Onun vəzifəsi adətən eynidir: React ağacı ilə Apps SDK runtime‑ı arasında əlaqə qurmaq, qlobal məlumatlara (theme, displayMode, toolInput və s.) abunə olmaq və onları uşaqlara paylamaq.

Praktiki nəticə: qlobal kontekst, stillər və ya UI kitabxanası (məsələn, shadcn/ui) qoşmaq lazımdırsa — bunun yeri demək olar ki, həmişə app/layout.tsx olur (və ya yalnız vidcetə xas sazlama/komponentlər üçün app/widget daxilində layout).

NextChatSDKBootstrap təhlili

NextChatSDKBootstrap‑u Vercel‑in rəsmi şablonunda görmüşəm. Bilmirdinizsə, Next‑i yaradan və inkişaf etdirən elə həmin komanda onlardır. Onların saytında Next üzərində ChatGPT App haqqında yaxşı bir yazı var. Həmçinin Starter Template də mövcuddur. Bəzi məqamlarda bir az köhnəlsə də, düşünürəm ki, aktuallığını qoruyacaqlarına böyük ehtimal var.

NextChatSDKBootstrap bizə verən 5 əsas şeyi seçək:

  • 1. Hidrasiyadakı problemləri düzəldir
    Məsələ ondadır ki, ChatGPT vidcetinizin HTML‑ni əvvəlcə öz serverində yükləyir, təmizləyir və patch edir. Nəticədə hidrasiyanın mexanizmi narazı qalır və konsola xəbərdarlıqlar (Warnings) tökülür. Bu da review‑dan keçməyinizə mane ola bilər.
  • 2. Brauzer tarixçəsini patch edir
    Vidcetiniz iframe daxilində ChatGPT‑də xüsusi bir domendən yüklənir. Öz domeninizi istifadə etsəniz, sandbox‑u poza bilərsiniz. Buna görə brauzerin tarixçəsinə yalnız domen olmadan yol yazılır.
  • 3. fetch() funksiyasını yenidən yazır
    Domen göstərmədən nisbi ünvanlara edilən bütün fetch() çağırışlarınız vidcetdə işləməyəcək, çünki iframe‑in domeni başqadır. Buna görə fetch() funksiyasını elə bir versiya ilə əvəzləyirik ki, domen göstərilməyən sorğuları düzgün URL‑ə göndərsin. Əgər domen göstərilibsə, hər şey dəyişmədən işləyir.
  • 4. Linklərə kliklər işləyir
    Əgər linklər iframe daxilində açılarsa, ChatGPT bunu bəyənməyəcək. Ona görə link kliklərini izləyən və onları xarici pəncərədə openExternal() vasitəsilə açan kod əlavə olunub.
  • 5. head base quraşdırılması (DEPRECATED)
    Bu kod həmçinin <base>‑i <head> daxilinə əlavə edirdi, amma bu artıq işləmir. Sandbox istənilən base quruluşunu sıfırlayır, ona görə skriptlər, resurslar, şriftlər, API və s. üçün mütləq absolut linklərdən istifadə etməyi məsləhət görürəm.

5. MCP server: app/mcp/route.ts

İndi “ikiüzlü Yanus”un ikinci yarısına — MCP vasitəsilə ChatGPT ilə danışan serverə keçiririk.

app/mcp/route.ts faylı — App Router‑ın adi Route Handler‑ıdır və o:

  • ChatGPT‑dən HTTP sorğularını qəbul edir (adətən POST və MCP formatında JSON payload),
  • onları MCP serverinə ötürür (@modelcontextprotocol/sdk və ya nazik bir örtük əsasında),
  • geriyə MCP formatında JSON cavab qaytarır.

İki variant var: MCP SDK ilə “düz TS” yazmaq olar, ya da Next/Vercel‑dən bir neçə hazır sinif ilə küncləri hamarlamaq olar.

Budur, təmiz TS MCP SDK variantı:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

// 1. MCP server yaradırıq
const server = new McpServer({
  name: "simple-mcp-server",
  version: "1.0.0",
});

// 2. MCP Resources qeydiyyatı
// 3. MCP Tools qeydiyyatı

// 4. HTTP nəqliyyatı
const transport = new HttpServerTransport({
  port: 3001,
  path: "/mcp",
});

// 5. Serverin işə salınması
await server.connect(transport);

Amma bir neçə hazır sinifdən istifadə etmək daha rahatdır:

// app/mcp/route.ts
import { NextRequest } from 'next/server';
import { createMcpHandler } from "mcp-handler";

const handler = createMcpHandler(async (server) => {
  const gateway = new McpGateway(server);
  await gateway.initialize();
  gateway.registerResources();
  gateway.registerTools();
});

export const GET = handler;
export const POST = handler;

Burada McpGateway — SDK ilə yaratdığınız (məsələn, lib/mcp/server.ts daxilində) McpServer ətrafında örtük sinifidir. Bizim halda o, tam olaraq app/mcp/route.ts daxilində yerləşir. Gəlin bu faylda nə olduğuna tam baxaq.

type ContentWidget

Faylın əvvəlində ContentWidget tipi təsvir olunub. O, vidcetə aid bütün məlumatları saxlayır və iki yerdə istifadə olunur: vidcet mcp‑resource kimi qeydiyyata alınanda və mcp‑tool metadata qaytaranda — hansı vidcetin qaytardığı məlumatları göstərməli olduğunu göstərir.

type ContentWidget = {
  id: string;            // Unikal ad/key
  title: string;         // Title
  description: string;   // Description
  templateUri: string;   // Vidcet üçün unikal URI, istənilən ola bilər. Heç nəyə təsir etmir.
  invoking: string;      // Yüklənərkən vidcet üzərində görünən yazı
  invoked: string;       // Vidcet yükləndikdən sonra görünən yazı
  html: string;          // Vidcetin bütün HTML kodu
  widgetDomain: string;  // Vidcetin "domen"i. Heç nəyə təsir etmir.
};

class McpGateway

McpServer üzərində örtük sinfi, bəzi şeyləri sadələşdirir. 6 metoddan ibarətdir:

  • initialize() — burada vidcetimizin HTML‑ni yükləyirik
  • registerResources() — vidcetləri mcp‑resources kimi qeydiyyatdan keçiririk
  • registerTools() — funksiyaları mcp‑tools kimi qeydiyyatdan keçiririk
  • widgetMeta() — vidcet metadata qaytarır
  • getAppsSdkCompatibleHtml() — vidcetin HTML‑ni yükləyir və bir az patch edir
  • makeImgUrlsAbsolute() — HTML‑i patch edir: şəkil linklərini absolut edir

Gəlin onları bir az detallı nəzərdən keçirək:

public async initialize()

Bu metod internetdən vidcetlərin HTML kodunu yükləyir və ContentWidget tipində obyekt doldurur.

{
  id: "hello_world",                         // Vidcetin unikal key‑i
  templateUri: "ui://widget/hello_world.html", // Vidcetin unikal URI‑si. "ui:" heç nə demək deyil.
  title: "HelloWorld Widget",               // Vidcetin adı
  description: "Displays the HelloWorld widget", // LLM üçün vidcet nə edir izahı
  invoking: "Loading widget...",            // Yüklənmə zamanı vidcet üzərində yazı
  invoked: "Widget loaded",                 // Yükləndikdən sonra vidcet üzərində yazı
  html: htmlWidget,                         // Vidcetin HTML‑i
  widgetDomain: baseURL,                    // Vidcetin "domen"i. Hazırda heç nəyə təsir etmir.
}

public registerResources()

Vidcetləri mcp‑resources kimi qeydiyyatdan keçirir. server.registerResource() metodunu çağırır, ona 4 parametr ötürülür:

  • MCP resursunun id/key‑i
  • Resursun URI‑si (bu MCP protokolu üçündür, vidcet üçün faktiki olaraq unikal ünvan sinonimidir)
  • MCP resursunun metadata‑sı
  • MCP resursunu qaytaran funksiya

Vidcet metadata‑sı

{
  title: widget.title,                 // Resurs/vidcet adı
  description: widget.description,     // Resurs/vidcet təsviri
  mimeType: "text/html+skybridge",     // Vacibdir! Yalnız bu tip html vidcet kimi göstərilir
  _meta: {
    "openai/widgetDescription": widget.description, // Vidcet təsviri
    "openai/widgetPrefersBorder": true,            // ChatGPT‑dən vidcet üçün çərçivə çəkməsini xahiş edirik
  },
}

MCP resursu kimi vidcet

{
  uri: uri.href,                        // Bizim URI (parametr uri‑dən götürülür)
  mimeType: "text/html+skybridge",      // Vacibdir! Yalnız bu tip html vidcet kimi göstərilir
  text: widget.html,                    // Vidcetin HTML‑i
  _meta: {
    "openai/widgetDescription": widget.description, // Vidcet təsviri
    "openai/widgetPrefersBorder": true,            // Vidcet üçün çərçivə
    "openai/widgetDomain": widget.widgetDomain,    // Vidcetin "domen"i. Hazırda heç nəyə təsir etmir.
    "openai/widgetCSP": {                          // Vacibdir! Vidcet üçün əlçatan domenlər:
      connect_domains: [                           // Qoşulmalar üçün domenlər (fetch və s.)
        baseURL,
        "https://codegym.cc",
      ],
      resource_domains: [                          // Resurslar üçün domenlər (css/fonts/img)
        baseURL,
        "https://codegym.cc",
        "https://cdn.tailwindcss.com",
        "https://persistent.oaistatic.com",
        "https://fonts.googleapis.com",
        "https://fonts.gstatic.com"
      ]
    }
  },
}

Gələcəkdə openai/widgetCSP haqqında yenə danışacağıq, amma indi onunla bağlı 2 məqamı qeyd edim:

  • connect_domains — aşağıdakılar üçün domenlərin siyahısı:
    • fetch()
    • skriptlərin yüklənməsi
    • openExternal()
  • resource_domains — aşağıdakılar üçün domenlərin siyahısı:
    • şəkillər
    • CSS
    • şriftlər

Nəzəri cəhətdən 200 domen yaza bilərsiniz, amma bu siyahı ilə review‑dan keçə biləcəksinizmi — böyük sualdır.

Həmçinin artıq dərc olunmuş tətbiqlərdəki bu parametrləri araşdırdım və amplitude.com‑u da gördüm. Bu da pozitiv xəbərdir. Düşünürəm ki, yaxşı analitika heç kimə zərər verməz.

public registerTools()

Funksiyaları mcp‑tools kimi qeydiyyatdan keçirir. server.registerTool() metodunu çağırır, 3 parametr ötürülür:

  • MCP tool üçün id/key
  • MCP tool metadata‑sı
  • MCP tool qaytaran funksiya

Alətin metadata‑sı

Bu siyahıdakı bütün parametrlər vacibdir. Onlar haqqında növbəti mühazirələrdə daha detallı danışacağam.

{
  title: widget.title,                               // Alətin adı
  description: "Returns HelloWorld widget",          // Vacibdir! Alət nə edir izahı
  inputSchema: z.object({}).describe("No inputs"),   // Alət parametrlərinin sxemi. Zod ola bilər
  _meta: this.widgetMeta(widget),                    // Vidcet metadata‑sı: hansı vidcet göstərilsin
  annotations: {
    destructiveHint: false,                          // Metod nəyisə vacib dəyişir - confirm lazımdır
    openWorldHint: false,                            // Metod üçüncü tərəf servislərdə nəyisə dəyişir
    readOnlyHint: true                               // Metod heç nəyi dəyişmir
  },
}

Vacib iş görən funksiya

async (input, extra) => {
  // 1. Parametrlərin validasiyası
  // 2. Vacib bir iş görürük
  return {
    content: [{ type: "text", text: "HelloWorld MCP-tool" }], // Nəticənin İİ üçün təsviri
    structuredContent: {                                      // Vacibdir! Məhz bu nəticənin JSON‑udur.
      timestamp: new Date().toISOString()                     // İstənilən məlumatları ehtiva edə bilər.
    },
    _meta: this.widgetMeta(widget),                           // JSON‑u göstərən vidcetin metadata‑sı
  };                                                          // Ola da bilər, olmaya da - vidcet olmayacaqsa
}

private widgetMeta(widget: ContentWidget)

Vidcet metadata‑sını qaytarır — ChatGPT bu məlumatlara görə JSON nəticəni göstərmək üçün hansı vidceti istifadə edəcəyini müəyyən edir.

{
  "openai/outputTemplate": widget.templateUri,            // Vidcetin URI‑si
  "openai/toolInvocation/invoking": widget.invoking,      // Yüklənənədək vidcet üzərində yazı
  "openai/toolInvocation/invoked": widget.invoked,        // Yükləndikdən sonra vidcet üzərində yazı
  "openai/widgetAccessible": true,                        // MCP‑tool vidcetdən çağırıla bilər
  "openai/resultCanProduceWidget": true,                  // MCP‑tool vidcet qaytaracaq
}

Ayrı‑ayrılıqda "openai/outputTemplate" kimi sadə bir şeyi müzakirə etmək istərdim. MCP protokolunda 3 entitet var (6‑cı modulda daha ətraflı öyrənəcəksiniz):

  • MCP Resources
  • MCP Templates
  • MCP Tools

Belə ki, bu "openai/outputTemplate" heç bir əlaqəsi yoxdur MCP Templates ilə. MCP Templates ümumiyyətlə ChatGPT Apps‑də istifadə olunmur. Buradakı “template” sözü isə belə yaranıb:

Vidcetlər JSON‑u göstərmək üçün bir şablon kimi düşünülmüşdü. MCP tool müəyyən bir JSON qaytardı, İİ vidceti göstərdi, JSON‑u ToolOutput parametri ilə ona verdi və vidcet bu JSON‑u səliqəli göstərdi. outputTemplate — sadəcə vidcetin sinonimidir.

Düşünürəm ki, bu qədər. Bu məsələləri 4‑cü modulda daha detallı açacağıq: alətləri necə təsvir etməli, JSON Schema və emalçılar necə olmalıdır və s. İndilik bunu başa düşmək kifayətdir: əgər nəsə alətlərlə (tools) və məntiq ilə bağlıdırsa — app/mcp/route.ts ətrafına baxın.

6. Konfiqurasiya və “yapışdırıcı qat”: next.config.ts, middleware.ts, .env və dostları

İndi isə əsas fayllar dəstini nəzərdən keçirək ki, Next.js layihəniz iframe daxilində ChatGPT ilə düzgün işləsin və ChatGPT tərəfindən HTTPS tuneli (ngrok, Cloudflare Tunnel və s.; tunellər barədə ayrıca danışacağıq) vasitəsilə əlçatan olsun.

next.config.ts

Bu faylda, Next.js‑in standart sazlamalarından əlavə, tez‑tez aşağıdakılar konfiqurasiya olunur:

  • assetPrefix — statikanın (JS, /_next/‑dən gələn CSS) ChatGPT domenindən yox, sizin dev URL‑dən (tuneldən və ya Vercel‑dən) düzgün yüklənməsi üçün;
  • şablona lazım olan xüsusi sazlamalar (məsələn, Next 16 üçün eksperimental flag‑lər).

Praktikada bu, lazım olan sahələri olan nextConfig obyektinin exportu kimi görünür. Bu mühazirə üçün bir vacib fikir: əgər ChatGPT‑də vidcet CSS/JS yükləyə bilmirsə, çox vaxt günahkar assetPrefix olur.

proxy.ts (əvvəlki middleware.ts)

Bu fayl ChatGPT‑dən gələn sorğu ilə routelarınız arasında middleware qatı əlavə edir. Şablonda adətən o:

  • iframe daxilindəki ChatGPT‑nin serverinizə müraciət etmək hüququ olsun deyə CORS başlıqlarını qoyur;
  • bəzən React Server Components üçün əlavə başlıqlar qurur.

Bütün incəlikləri indi bilmək şərt deyil. Sadəcə yadınızda saxlayın: əgər ChatGPT CORS barədə şikayət edirsə və ya DevTools‑da qəribə giriş qadağaları görürsünüzsə, proxy.ts faylına baxın.

.env

.env (və ya .env.local) faylı — sirlər və mühit parametrləri üçündür:

  • OPENAI_API_KEY (əgər MCP serveriniz OpenAI API‑yə özü gedirsə),
  • daxili API ünvanlarınız,
  • üçüncü tərəf servislərin tokenləri və s.

Vacib nüans: Next.js‑də NEXT_PUBLIC_ ilə başlayan dəyişənlər avtomatik JS bundle‑a düşür və brauzerdə əlçatan olur. OPENAI_API_KEY ilə bunu əsla etməyin; sirlər yalnız server dəyişənləri olmalıdır.

package.jsontsconfig.json

package.json daxilində görəcəksiniz:

  • Next.js, React, Apps SDK, MCP SDK və digər asılılıqların versiyalarını;
  • dev, build, start skriptlərini, bəzən də köməkçi komandaları (linter, formatlayıcı və s.).

tsconfig.json daxilində isə TypeScript üçün tanış sazlamalar olur:

  • alyas yolları (@/lib, @/components),
  • strict rejimi,
  • kompilyasiya target‑ləri.

Bu kurs baxımından əsas — şablonun adi TypeScript stack‑indən istifadə etdiyini və onu standart şəkildə genişləndirə biləcəyinizi anlamaqdır.

7. Tərtibatçı üçün sürətli “layihə naviqatoru”

Gəlin, tipik işləri görmək istəyəndə hara getməli olduğunuzu fiks edək. Siyahısız, mini ssenarilər şəklində.

Vidcetdə mətn/düymələri dəyişmək istəyirsinizsə, vidcet UI faylını açırsınız: bu ya app/widget/page.tsx, ya da app/page.tsx olacaq — şablondan asılıdır. Orada JSX‑i düzəldir, yeni komponentlər əlavə edir, dizayn sistemini qoşursunuz. Məhz burada Apps SDK runtime‑ını (window.openai və ya rahat hook‑lar) məlumatları göstərmək üçün istifadə edəcəksiniz.

Serverdə nəsə edən yeni düymə əlavə etmək lazımdırsa, yenə UI faylından başlayırsınız. Vidcetdəki düymə klikdə window.openai.callTool çağıracaq, alətin reallaşdırmasını isə MCP server konfiqurasiyasına — yəni app/mcp/route.ts ətrafındakı koda əlavə edəcəksiniz. UI ↔ tool məntiqi əlaqəsini 4‑cü moduldan etibarən ətraflı açacağıq.

ChatGPT‑yə yeni funksionallıq öyrətmək istəyəndə (məsələn, “turların axtarışı” və ya “məhsul seçimi”), MCP qatına gedirsiniz (app/mcp/route.ts‑dən import olunan fayllar). Orada JSON Schema, təsvir və emalçı ilə yeni tool qeydiyyatdan keçirirsiniz. Vidcet isə nəticəni window.openai.toolOutput vasitəsilə oxuyub səliqəli göstərə bilər.

Statika “uçub” və ya vidcet yalnız ChatGPT‑də qəribə göstərilir, lokalda isə hər şey qaydasındadırsa, “yapışdırıcı” qatı xatırlayırıq. İlk növbədə next.config.ts‑i (xüsusən assetPrefix) və middleware.ts/proxy.ts‑i (CORS) yoxlayın. Yaxınlarda tuneli, URL‑i dəyişmisinizsə və ya Vercel‑ə deploy etmisinizsə, bu sazlamaların dəqiqliyi kritiktir.

Nəhayət, açar və ya mühit problemlərindən şübhələnirsinizsə, sizin üç faylınız — .env.local, package.json (həqiqətən hansı asılılıqlar və skriptlər istifadə olunur, bunu anlamaq üçün) və dev serverin loglarıdır. Məhz bu üçlük MCP‑nin lazımi sirlərə və servislərə çıxışı olmasına cavabdehdir.

8. Mini praktika: fayl sistemi ilə tanışlıq

Teoriya yaxşıdır, amma gəlin əllə harada nə olduğunu fiks edək. Bu addımları birbaşa editor/IDE‑də indi edə bilərsiniz.

Layihənizdə app qovluğunu açın və hansı faylın vidcetə cavabdeh olduğunu tapmağa çalışın. Əgər şablonda app/page.tsx istifadə olunursa, elə orada “HelloWorld — ChatGPT App” və ya salamlayıcı mətni görəcəksiniz. Əgər vidcet ayrıca qovluq kimi yoxdursa, app/page.tsx faylını açın və 'use client' ilə hansısa JSX markup‑ın olduğuna əmin olun.

Sonra app/mcp/route.ts faylına baxın. O, hansı modulları import edir — diqqət edin: adətən ya birbaşa MCP SDK istifadə olunur, ya da lib/mcp/* içindən köməkçi funksiya çağırılır. Bu araqatın nə qədər “nazik” hazırlandığına baxın — idealda orada demək olar ki, heç bir biznes məntiqi olmamalıdır, sadəcə “JSON qəbul et → serverə ötür → JSON qaytar”.

Bundan sonra next.config.tsproxy.ts/middleware.ts fayllarına nəzər salın. Oradakı hər şeyi başa düşməyiniz lazım deyil, sadəcə fiks edin ki:

  • next.config.ts Next konfiqurasiyasına, o cümlədən assetlərin build və verilmə qaydalarına cavabdehdir;
  • proxy.ts HTTP sorğularına müdaxilə edir (orada başlıqla bağlı işləmələr görəcəyiniz çox gümandır).

Və sonda .env və ya .env.local faylını açın və açarların məhz orada saxlandığına, kodda olmadığına əmin olun. Haradasa NEXT_PUBLIC_OPENAI_API_KEY görsəniz — bu, hələ lokal inkişaf mərhələsindəykən düzəltmək üçün əla fürsətdir.

9. Vizual sxem: ChatGPT şablonunuzla necə qarşılıqlı fəaliyyət göstərir

Ümumi şəkil tamamlansın deyə, sadə bir axına baxmaq faydalıdır:

flowchart TD
    U[İstifadəçi ChatGPT‑də] -->|Sorğu yazır| M[ChatGPT modeli]

    M -->|Aləti çağırır| MCP["Sizin MCP endpoint‑iniz
app/mcp/route.ts"] MCP -->|"MCP‑nin JSON cavabı (structuredContent, _meta, UI linki)"| M M -->|UI göstərməyə qərar verir| WIDGET_URL["Vidcetin URL‑i
(/widget və ya /)"] WIDGET_URL -->|iframe| W[Sizin vidcetiniz
app/page.tsx] W -->|window.openai.toolOutput
+ widgetState oxuyur| U

Burada vacibdir ki, təşəbbüskar demək olar ki, həmişə ChatGPT modelidir, klassik veb tətbiqində olduğu kimi istifadəçi brauzeri deyil. app/mcp/route.tsapp/widget/page.tsx — eyni Next.js layihəsində iki fərqli “qapıdır”: biri robot (MCP) üçün, digəri isə UI üçün.

Bu layihə xəritəsini (vidcet → MCP qatı → konfiqlər) yadda saxlayıb sadalanan “mina”lardan şüurlu şəkildə yayınsanız, kursun növbəti hissələrində artıq App‑ınızın məntiqi və UX‑inə fokuslana biləcəksiniz, “hər şeyi sındıran o faylı” axtarmağa yox.

10. Şablon strukturu ilə işləyərkən tipik səhvlər

Səhv №1: Vidceti saytın adi səhifəsi ilə qarışdırmaq.
Bəzən tərtibatçı şablonda həm app/page.tsx, həm də app/widget/page.tsx görür, “səhv” faylı redaktə edir və dəyişikliklərin ChatGPT‑də niyə görünmədiyinə təəccüblənir. Vidcet — məhz MCP alətinin outputTemplate/iframe kimi istifadə etdiyi səhifədir. Başqa route‑u dəyişsəniz, ChatGPT bundan heç xəbər tutmayacaq. Həmişə şablonun README‑sini yoxlayın və vidcet kimi hansı URL‑in göstərildiyinə baxın.

Səhv №2: MCP‑nin server fayllarında müştəri kodu (window, document) yazmaq.
app/mcp/route.ts faylı və onun import etdiyi hər şey serverdə icra olunur. Orada window və ya DOM API istifadə etmək cəhdləri runtime‑ın düşməsinə səbəb olacaq. UI‑da nəsə etmək istəyirsinizsə, bu demək olar ki, app/widget altındakı fayllarda və ya digər müştəri komponentlərində olmalıdır. MCP qatı — saf backend‑dir: sorğular, bazalar, xarici API‑lər və strukturlu cavabın formalaşdırılması.

Səhv №3: assetPrefix və CORS sazlamalarını görməzlikdən gəlmək.
Lokal localhost:3000‑da hər şey əladır, amma ChatGPT vasitəsilə tunel üzərindən App‑ı açanda — stillər yox olur, JS yüklənmir, konsolda bir yığın CORS xətası görünür. Çox vaxt səbəb next.config.ts konfiqurasiyasının və ya middleware.ts/proxy.ts fayllarının yeni publik URL‑i nəzərə almaması və ya refaktor zamanı təsadüfən pozulmasıdır. Bu faylları dəyişəndə unutmayın ki, kodunuz iframe daxilində ChatGPT domenində yaşayacaq, birbaşa localhostda yox.

Səhv №4: Sirləri .env yerinə birbaşa kodda və ya NEXT_PUBLIC_* dəyişənlərində saxlamaq.
OPENAI_API_KEY‑i hardasa app/widget/page.tsx daxilində const apiKey = 'sk-...' şəklində gizlətmək ən pis fikirdir: açar JS bundle‑a düşəcək və istənilən istifadəçiyə gedəcək. Demək olar ki, eyni dərəcədə pis — NEXT_PUBLIC_OPENAI_API_KEY yaratmaqdır, çünki NEXT_PUBLIC_ prefiksi brauzerə düşməyi təmin edir. Sirləri həmişə bu prefikssiz .env faylına qoyun və yalnız server tərəfdə (MCP server, backend funksiyaları) istifadə edin.

Səhv №5: Şablonu “həddindən artıq ağıllı” sayıb ona toxunmaqdan çəkinmək.
Bəzən tərtibatçılar rəsmi starter‑ə müqəddəs bir şey kimi yanaşırlar: “dəyib toxunmasam yaxşıdır, inteqrasiyanı sındıra bilərəm”. Nəticədə bütün kodlarını kənarda yazır, arxitekturanı mürəkkəbləşdirir və yenə eyni “minalara” düşürlər. Əslində şablon — Apps SDK üçün bir‑iki sazlama ilə səliqəli yığılmış Next.js layihəsidir. app/‑ın UI və MCP olduğunu, qalanının isə adi konfiqlər olduğunu anlamaq çox azad edir: artıq kodla tanış React/Next layihəsi kimi işləyirsiniz, sehrli qutu kimi yox.

Səhv №6: Bütün problemləri “vidcet səviyyəsində” həll etməyə çalışmaq.
Bəzən UI‑da hər şeyi etmək istəyirlər: həm biznes məntiqini, həm bazalara çıxışı, həm də xarici API sorğularını. ChatGPT Apps kontekstində bu xüsusilə pis fikirdir: vidcet çox sərt sandbox‑da yaşayır, sirlərinizi görmür və çox dərəcədə window.openai‑dən asılıdır. Əgər ciddi bir şey lazımdırsa — yeri MCP qatı və backend servisləridir; vidcet isə incə təqdimat qatı olmalı, strukturlu məlumatı göstərməli və lazım gələrsə alətləri işə salmalıdır.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION