Skip to content
DaneshyarDocs
Sign in
  • Introduction
  • Quickstart
  • What is Daneshyar?Recently added: New
  • The Apps pageRecently added: New
  • Inside an appRecently added: New
  • Workspace: files and chatRecently added: New
  • Sharing and membersRecently added: New
  • Notes and dictationRecently added: New
  • AutomationsRecently added: New
  • The file readerRecently added: New
  • Your profileRecently added: New
  • API keys
  • Authentication
  • Apps, workspaces, resources
  • Ingestion
  • Chat and sessions
  • Citations
  • Skills
  • Conventions
  • Workspaces
  • Resources
  • Chat
  • Skills
  • Errors
  • Customer support assistant
  • Internal knowledge base
  • Documentation chatbot
  • Embedded widgetRecently added: New
  • Widget configurationRecently added: New
  • Framework guidesRecently added: New
  • SecurityRecently added: New
  • TroubleshootingRecently added: New
  • FAQRecently added: New
  • TypeScript SDKOn the roadmap: Soon
  1. Docs
  2. Embedded widget
  3. Framework guides

Framework guides

Where the snippet goes in HTML, React, Next.js, Vue, Nuxt, Angular, SvelteKit and Laravel.

The widget is one script, so every framework does the same thing: load it once, with three attributes. Only the place where the script goes is different.

Before you start

  • Get your snippet from the Embed page in the console. It has your key, app id and workspace id.
  • Add your site's domain, and your local dev address (like localhost:5173), to the allowed domains.
  • Load the script once, in the part of your app that never unmounts. It has no unmount and ignores a second copy, so do not put it inside a page component. The examples below use dk_live_… as a sample key.

Keeping the three values in environment variables lets you use one app for staging and another for production:

.env
DANESHYAR_WIDGET_KEY=dk_live_…
DANESHYAR_APP_ID=…
DANESHYAR_WORKSPACE_ID=…
A front-end environment variable is built into the page, so it is public. That is fine for the widget key, which is public anyway, but never use it for a key you call the REST API with.

Static HTML

Paste the tag just before </body> on every page that should show the chat. If your site has a shared footer or template, put it there once.

index.html
<!doctype html>
<html lang="fa" dir="rtl">
  <head>
    <meta charset="utf-8" />
    <title>My clinic</title>
  </head>
  <body>
    <h1>Welcome</h1>

    <!-- Daneshyar widget: once per page, just before </body> -->
    <script
      src="https://app.daneshyar.info/widget.js"
      data-api-key="dk_live_9f3aC2xQ7mB1vT8sE4nK6pR0jY5wZ2hL"
      data-app-id="2d7c0e91-4a5b-4f8e-b3c6-9e1f0a7d5c42"
      data-workspace-id="8f14e45f-ceea-467a-9f4c-1a2b3c4d5e6f"
      async
    ></script>
  </body>
</html>

Nothing else is needed. The script loads with async, after your content.

React (Vite)

Make a small component and render it once in App.tsx. It adds the script when the app first mounts.

src/DaneshyarWidget.tsx
import { useEffect } from "react";

const WIDGET_SRC = "https://app.daneshyar.info/widget.js";

/** Render once, in your root component (App.tsx). */
export function DaneshyarWidget() {
  useEffect(() => {
    // widget.js runs once per page. Don't add it twice.
    if (document.querySelector(`script[src="${WIDGET_SRC}"]`)) return;

    const script = document.createElement("script");
    script.src = WIDGET_SRC;
    script.async = true;
    // dataset.apiKey becomes data-api-key, and so on.
    script.dataset.apiKey = import.meta.env.VITE_DANESHYAR_WIDGET_KEY;
    script.dataset.appId = import.meta.env.VITE_DANESHYAR_APP_ID;
    script.dataset.workspaceId = import.meta.env.VITE_DANESHYAR_WORKSPACE_ID;
    script.dataset.locale = "fa";
    document.body.appendChild(script);
  }, []);

  return null; // the widget draws its own button
}

With Create React App, use process.env.REACT_APP_… instead of import.meta.env.VITE_…. In development, React's Strict Mode runs effects twice. The querySelector check stops a second copy.

Next.js (App Router)

Use next/script in the root layout, app/layout.tsx, so the script stays loaded while visitors move between pages.

app/layout.tsx
import Script from "next/script";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="fa" dir="rtl">
      <body>
        {children}
        <Script
          src="https://app.daneshyar.info/widget.js"
          strategy="afterInteractive"
          data-api-key={process.env.NEXT_PUBLIC_DANESHYAR_WIDGET_KEY}
          data-app-id={process.env.NEXT_PUBLIC_DANESHYAR_APP_ID}
          data-workspace-id={process.env.NEXT_PUBLIC_DANESHYAR_WORKSPACE_ID}
        />
      </body>
    </html>
  );
}

Variables used in the browser must start with NEXT_PUBLIC_. With the Pages Router, put the same <Script> in pages/_app.tsx.

Vue 3

Add the script from App.vue, the root component, in onMounted.

src/App.vue
<script setup lang="ts">
import { onMounted } from "vue";

const WIDGET_SRC = "https://app.daneshyar.info/widget.js";

onMounted(() => {
  // widget.js runs once per page. Don't add it twice.
  if (document.querySelector(`script[src="${WIDGET_SRC}"]`)) return;

  const script = document.createElement("script");
  script.src = WIDGET_SRC;
  script.async = true;
  script.dataset.apiKey = import.meta.env.VITE_DANESHYAR_WIDGET_KEY;
  script.dataset.appId = import.meta.env.VITE_DANESHYAR_APP_ID;
  script.dataset.workspaceId = import.meta.env.VITE_DANESHYAR_WORKSPACE_ID;
  document.body.appendChild(script);
});
</script>

<template>
  <RouterView />
</template>

For a Vue app without a build step, paste the HTML snippet into index.html instead.

Nuxt 3

Add the script to app.head in nuxt.config.ts. Nuxt adds it to every page.

nuxt.config.ts
export default defineNuxtConfig({
  app: {
    head: {
      script: [
        {
          src: "https://app.daneshyar.info/widget.js",
          async: true,
          tagPosition: "bodyClose",
          "data-api-key": "dk_live_9f3aC2xQ7mB1vT8sE4nK6pR0jY5wZ2hL",
          "data-app-id": "2d7c0e91-4a5b-4f8e-b3c6-9e1f0a7d5c42",
          "data-workspace-id": "8f14e45f-ceea-467a-9f4c-1a2b3c4d5e6f",
        },
      ],
    },
  },
});

To read the values from the environment, use runtimeConfig.public and useHead() in app.vue instead.

Angular

Paste the snippet into src/index.html, after <app-root>.

src/index.html
<!doctype html>
<html lang="fa" dir="rtl">
  <head>
    <meta charset="utf-8" />
    <base href="/" />
  </head>
  <body>
    <app-root></app-root>

    <script
      src="https://app.daneshyar.info/widget.js"
      data-api-key="dk_live_9f3aC2xQ7mB1vT8sE4nK6pR0jY5wZ2hL"
      data-app-id="2d7c0e91-4a5b-4f8e-b3c6-9e1f0a7d5c42"
      data-workspace-id="8f14e45f-ceea-467a-9f4c-1a2b3c4d5e6f"
      async
    ></script>
  </body>
</html>

This is the simplest place, and it works for every route. To use different values per environment, use one index.html per build with fileReplacements in angular.json.

SvelteKit

Paste the snippet into src/app.html, after %sveltekit.body%.

src/app.html
<!doctype html>
<html lang="fa" dir="rtl">
  <head>
    <meta charset="utf-8" />
    %sveltekit.head%
  </head>
  <body data-sveltekit-preload-data="hover">
    <div style="display: contents">%sveltekit.body%</div>

    <script
      src="https://app.daneshyar.info/widget.js"
      data-api-key="dk_live_9f3aC2xQ7mB1vT8sE4nK6pR0jY5wZ2hL"
      data-app-id="2d7c0e91-4a5b-4f8e-b3c6-9e1f0a7d5c42"
      data-workspace-id="8f14e45f-ceea-467a-9f4c-1a2b3c4d5e6f"
      async
    ></script>
  </body>
</html>

For plain Svelte with Vite, add it to index.html. To read the values from the environment, add the script in onMount in your root +layout.svelte, as in the React example.

Laravel (Blade)

Put the tag in your main Blade layout and read the values from config/services.php.

resources/views/layouts/app.blade.php
{{-- config/services.php:
     'daneshyar' => [
         'widget_key'   => env('DANESHYAR_WIDGET_KEY'),
         'app_id'       => env('DANESHYAR_APP_ID'),
         'workspace_id' => env('DANESHYAR_WORKSPACE_ID'),
     ], --}}

<body>
    @yield('content')

    <script
        src="https://app.daneshyar.info/widget.js"
        data-api-key="{{ config('services.daneshyar.widget_key') }}"
        data-app-id="{{ config('services.daneshyar.app_id') }}"
        data-workspace-id="{{ config('services.daneshyar.workspace_id') }}"
        async
    ></script>
</body>

After you change .env, run php artisan config:clear so Laravel reads the new values.

Node.js and Express

The widget runs in the browser, so you don't need a server for it. Use a server only if you build your own chat screen. Then your server calls the REST API with the key, and your page calls your server:

server.js
import express from "express";

const BASE = "https://api.daneshyar.info/api/v1";
const KEY = process.env.DANESHYAR_API_KEY; // server only, never in the browser
const WORKSPACE = process.env.DANESHYAR_WORKSPACE_ID;

const app = express();
app.use(express.json());

// Your frontend calls POST /api/ask. Only this server knows the key.
app.post("/api/ask", async (req, res) => {
  const { question, sessionId } = req.body;
  if (typeof question !== "string" || !question.trim()) {
    return res.status(400).json({ error: "question is required" });
  }

  const upstream = await fetch(`${BASE}/workspaces/${WORKSPACE}/chat/`, {
    method: "POST",
    headers: { "X-API-Key": KEY, "Content-Type": "application/json" },
    body: JSON.stringify({
      message: question,
      session_id: sessionId ?? undefined,
      // One id per signed-in user keeps their chats apart.
      external_user_id: String(req.user?.id ?? "anonymous"),
    }),
  });

  const body = await upstream.json();
  if (!upstream.ok) {
    return res.status(upstream.status).json({ error: body.message ?? body.detail });
  }

  const reply = body.data.assistant_message;
  res.json({
    sessionId: body.data.session.id,
    answer: reply.content,
    sources: reply.citations,
  });
});

app.listen(3000);

The full request and response are on the Chat API page. This key must stay on the server. Never send it to the browser.

What you should see

  • A round button in the corner of every page (or the chat inside your element, in inline mode).
  • Clicking it opens the chat with your title and the list of documents.
  • A question about your documents gets an answer with numbered sources.

Nothing appears, or you see an error message? See Troubleshooting.

PreviousWidget configurationNextSecurity

On this page

  • Before you start
  • Static HTML
  • React (Vite)
  • Next.js (App Router)
  • Vue 3
  • Nuxt 3
  • Angular
  • SvelteKit
  • Laravel (Blade)
  • Node.js and Express
  • What you should see

Every example on this site runs against the live API. If one doesn't, tell us — that's a bug in the docs.

ServicesAboutTalk to us