@@ -16,7 +16,6 @@ import {
1616import "@copilotkit/react-ui/styles.css" ;
1717import { useRouter } from "next/router" ;
1818import { useCallback , useEffect , useMemo , useState } from "react" ;
19- import { fetchAllGithubContent } from "../utils/fetchGithubContent" ;
2019import {
2120 NavigationCard ,
2221 ProductSelector ,
@@ -165,8 +164,6 @@ routes from the PAGE ROUTE MAP above. NEVER invent routes that are not in the ma
165164// ── Inner component (has access to CopilotKit context) ──────────────────
166165function AppWithContext ( { children } ) {
167166 const router = useRouter ( ) ;
168- const enableGithubContext =
169- process . env . NEXT_PUBLIC_ENABLE_GITHUB_CONTEXT === "true" ;
170167
171168 // ─ state ─────────────────────────────────────────────────────────────
172169 const [ docsCtx , setDocsCtx ] = useState ( null ) ; // parsed docs-context.json
@@ -219,11 +216,38 @@ function AppWithContext({ children }) {
219216 return `## ${ currentPage . title } (CURRENT PAGE)\nURL: ${ currentPage . url } \n\n${ currentPage . content } ` ;
220217 } , [ currentPage ] ) ;
221218
222- // Extract headings from current page for suggestion generation
223- const currentPageHeadings = useMemo ( ( ) => {
224- if ( ! currentPage ?. content ) return [ ] ;
225- const matches = currentPage . content . match ( / ^ # { 1 , 3 } \s + .+ $ / gm) || [ ] ;
226- return matches . map ( ( h ) => h . replace ( / ^ # + \s + / , "" ) ) . slice ( 0 , 8 ) ;
219+ const currentPageRelatedPages = useMemo ( ( ) => {
220+ const route = currentPage ?. route || "/" ;
221+
222+ if ( route === "/" ) {
223+ return [
224+ { route : "/plan-your-integration" , title : "Plan Your Integration" } ,
225+ { route : "/android-terminals/overview" , title : "Android Overview" } ,
226+ { route : "/linux-terminals/getting-started" , title : "Linux Getting Started" } ,
227+ ] ;
228+ }
229+
230+ if ( route . startsWith ( "/android-terminals" ) ) {
231+ return [
232+ { route : "/android-terminals/overview" , title : "Android Overview" } ,
233+ { route : "/android-terminals/set-up-integration" , title : "Set Up Integration" } ,
234+ { route : "/android-terminals/accept-card-payment" , title : "Accept Card Payment" } ,
235+ ] ;
236+ }
237+
238+ if ( route . startsWith ( "/linux-terminals" ) ) {
239+ return [
240+ { route : "/linux-terminals/getting-started" , title : "Linux Getting Started" } ,
241+ { route : "/linux-terminals/transaction-flow" , title : "Transaction Flow" } ,
242+ { route : "/linux-terminals/best-practices" , title : "Best Practices" } ,
243+ ] ;
244+ }
245+
246+ return [
247+ { route : "/plan-your-integration" , title : "Plan Your Integration" } ,
248+ { route : "/how-terminal-works" , title : "How Terminal Works" } ,
249+ { route : "/linux-terminals/getting-started" , title : "Linux Getting Started" } ,
250+ ] ;
227251 } , [ currentPage ] ) ;
228252
229253 // ─ 1. Current page — highest priority ────────────────────────────────
@@ -241,23 +265,7 @@ function AppWithContext({ children }) {
241265 value : docsIndexText || "Loading documentation index…" ,
242266 } ) ;
243267
244- // ─ 3. GitHub repo content — opt-in only because it is expensive to load ─
245- useCopilotReadable (
246- enableGithubContext
247- ? {
248- description :
249- "GITHUB REPOSITORY CONTENT — READMEs and key files from official Dspread repos. " +
250- "Use exact code from this content when users ask about source code." ,
251- value : "GitHub context is enabled, but loaded only when the feature flag is on." ,
252- }
253- : {
254- description :
255- "GITHUB REPOSITORY CONTENT is disabled by default to reduce runtime pressure." ,
256- value : "GitHub context disabled" ,
257- }
258- ) ;
259-
260- // ─ 4. Product model → category mapping (always available) ────────────
268+ // ─ 3. Product model → category mapping (always available) ────────────
261269 useCopilotReadable ( {
262270 description :
263271 "PRODUCT MODEL MAPPING — use this to identify which documentation section applies to the user's terminal model." ,
@@ -285,21 +293,16 @@ Key differences:
285293 [ router ] ,
286294 ) ;
287295
288- // ─ Navigation action — AI calls this to jump to a docs page ──────────
289- // Now with AGENTIC UI: renders a styled card inline in the chat.
290296 useCopilotAction ( {
291297 name : "navigateToPage" ,
292298 description :
293299 "Navigate the user's browser to a specific documentation page. " +
294- "Call this EVERY TIME your answer relates to a specific page. " +
295- "ONLY use routes from the PAGE ROUTE MAP. Valid routes: " +
296- VALID_ROUTES . join ( ", " ) ,
300+ "Use this whenever the answer relates to a specific docs route." ,
297301 parameters : [
298302 {
299303 name : "route" ,
300304 type : "string" ,
301- description :
302- 'The page route to navigate to. MUST be one of the valid routes from the PAGE ROUTE MAP. Example: "/android-terminals/accept-card-payment"' ,
305+ description : "The page route to navigate to." ,
303306 required : true ,
304307 enum : VALID_ROUTES ,
305308 } ,
@@ -310,24 +313,6 @@ Key differences:
310313 required : true ,
311314 } ,
312315 ] ,
313- handler : ( { route, pageTitle } ) => {
314- // Normalize: strip trailing slash for comparison
315- const normalized = ( route || "" ) . replace ( / \/ + $ / , "" ) || "/" ;
316- if ( ! VALID_ROUTES . includes ( normalized ) ) {
317- // Find closest match
318- const match = VALID_ROUTES . find ( ( r ) =>
319- r . includes ( normalized . split ( "/" ) . pop ( ) )
320- ) ;
321- if ( match ) {
322- router . push ( match ) ;
323- return `Navigated to "${ pageTitle } " (${ match } ) [auto-corrected from ${ route } ]` ;
324- }
325- return `Invalid route "${ route } ". Valid routes: ${ VALID_ROUTES . join ( ", " ) } ` ;
326- }
327- router . push ( normalized ) ;
328- return `Navigated to "${ pageTitle } " (${ normalized } )` ;
329- } ,
330- // ── Agentic UI: show navigation card in chat ──
331316 render : ( { args, status } ) => (
332317 < NavigationCard
333318 route = { args ?. route || "/" }
@@ -337,93 +322,54 @@ Key differences:
337322 ) ,
338323 } ) ;
339324
340- // ─ Product selector action — interactive product type picker ──────────
341- // Uses renderAndWaitForResponse: the AI pauses while user picks a product.
342325 useCopilotAction ( {
343326 name : "selectProductType" ,
344327 description :
345- "Show an interactive product type selector card. Call this when you need " +
346- "the user to choose their product type (Smart POS, mPOS, Linux, Cloud Speaker) " +
347- "and you want to present a visual picker instead of asking via text. " +
348- "ONLY call this once at the start of a conversation when the product type is unknown." ,
328+ "Show the interactive product selector when the user's device type is unknown." ,
349329 parameters : [ ] ,
350330 renderAndWaitForResponse : ( { respond, status } ) => (
351331 < ProductSelector respond = { respond } status = { status } />
352332 ) ,
353333 } ) ;
354334
355- // ─ Show related pages action — displays a grid of related pages ───────
356335 useCopilotAction ( {
357336 name : "showRelatedPages" ,
358337 description :
359- "Show a card with related documentation pages. Call this when you want to " +
360- "recommend multiple pages to the user, for example at the end of an answer. " +
361- "Pass an array of pages with route and title." ,
362- parameters : [
363- {
364- name : "pages" ,
365- type : "object[]" ,
366- description : "Array of related pages to display" ,
367- attributes : [
368- {
369- name : "route" ,
370- type : "string" ,
371- description : "Page route (must be a valid route from PAGE ROUTE MAP)" ,
372- required : true ,
373- } ,
374- {
375- name : "title" ,
376- type : "string" ,
377- description : "Human-readable page title" ,
378- required : true ,
379- } ,
380- ] ,
381- required : true ,
382- } ,
383- ] ,
384- handler : ( { pages } ) => {
385- return `Showing ${ ( pages || [ ] ) . length } related pages.` ;
386- } ,
387- render : ( { args, status } ) => (
338+ "Show a lightweight card with related documentation pages." ,
339+ parameters : [ ] ,
340+ render : ( { status } ) => (
388341 < RelatedPagesCard
389- pages = { args ?. pages || [ ] }
342+ pages = { currentPageRelatedPages }
390343 status = { status }
391344 onNavigate = { handleNavigate }
392345 />
393346 ) ,
394347 } ) ;
395348
396- // ─ Progress/guide action — shows step-by-step integration guide ───────
397349 useCopilotAction ( {
398350 name : "showIntegrationGuide" ,
399351 description :
400- "Show a step-by-step progress card for integration guidance. " +
401- "Use this when walking a user through a multi-step process like " +
402- "SDK setup, payment integration, or certification. " +
403- "Provide the list of steps and the current step index (0-based)." ,
352+ "Show a simple step-by-step progress card for multi-step documentation flows." ,
404353 parameters : [
405354 {
406355 name : "title" ,
407356 type : "string" ,
408- description : "Title of the integration guide. E.g. 'Android SDK Setup' " ,
357+ description : "Guide title. " ,
409358 required : true ,
410359 } ,
411360 {
412361 name : "steps" ,
413362 type : "string[]" ,
414- description : "List of step descriptions in order ." ,
363+ description : "Ordered list of steps ." ,
415364 required : true ,
416365 } ,
417366 {
418367 name : "currentStep" ,
419368 type : "number" ,
420- description : "The index of the current step being worked on (0-based) ." ,
369+ description : "Zero-based step index ." ,
421370 required : true ,
422371 } ,
423372 ] ,
424- handler : ( { title, steps, currentStep } ) => {
425- return `Integration guide: "${ title } " — step ${ currentStep + 1 } of ${ ( steps || [ ] ) . length } ` ;
426- } ,
427373 render : ( { args, status } ) => (
428374 < ProgressCard
429375 title = { args ?. title || "Guide" }
@@ -434,118 +380,36 @@ Key differences:
434380 ) ,
435381 } ) ;
436382
437- // ─ Dynamic suggestion instructions based on current page ──────────────
438- const suggestionInstructions = useMemo ( ( ) => {
439- const pageTitle = currentPage ?. title || "Overview" ;
440- const route = currentPage ?. route || "/" ;
441- const headings = currentPageHeadings . length > 0
442- ? `\nThis page has these sections: ${ currentPageHeadings . join ( ", " ) } `
443- : "" ;
444-
445- // Determine which sibling/related pages to suggest
446- let relatedSuggestions = "" ;
447- if ( route === "/" || ! currentPage ) {
448- relatedSuggestions = `
449- Suggest these topics (pick 3-5):
450- - "I have a Smart POS (D30/D60), how do I start?"
451- - "How do I set up a Linux terminal?"
452- - "What's the difference between Smart POS and mPOS?"
453- - "How do I integrate with a payment gateway?"
454- - "I need help with EMV L3 certification"
455- - "How does the Cloud Speaker work?"` ;
456- } else if ( route . startsWith ( "/android-terminals" ) ) {
457- relatedSuggestions = `
458- The user is reading Android terminal docs. Suggest questions about:
459- - Accepting card payments (if not on that page)
460- - Printing receipts (if not on that page)
461- - Scanning QR/Bar codes (if not on that page)
462- - Setting up the SDK (if not on that page)
463- - Customizing the OS
464- Also suggest 1-2 questions SPECIFIC to this page's content based on the headings.` ;
465- } else if ( route . startsWith ( "/linux-terminals" ) ) {
466- relatedSuggestions = `
467- The user is reading Linux terminal docs. Suggest questions about:
468- - Getting started with Linux SDK (if not on that page)
469- - Transaction flow details (if not on that page)
470- - Common issues and troubleshooting
471- - Best practices
472- Also suggest 1-2 questions SPECIFIC to this page's content based on the headings.` ;
473- } else if ( route === "/cloud-speaker" ) {
474- relatedSuggestions = `
475- The user is reading Cloud Speaker docs. Suggest questions about:
476- - How to compile and build firmware
477- - OTA update process
478- - Device type configuration
479- - How to set up the development environment` ;
480- } else if ( route . includes ( "payment-gateway" ) || route . includes ( "key-management" ) ) {
481- relatedSuggestions = `
482- The user is reading payment/encryption docs. Suggest questions about:
483- - Decrypting POS terminal data with AWS
484- - DUKPT key management
485- - TR-31 key export/import
486- - How to send encrypted data from the terminal` ;
487- } else if ( route === "/emv-l3-testing" ) {
488- relatedSuggestions = `
489- The user is reading EMV L3 testing docs. Suggest questions about:
490- - Which countries are supported for L3 certification
491- - How to download test configurations for a specific country
492- - What terminal models are supported
493- - Firmware download for certification` ;
494- } else {
495- relatedSuggestions = `
496- Suggest 3-5 questions relevant to "${ pageTitle } " based on the page content and headings.` ;
497- }
498-
499- return `The user is currently on the "${ pageTitle } " page (route: ${ route } ).${ headings }
500-
501- Generate 3-5 short suggestion buttons (under 50 characters each) that are SPECIFIC to this page's content.
502-
503- IMPORTANT: When the user clicks a suggestion, your response MUST:
504- 1. Answer the question using content from the documentation
505- 2. Call the navigateToPage action if the answer relates to a different page
506- ${ relatedSuggestions }
507-
508- Make sure each suggestion is a natural question a developer would ask while reading this specific page.` ;
509- } , [ currentPage , currentPageHeadings ] ) ;
510-
511- // ─ Welcome suggestions — static product categories (before first message) ─
512383 useCopilotChatSuggestions ( {
513384 suggestions : [
514385 {
515386 title : "📱 Smart POS (Android)" ,
516- message : "I'm using a Smart POS terminal (D20, D30, D50, D60, D70, D80, D80K). Help me get started with Android SDK integration." ,
387+ message :
388+ "I'm using a Smart POS terminal (D20, D30, D50, D60, D70, D80, D80K). Help me get started with Android SDK integration." ,
517389 className : "welcome-product-suggestion welcome-smartpos" ,
518390 } ,
519391 {
520392 title : "📲 mPOS / Mobile Reader" ,
521- message : "I'm using an mPOS mobile reader (QPOS mini, QPOS Cute, CR100, QPOS Plus). Help me connect it to my app." ,
393+ message :
394+ "I'm using an mPOS mobile reader (QPOS mini, QPOS Cute, CR100, QPOS Plus). Help me connect it to my app." ,
522395 className : "welcome-product-suggestion welcome-mpos" ,
523396 } ,
524397 {
525398 title : "🐧 Linux Terminal" ,
526- message : "I'm using a Linux terminal (D30-linux, QPOS-linux). Help me set up the Linux SDK." ,
399+ message :
400+ "I'm using a Linux terminal (D30-linux, QPOS-linux). Help me set up the Linux SDK." ,
527401 className : "welcome-product-suggestion welcome-linux" ,
528402 } ,
529403 {
530404 title : "🔊 Cloud Speaker" ,
531- message : "I'm using a Cloud Speaker (DS10, DS50, DS200). Help me set up audio payment notifications." ,
405+ message :
406+ "I'm using a Cloud Speaker (DS10, DS50, DS200). Help me set up audio payment notifications." ,
532407 className : "welcome-product-suggestion welcome-cloudspeaker" ,
533408 } ,
534409 ] ,
535410 available : "before-first-message" ,
536411 } ) ;
537412
538- // ─ Contextual suggestions — dynamically generated per page (after 1st msg) ─
539- useCopilotChatSuggestions (
540- {
541- instructions : suggestionInstructions ,
542- minSuggestions : 3 ,
543- maxSuggestions : 5 ,
544- available : "after-first-message" ,
545- } ,
546- [ currentPath ] ,
547- ) ;
548-
549413 return (
550414 < >
551415 < CopilotSidebar
0 commit comments