/ frontend / src / app / views / projects / API.jsx
API.jsx
  1  import { useState, useEffect } from "react";
  2  import {
  3    Box, Card, Chip, IconButton, Tab, Tabs, Tooltip, Typography, styled,
  4  } from "@mui/material";
  5  import { ContentCopy, Check } from "@mui/icons-material";
  6  import Breadcrumb from "app/components/Breadcrumb";
  7  import { useParams } from "react-router-dom";
  8  import useAuth from "app/hooks/useAuth";
  9  import api from "app/utils/api";
 10  
 11  const Container = styled("div")(({ theme }) => ({
 12    margin: "24px 48px",
 13    [theme.breakpoints.down("md")]: { margin: "24px 32px" },
 14    [theme.breakpoints.down("sm")]: { margin: 16 },
 15    "& .breadcrumb": { marginBottom: 24 },
 16  }));
 17  
 18  const CodeBlock = styled(Box)(() => ({
 19    background: "#1e1e2e",
 20    color: "#cdd6f4",
 21    padding: "20px 24px",
 22    borderRadius: "0 0 10px 10px",
 23    fontFamily: "'JetBrains Mono', 'SF Mono', Monaco, Consolas, monospace",
 24    fontSize: "0.82rem",
 25    lineHeight: 1.7,
 26    overflowX: "auto",
 27    whiteSpace: "pre",
 28    position: "relative",
 29    tabSize: 4,
 30  }));
 31  
 32  const LangTab = styled(Tab)(() => ({
 33    textTransform: "none",
 34    fontWeight: 500,
 35    fontSize: "0.85rem",
 36    minHeight: 40,
 37    minWidth: 80,
 38    padding: "6px 16px",
 39  }));
 40  
 41  const LANGUAGES = [
 42    { id: "curl", label: "cURL" },
 43    { id: "python", label: "Python" },
 44    { id: "javascript", label: "JavaScript" },
 45    { id: "php", label: "PHP" },
 46    { id: "go", label: "Go" },
 47    { id: "ruby", label: "Ruby" },
 48  ];
 49  
 50  function replaceVars(code, project) {
 51    const url = window.location.protocol + "//" + window.location.host;
 52    const question = project.default_prompt || "What can you help me with?";
 53    return code
 54      .replaceAll("<URL>", url)
 55      .replaceAll("<PROJECT>", project.name || "my-project")
 56      .replaceAll("<QUESTION>", question);
 57  }
 58  
 59  // ── Code templates ──────────────────────────────────────────────────────
 60  
 61  const snippets = {
 62    curl: {
 63      question: `curl -X POST '<URL>/projects/<PROJECT>/question' \\
 64    -H 'Content-Type: application/json' \\
 65    -H 'Authorization: Bearer YOUR_API_KEY' \\
 66    -d '{
 67      "question": "<QUESTION>"
 68    }'`,
 69      chat: `# First message — omit "id" to start a new conversation
 70  curl -X POST '<URL>/projects/<PROJECT>/chat' \\
 71    -H 'Content-Type: application/json' \\
 72    -H 'Authorization: Bearer YOUR_API_KEY' \\
 73    -d '{
 74      "question": "<QUESTION>"
 75    }'
 76  
 77  # Follow-up — include the "id" from the first response
 78  curl -X POST '<URL>/projects/<PROJECT>/chat' \\
 79    -H 'Content-Type: application/json' \\
 80    -H 'Authorization: Bearer YOUR_API_KEY' \\
 81    -d '{
 82      "question": "Tell me more",
 83      "id": "CHAT_ID_FROM_FIRST_RESPONSE"
 84    }'`,
 85    },
 86    python: {
 87      question: `import requests
 88  
 89  API_KEY = "YOUR_API_KEY"
 90  
 91  response = requests.post(
 92      "<URL>/projects/<PROJECT>/question",
 93      headers={
 94          "Content-Type": "application/json",
 95          "Authorization": f"Bearer {API_KEY}",
 96      },
 97      json={"question": "<QUESTION>"},
 98  )
 99  
100  data = response.json()
101  print(data["answer"])`,
102      chat: `import requests
103  
104  API_KEY = "YOUR_API_KEY"
105  
106  def chat(question, chat_id=None):
107      body = {"question": question}
108      if chat_id:
109          body["id"] = chat_id
110  
111      response = requests.post(
112          "<URL>/projects/<PROJECT>/chat",
113          headers={
114              "Content-Type": "application/json",
115              "Authorization": f"Bearer {API_KEY}",
116          },
117          json=body,
118      )
119      return response.json()
120  
121  # Start a new conversation
122  result = chat("<QUESTION>")
123  print(result["answer"])
124  
125  # Continue the conversation using the returned id
126  result = chat("Tell me more", chat_id=result["id"])
127  print(result["answer"])`,
128    },
129    javascript: {
130      question: `const API_KEY = "YOUR_API_KEY";
131  
132  const response = await fetch("<URL>/projects/<PROJECT>/question", {
133    method: "POST",
134    headers: {
135      "Content-Type": "application/json",
136      "Authorization": \`Bearer \${API_KEY}\`,
137    },
138    body: JSON.stringify({ question: "<QUESTION>" }),
139  });
140  
141  const data = await response.json();
142  console.log(data.answer);`,
143      chat: `const API_KEY = "YOUR_API_KEY";
144  
145  async function chat(question, chatId) {
146    const body = { question };
147    if (chatId) body.id = chatId;
148  
149    const response = await fetch("<URL>/projects/<PROJECT>/chat", {
150      method: "POST",
151      headers: {
152        "Content-Type": "application/json",
153        "Authorization": \`Bearer \${API_KEY}\`,
154      },
155      body: JSON.stringify(body),
156    });
157  
158    return response.json();
159  }
160  
161  // Start a new conversation
162  const result = await chat("<QUESTION>");
163  console.log(result.answer);
164  
165  // Continue the conversation using the returned id
166  const followUp = await chat("Tell me more", result.id);
167  console.log(followUp.answer);`,
168    },
169    php: {
170      question: `<?php
171  
172  $apiKey = "YOUR_API_KEY";
173  
174  $ch = curl_init("<URL>/projects/<PROJECT>/question");
175  curl_setopt_array($ch, [
176      CURLOPT_RETURNTRANSFER => true,
177      CURLOPT_POST => true,
178      CURLOPT_POSTFIELDS => json_encode([
179          "question" => "<QUESTION>",
180      ]),
181      CURLOPT_HTTPHEADER => [
182          "Content-Type: application/json",
183          "Authorization: Bearer " . $apiKey,
184      ],
185  ]);
186  
187  $response = curl_exec($ch);
188  curl_close($ch);
189  
190  $data = json_decode($response, true);
191  echo $data["answer"];`,
192      chat: `<?php
193  
194  $apiKey = "YOUR_API_KEY";
195  
196  function chat($question, $chatId = null) {
197      global $apiKey;
198  
199      $body = ["question" => $question];
200      if ($chatId) {
201          $body["id"] = $chatId;
202      }
203  
204      $ch = curl_init("<URL>/projects/<PROJECT>/chat");
205      curl_setopt_array($ch, [
206          CURLOPT_RETURNTRANSFER => true,
207          CURLOPT_POST => true,
208          CURLOPT_POSTFIELDS => json_encode($body),
209          CURLOPT_HTTPHEADER => [
210              "Content-Type: application/json",
211              "Authorization: Bearer " . $apiKey,
212          ],
213      ]);
214  
215      $response = curl_exec($ch);
216      curl_close($ch);
217  
218      return json_decode($response, true);
219  }
220  
221  // Start a new conversation
222  $result = chat("<QUESTION>");
223  echo $result["answer"];
224  
225  // Continue the conversation using the returned id
226  $result = chat("Tell me more", $result["id"]);
227  echo $result["answer"];`,
228    },
229    go: {
230      question: `package main
231  
232  import (
233  	"bytes"
234  	"encoding/json"
235  	"fmt"
236  	"io"
237  	"net/http"
238  )
239  
240  func main() {
241  	apiKey := "YOUR_API_KEY"
242  
243  	body, _ := json.Marshal(map[string]string{
244  		"question": "<QUESTION>",
245  	})
246  
247  	req, _ := http.NewRequest("POST",
248  		"<URL>/projects/<PROJECT>/question",
249  		bytes.NewBuffer(body))
250  	req.Header.Set("Content-Type", "application/json")
251  	req.Header.Set("Authorization", "Bearer "+apiKey)
252  
253  	resp, err := http.DefaultClient.Do(req)
254  	if err != nil {
255  		panic(err)
256  	}
257  	defer resp.Body.Close()
258  
259  	respBody, _ := io.ReadAll(resp.Body)
260  
261  	var data map[string]interface{}
262  	json.Unmarshal(respBody, &data)
263  	fmt.Println(data["answer"])
264  }`,
265      chat: `package main
266  
267  import (
268  	"bytes"
269  	"encoding/json"
270  	"fmt"
271  	"io"
272  	"net/http"
273  )
274  
275  func chat(apiKey, question string, chatID *string) map[string]interface{} {
276  	body := map[string]string{"question": question}
277  	if chatID != nil {
278  		body["id"] = *chatID
279  	}
280  
281  	payload, _ := json.Marshal(body)
282  	req, _ := http.NewRequest("POST",
283  		"<URL>/projects/<PROJECT>/chat",
284  		bytes.NewBuffer(payload))
285  	req.Header.Set("Content-Type", "application/json")
286  	req.Header.Set("Authorization", "Bearer "+apiKey)
287  
288  	resp, _ := http.DefaultClient.Do(req)
289  	defer resp.Body.Close()
290  
291  	respBody, _ := io.ReadAll(resp.Body)
292  
293  	var data map[string]interface{}
294  	json.Unmarshal(respBody, &data)
295  	return data
296  }
297  
298  func main() {
299  	apiKey := "YOUR_API_KEY"
300  
301  	// Start a new conversation
302  	result := chat(apiKey, "<QUESTION>", nil)
303  	fmt.Println(result["answer"])
304  
305  	// Continue using the returned id
306  	id := result["id"].(string)
307  	result = chat(apiKey, "Tell me more", &id)
308  	fmt.Println(result["answer"])
309  }`,
310    },
311    ruby: {
312      question: `require "net/http"
313  require "json"
314  require "uri"
315  
316  api_key = "YOUR_API_KEY"
317  
318  uri = URI("<URL>/projects/<PROJECT>/question")
319  http = Net::HTTP.new(uri.host, uri.port)
320  http.use_ssl = uri.scheme == "https"
321  
322  request = Net::HTTP::Post.new(uri)
323  request["Content-Type"] = "application/json"
324  request["Authorization"] = "Bearer #{api_key}"
325  request.body = { question: "<QUESTION>" }.to_json
326  
327  response = http.request(request)
328  data = JSON.parse(response.body)
329  puts data["answer"]`,
330      chat: `require "net/http"
331  require "json"
332  require "uri"
333  
334  api_key = "YOUR_API_KEY"
335  
336  def chat(api_key, question, chat_id = nil)
337    uri = URI("<URL>/projects/<PROJECT>/chat")
338    http = Net::HTTP.new(uri.host, uri.port)
339    http.use_ssl = uri.scheme == "https"
340  
341    body = { question: question }
342    body[:id] = chat_id if chat_id
343  
344    request = Net::HTTP::Post.new(uri)
345    request["Content-Type"] = "application/json"
346    request["Authorization"] = "Bearer #{api_key}"
347    request.body = body.to_json
348  
349    response = http.request(request)
350    JSON.parse(response.body)
351  end
352  
353  # Start a new conversation
354  result = chat(api_key, "<QUESTION>")
355  puts result["answer"]
356  
357  # Continue using the returned id
358  result = chat(api_key, "Tell me more", result["id"])
359  puts result["answer"]`,
360    },
361  };
362  
363  // ── Copy button ─────────────────────────────────────────────────────────
364  
365  function CopyButton({ text }) {
366    const [copied, setCopied] = useState(false);
367    const handleCopy = () => {
368      navigator.clipboard.writeText(text);
369      setCopied(true);
370      setTimeout(() => setCopied(false), 2000);
371    };
372    return (
373      <Tooltip title={copied ? "Copied!" : "Copy code"}>
374        <IconButton
375          size="small"
376          onClick={handleCopy}
377          sx={{
378            position: "absolute", top: 10, right: 10,
379            color: "#6c7086",
380            "&:hover": { color: "#cdd6f4", background: "rgba(255,255,255,0.08)" },
381          }}
382        >
383          {copied ? <Check fontSize="small" /> : <ContentCopy fontSize="small" />}
384        </IconButton>
385      </Tooltip>
386    );
387  }
388  
389  // ── Example card ────────────────────────────────────────────────────────
390  
391  function ExampleCard({ title, description, endpoint, method, code }) {
392    return (
393      <Card
394        variant="outlined"
395        sx={{
396          borderRadius: "10px",
397          overflow: "hidden",
398          border: "1px solid",
399          borderColor: "divider",
400        }}
401      >
402        <Box sx={{ p: 2.5, pb: 1.5 }}>
403          <Box sx={{ display: "flex", alignItems: "center", gap: 1, mb: 0.5 }}>
404            <Typography variant="subtitle1" fontWeight={600}>{title}</Typography>
405            <Chip label={method} size="small" variant="outlined"
406              sx={{ fontSize: "0.7rem", height: 22, fontFamily: "monospace", fontWeight: 600 }} />
407          </Box>
408          <Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
409            {description}
410          </Typography>
411          <Typography
412            variant="caption"
413            sx={{
414              fontFamily: "monospace",
415              color: "primary.main",
416              background: (t) => t.palette.mode === "dark" ? "rgba(99,102,241,0.1)" : "rgba(99,102,241,0.08)",
417              px: 1, py: 0.3, borderRadius: 1,
418            }}
419          >
420            {endpoint}
421          </Typography>
422        </Box>
423        <CodeBlock>
424          <CopyButton text={code} />
425          {code}
426        </CodeBlock>
427      </Card>
428    );
429  }
430  
431  // ── Main component ──────────────────────────────────────────────────────
432  
433  export default function ProjectAPI() {
434    const { id } = useParams();
435    const [project, setProject] = useState({});
436    const [lang, setLang] = useState("curl");
437    const auth = useAuth();
438  
439    useEffect(() => {
440      document.title = (process.env.REACT_APP_RESTAI_NAME || "RESTai") + " - API - " + id;
441      api.get("/projects/" + id, auth.user.token)
442        .then((d) => setProject(d))
443        .catch(() => {});
444    }, [id]);
445  
446    const projectName = project.name || "my-project";
447    const langSnippets = snippets[lang] || snippets.curl;
448    const questionCode = replaceVars(langSnippets.question, project);
449    const chatCode = replaceVars(langSnippets.chat, project);
450  
451    return (
452      <Container>
453        <Box className="breadcrumb">
454          <Breadcrumb
455            routeSegments={[
456              { name: "Projects", path: "/projects" },
457              { name: projectName, path: "/project/" + id },
458              { name: "API" },
459            ]}
460          />
461        </Box>
462  
463        <Box>
464          <Typography variant="h5" fontWeight={700} sx={{ mb: 0.5 }}>
465            API Reference
466          </Typography>
467          <Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
468            Use your API key to interact with the <strong>{projectName}</strong> project programmatically.
469          </Typography>
470  
471          {/* Language selector */}
472          <Tabs
473            value={lang}
474            onChange={(_, v) => setLang(v)}
475            variant="scrollable"
476            scrollButtons="auto"
477            sx={{
478              mb: 3,
479              minHeight: 40,
480              "& .MuiTabs-indicator": { height: 2.5, borderRadius: 2 },
481            }}
482          >
483            {LANGUAGES.map((l) => (
484              <LangTab key={l.id} value={l.id} label={l.label} />
485            ))}
486          </Tabs>
487  
488          {/* Examples */}
489          <Box sx={{ display: "flex", flexDirection: "column", gap: 3 }}>
490            <ExampleCard
491              title="Question"
492              description="Send a one-shot question. Each request is independent with no conversation memory."
493              endpoint={`POST /projects/${projectName}/question`}
494              method="POST"
495              code={questionCode}
496            />
497            <ExampleCard
498              title="Chat"
499              description="Start or continue a conversation. Omit the id on the first request; include the returned id for follow-ups."
500              endpoint={`POST /projects/${projectName}/chat`}
501              method="POST"
502              code={chatCode}
503            />
504          </Box>
505        </Box>
506      </Container>
507    );
508  }