Commit 015b7902 authored by Mahmoud Aglan's avatar Mahmoud Aglan

bg

parent fcd5022b
"""
Utility endpoints — chat search, export, regenerate, usage analytics.
Son of Anton v4.2.0 — NEW TOOLS
"""
import json
import re
from datetime import datetime, timedelta
from typing import Optional
from pydantic import BaseModel
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session
from sqlalchemy import func, or_
from backend.database import get_db
from backend.models import User, Chat, Message, ChatAttachment
from backend.auth import get_current_user
router = APIRouter()
# ═══════════════════════════════════════════════════
# CHAT SEARCH — find messages across all your chats
# ═══════════════════════════════════════════════════
@router.get("/search")
def search_messages(
q: str = Query(..., min_length=2, max_length=500),
limit: int = Query(50, ge=1, le=200),
user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Search through all messages in user's chats."""
search_term = f"%{q}%"
results = (
db.query(Message, Chat.title, Chat.id)
.join(Chat, Message.chat_id == Chat.id)
.filter(
Chat.user_id == user.id,
Message.content.ilike(search_term),
)
.order_by(Message.created_at.desc())
.limit(limit)
.all()
)
return {
"query": q,
"count": len(results),
"results": [
{
"message_id": msg.id,
"chat_id": chat_id,
"chat_title": chat_title,
"role": msg.role,
"content_preview": _highlight_match(msg.content or "", q, 300),
"created_at": str(msg.created_at),
}
for msg, chat_title, chat_id in results
],
}
def _highlight_match(text: str, query: str, max_len: int = 300) -> str:
"""Extract a snippet around the match."""
lower = text.lower()
idx = lower.find(query.lower())
if idx == -1:
return text[:max_len]
start = max(0, idx - 80)
end = min(len(text), idx + len(query) + 220)
snippet = text[start:end]
if start > 0:
snippet = "…" + snippet
if end < len(text):
snippet = snippet + "…"
return snippet
# ═══════════════════════════════════════════════════
# CHAT EXPORT — download full chat as markdown
# ═══════════════════════════════════════════════════
@router.get("/chats/{chat_id}/export")
def export_chat_markdown(
chat_id: str,
user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Export entire chat as a clean markdown file."""
chat = db.query(Chat).filter(Chat.id == chat_id, Chat.user_id == user.id).first()
if not chat:
raise HTTPException(404, "Chat not found")
messages = (
db.query(Message)
.filter(Message.chat_id == chat_id)
.order_by(Message.created_at)
.all()
)
lines = [
f"# {chat.title}",
f"",
f"**Model:** {chat.model}",
f"**Created:** {chat.created_at}",
f"**Messages:** {len(messages)}",
f"",
f"---",
f"",
]
total_input = 0
total_output = 0
for msg in messages:
role_label = "🧑 **User**" if msg.role == "user" else "🔥 **Son of Anton**"
lines.append(f"### {role_label}")
lines.append(f"*{msg.created_at}*")
lines.append("")
if msg.thinking_content:
lines.append("<details>")
lines.append("<summary>🧠 Reasoning</summary>")
lines.append("")
lines.append(msg.thinking_content)
lines.append("")
lines.append("</details>")
lines.append("")
lines.append(msg.content or "*(empty)*")
lines.append("")
if msg.input_tokens or msg.output_tokens:
total_input += msg.input_tokens or 0
total_output += msg.output_tokens or 0
lines.append("---")
lines.append("")
lines.append(f"## Token Usage")
lines.append(f"- Input: {total_input:,}")
lines.append(f"- Output: {total_output:,}")
lines.append(f"- Total: {total_input + total_output:,}")
lines.append("")
lines.append("*Exported from Son of Anton*")
md = "\n".join(lines)
safe_title = re.sub(r'[^\w\s-]', '', chat.title).strip()
safe_title = re.sub(r'\s+', '-', safe_title)[:60] or "chat-export"
import io
return StreamingResponse(
io.BytesIO(md.encode("utf-8")),
media_type="text/markdown",
headers={"Content-Disposition": f'attachment; filename="{safe_title}.md"'},
)
# ═══════════════════════════════════════════════════
# EXPORT CHAT AS JSON (machine-readable)
# ═══════════════════════════════════════════════════
@router.get("/chats/{chat_id}/export-json")
def export_chat_json(
chat_id: str,
user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Export chat as structured JSON."""
chat = db.query(Chat).filter(Chat.id == chat_id, Chat.user_id == user.id).first()
if not chat:
raise HTTPException(404, "Chat not found")
messages = (
db.query(Message)
.filter(Message.chat_id == chat_id)
.order_by(Message.created_at)
.all()
)
data = {
"chat": {
"id": chat.id,
"title": chat.title,
"model": chat.model,
"created_at": str(chat.created_at),
"updated_at": str(chat.updated_at),
},
"messages": [
{
"id": m.id,
"role": m.role,
"content": m.content,
"thinking_content": m.thinking_content,
"input_tokens": m.input_tokens,
"output_tokens": m.output_tokens,
"created_at": str(m.created_at),
}
for m in messages
],
"exported_at": datetime.utcnow().isoformat(),
"exported_by": user.username,
}
safe_title = re.sub(r'[^\w\s-]', '', chat.title).strip()
safe_title = re.sub(r'\s+', '-', safe_title)[:60] or "chat-export"
import io
return StreamingResponse(
io.BytesIO(json.dumps(data, indent=2, ensure_ascii=False).encode("utf-8")),
media_type="application/json",
headers={"Content-Disposition": f'attachment; filename="{safe_title}.json"'},
)
# ═══════════════════════════════════════════════════
# USER ANALYTICS — your personal usage dashboard
# ═══════════════════════════════════════════════════
@router.get("/my-stats")
def my_stats(
user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Get personal usage statistics."""
now = datetime.utcnow()
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
week_start = today_start - timedelta(days=7)
month_start = today_start.replace(day=1)
total_chats = db.query(Chat).filter(Chat.user_id == user.id).count()
total_messages = (
db.query(Message)
.join(Chat)
.filter(Chat.user_id == user.id)
.count()
)
messages_today = (
db.query(Message)
.join(Chat)
.filter(Chat.user_id == user.id, Message.role == "user", Message.created_at >= today_start)
.count()
)
messages_this_week = (
db.query(Message)
.join(Chat)
.filter(Chat.user_id == user.id, Message.role == "user", Message.created_at >= week_start)
.count()
)
# Token usage breakdown
token_data = (
db.query(
func.sum(Message.input_tokens),
func.sum(Message.output_tokens),
)
.join(Chat)
.filter(Chat.user_id == user.id, Message.role == "assistant")
.first()
)
total_input_tokens = token_data[0] or 0
total_output_tokens = token_data[1] or 0
# Most active chats
active_chats = (
db.query(Chat.id, Chat.title, func.count(Message.id).label("msg_count"))
.join(Message)
.filter(Chat.user_id == user.id)
.group_by(Chat.id, Chat.title)
.order_by(func.count(Message.id).desc())
.limit(5)
.all()
)
# Model usage breakdown
model_usage = (
db.query(Chat.model, func.count(Chat.id))
.filter(Chat.user_id == user.id)
.group_by(Chat.model)
.all()
)
# Daily message counts for last 7 days
daily_counts = []
for i in range(7):
day_start = today_start - timedelta(days=i)
day_end = day_start + timedelta(days=1)
count = (
db.query(Message)
.join(Chat)
.filter(
Chat.user_id == user.id,
Message.role == "user",
Message.created_at >= day_start,
Message.created_at < day_end,
)
.count()
)
daily_counts.append({
"date": day_start.strftime("%Y-%m-%d"),
"day": day_start.strftime("%a"),
"messages": count,
})
daily_counts.reverse()
# Attachment stats
att_count = (
db.query(ChatAttachment)
.join(Chat, ChatAttachment.chat_id == Chat.id)
.filter(Chat.user_id == user.id)
.count()
)
att_size = (
db.query(func.sum(ChatAttachment.file_size))
.join(Chat, ChatAttachment.chat_id == Chat.id)
.filter(Chat.user_id == user.id)
.scalar() or 0
)
return {
"user": {
"username": user.username,
"role": user.role,
"quota_monthly": user.quota_tokens_monthly,
"tokens_used_this_month": user.tokens_used_this_month,
"quota_remaining": max(0, user.quota_tokens_monthly - user.tokens_used_this_month),
"quota_percent_used": round(
(user.tokens_used_this_month / max(user.quota_tokens_monthly, 1)) * 100, 1
),
},
"totals": {
"chats": total_chats,
"messages": total_messages,
"input_tokens": total_input_tokens,
"output_tokens": total_output_tokens,
"total_tokens": total_input_tokens + total_output_tokens,
"attachments": att_count,
"attachment_size_mb": round(att_size / 1024 / 1024, 2),
},
"activity": {
"messages_today": messages_today,
"messages_this_week": messages_this_week,
"daily_activity": daily_counts,
},
"top_chats": [
{"id": cid, "title": title, "message_count": cnt}
for cid, title, cnt in active_chats
],
"model_usage": {
model: count for model, count in model_usage
},
}
# ═══════════════════════════════════════════════════
# DELETE MESSAGE — remove a specific message
# ═══════════════════════════════════════════════════
@router.delete("/chats/{chat_id}/messages/{message_id}")
def delete_message(
chat_id: str,
message_id: str,
user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Delete a specific message from a chat."""
chat = db.query(Chat).filter(Chat.id == chat_id, Chat.user_id == user.id).first()
if not chat:
raise HTTPException(404, "Chat not found")
msg = db.query(Message).filter(
Message.id == message_id, Message.chat_id == chat_id
).first()
if not msg:
raise HTTPException(404, "Message not found")
# Also unlink any attachments
db.query(ChatAttachment).filter(ChatAttachment.message_id == message_id).update(
{"message_id": None}
)
db.delete(msg)
db.commit()
return {"ok": True}
# ═══════════════════════════════════════════════════
# DUPLICATE CHAT — fork a conversation
# ═══════════════════════════════════════════════════
class ForkChatBody(BaseModel):
new_title: Optional[str] = None
up_to_message_id: Optional[str] = None
@router.post("/chats/{chat_id}/fork")
def fork_chat(
chat_id: str,
body: ForkChatBody = ForkChatBody(),
user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Duplicate a chat, optionally up to a specific message."""
original = db.query(Chat).filter(Chat.id == chat_id, Chat.user_id == user.id).first()
if not original:
raise HTTPException(404, "Chat not found")
new_title = body.new_title or f"{original.title} (fork)"
new_chat = Chat(
user_id=user.id,
title=new_title[:200],
model=original.model,
knowledge_base_id=original.knowledge_base_id,
linked_repo_id=original.linked_repo_id,
max_tokens=original.max_tokens,
reasoning_budget=original.reasoning_budget,
)
db.add(new_chat)
db.commit()
db.refresh(new_chat)
messages = (
db.query(Message)
.filter(Message.chat_id == chat_id)
.order_by(Message.created_at)
.all()
)
copied = 0
for msg in messages:
new_msg = Message(
chat_id=new_chat.id,
role=msg.role,
content=msg.content,
thinking_content=msg.thinking_content,
input_tokens=msg.input_tokens,
output_tokens=msg.output_tokens,
)
db.add(new_msg)
copied += 1
if body.up_to_message_id and msg.id == body.up_to_message_id:
break
db.commit()
return {
"ok": True,
"new_chat_id": new_chat.id,
"title": new_chat.title,
"messages_copied": copied,
}
# ═══════════════════════════════════════════════════
# BOOKMARK / PIN messages
# ═══════════════════════════════════════════════════
@router.get("/chats/{chat_id}/code-blocks")
def extract_chat_code_blocks(
chat_id: str,
user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""Extract ALL code blocks from every assistant message in a chat."""
chat = db.query(Chat).filter(Chat.id == chat_id, Chat.user_id == user.id).first()
if not chat:
raise HTTPException(404, "Chat not found")
from backend.services.code_extractor import extract_code_blocks
messages = (
db.query(Message)
.filter(Message.chat_id == chat_id, Message.role == "assistant")
.order_by(Message.created_at)
.all()
)
all_blocks = []
seen_files = {}
for msg in messages:
if not msg.content:
continue
blocks = extract_code_blocks(msg.content)
for block in blocks:
# Keep track of latest version of each file
seen_files[block["filename"]] = block
all_blocks.append({
**block,
"message_id": msg.id,
"message_date": str(msg.created_at),
})
# Deduplicated = latest version of each filename
latest_files = list(seen_files.values())
return {
"chat_id": chat_id,
"chat_title": chat.title,
"total_blocks": len(all_blocks),
"unique_files": len(latest_files),
"all_blocks": all_blocks,
"latest_files": latest_files,
}
\ No newline at end of file
// ═══════════════════════════════════════════════════ const BASE = "/api";
// UTILITY TOOLS — v4.2.0 enhancements
// ═══════════════════════════════════════════════════ function headers(token) {
const h = { "Content-Type": "application/json" };
export const searchMessages = (token, query, limit = 50) => if (token) h["Authorization"] = `Bearer ${token}`;
request("GET", `/utils/search?q=${encodeURIComponent(query)}&limit=${limit}`, token); return h;
}
export const getMyStats = (token) => request("GET", "/utils/my-stats", token); function authHeader(token) { return token ? { Authorization: `Bearer ${token}` } : {}; }
function extractError(err, d) { let m = err.detail || err.message || d; if (Array.isArray(m)) return m.map(x => x.msg || JSON.stringify(x)).join(", "); if (typeof m === "object") return m.message || JSON.stringify(m); return String(m); }
export const forkChat = (token, chatId, data = {}) =>
request("POST", `/utils/chats/${chatId}/fork`, token, data); async function request(method, path, token, body) {
const opts = { method, headers: headers(token) };
export const deleteMessage = (token, chatId, messageId) => if (body) opts.body = JSON.stringify(body);
request("DELETE", `/utils/chats/${chatId}/messages/${messageId}`, token); const res = await fetch(`${BASE}${path}`, opts);
if (!res.ok) { const err = await res.json().catch(() => ({ detail: res.statusText })); throw new Error(extractError(err, "Request failed")); }
export const getChatCodeBlocks = (token, chatId) => return res.json();
request("GET", `/utils/chats/${chatId}/code-blocks`, token);
export function exportChatMarkdown(token, chatId) {
return fetch(`${BASE}/utils/chats/${chatId}/export`, {
headers: { Authorization: `Bearer ${token}` },
}).then((res) => {
if (!res.ok) throw new Error("Export failed");
return res.blob();
}).then((blob) => {
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "chat-export.md";
a.click();
URL.revokeObjectURL(url);
});
} }
export function exportChatJson(token, chatId) { // Auth
return fetch(`${BASE}/utils/chats/${chatId}/export-json`, { export const login = (u, p) => request("POST", "/auth/login", null, { username: u, password: p });
headers: { Authorization: `Bearer ${token}` }, export const register = (u, e, p) => request("POST", "/auth/register", null, { username: u, email: e, password: p });
}).then((res) => { export const getMe = (t) => request("GET", "/auth/me", t);
if (!res.ok) throw new Error("Export failed");
return res.blob(); // Chats
}).then((blob) => { export const listChats = (t) => request("GET", "/chats", t);
const url = URL.createObjectURL(blob); export const createChat = (t, d = {}) => request("POST", "/chats", t, d);
const a = document.createElement("a"); export const updateChat = (t, id, d) => request("PUT", `/chats/${id}`, t, d);
a.href = url; export const renameChat = (t, id, title) => updateChat(t, id, { title });
a.download = "chat-export.json"; export const deleteChat = (t, id) => request("DELETE", `/chats/${id}`, t);
a.click(); export const getMessages = (t, id) => request("GET", `/chats/${id}/messages`, t);
URL.revokeObjectURL(url); export const checkGenerating = (t, id) => request("GET", `/chats/${id}/generating`, t);
}); export const refreshRepoContext = (t, id) => request("POST", `/chats/${id}/refresh-repo`, t);
} export const commitFromChat = (t, id, d) => request("POST", `/chats/${id}/commit`, t, d);
\ No newline at end of file
// Streaming
export async function* streamMessage(token, chatId, body, signal) {
const res = await fetch(`${BASE}/chats/${chatId}/messages`, { method: "POST", headers: headers(token), body: JSON.stringify(body), signal });
if (!res.ok) { const err = await res.json().catch(() => ({ detail: res.statusText })); throw new Error(extractError(err, "Stream failed")); }
const reader = res.body.getReader(); const decoder = new TextDecoder(); let buffer = "";
while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const parts = buffer.split("\n\n"); buffer = parts.pop() || ""; for (const part of parts) { const line = part.trim(); if (line.startsWith("data: ")) { try { yield JSON.parse(line.slice(6)); } catch { } } } }
if (buffer.trim().startsWith("data: ")) { try { yield JSON.parse(buffer.trim().slice(6)); } catch { } }
}
// Attachments
export async function uploadAttachments(t, chatId, files) { const form = new FormData(); for (const f of files) form.append("files", f); const res = await fetch(`${BASE}/chats/${chatId}/attachments`, { method: "POST", headers: authHeader(t), body: form }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(extractError(err, "Upload failed")); } return res.json(); }
export function getAttachmentUrl(id) { return `${BASE}/attachments/${id}/file`; }
export const deleteAttachment = (t, id) => request("DELETE", `/attachments/${id}`, t);
// Knowledge
export const listKnowledgeBases = (t) => request("GET", "/knowledge", t);
export const createKnowledgeBase = (t, n, d = "") => request("POST", "/knowledge", t, { name: n, description: d });
export const getKnowledgeBase = (t, id) => request("GET", `/knowledge/${id}`, t);
export const updateKnowledgeBase = (t, id, d) => request("PUT", `/knowledge/${id}`, t, d);
export const deleteKnowledgeBase = (t, id) => request("DELETE", `/knowledge/${id}`, t);
export const listKnowledgeDocuments = (t, id) => request("GET", `/knowledge/${id}/documents`, t);
export const deleteKnowledgeDocument = (t, kbId, docId) => request("DELETE", `/knowledge/${kbId}/documents/${docId}`, t);
export async function uploadDocuments(t, kbId, files) { const form = new FormData(); for (const f of files) form.append("files", f); const res = await fetch(`${BASE}/knowledge/${kbId}/upload`, { method: "POST", headers: authHeader(t), body: form }); if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(extractError(err, "Upload failed")); } return res.json(); }
export const uploadDocument = (t, kbId, f) => uploadDocuments(t, kbId, [f]);
// Admin
export const adminStats = (t) => request("GET", "/admin/stats", t);
export const adminListUsers = (t) => request("GET", "/admin/users", t);
export const adminCreateUser = (t, d) => request("POST", "/admin/users", t, d);
export const adminUpdateUser = (t, id, d) => request("PUT", `/admin/users/${id}`, t, d);
export const adminDeleteUser = (t, id) => request("DELETE", `/admin/users/${id}`, t);
export const adminListChats = (t) => request("GET", "/admin/chats", t);
// Admin — Permissions
export const adminGetUserPermissions = (t, uid) => request("GET", `/admin/users/${uid}/permissions`, t);
export const adminUpdateUserPermissions = (t, uid, d) => request("PUT", `/admin/users/${uid}/permissions`, t, d);
export const adminGetDefaultPermissions = (t) => request("GET", "/admin/permissions/defaults", t);
export const adminUpdateDefaultPermissions = (t, d) => request("PUT", "/admin/permissions/defaults", t, d);
export const adminApplyDefaults = (t) => request("POST", "/admin/permissions/apply-defaults", t);
export const adminGetModels = (t) => request("GET", "/admin/models", t);
// Code Download
export async function downloadZip(t, md, title) { const res = await fetch(`${BASE}/files/download-zip`, { method: "POST", headers: headers(t), body: JSON.stringify({ markdown: md, title: title || null }) }); if (!res.ok) throw new Error("Download failed"); const ct = res.headers.get("content-type") || ""; if (ct.includes("application/zip")) { const blob = await res.blob(); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; const raw = (title || "").trim(); a.download = `${raw && raw !== "New Chat" ? raw.replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-").slice(0, 60) || "code" : "code"}.zip`; a.click(); URL.revokeObjectURL(url); } else { const data = await res.json(); if (data.error) throw new Error(data.error); } }
// Export PPTX / DOCX
export async function exportPptx(token, markdown, title) {
const res = await fetch(`${BASE}/export/pptx`, { method: "POST", headers: headers(token), body: JSON.stringify({ markdown, title }) });
if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(extractError(err, "PPTX export failed")); }
const blob = await res.blob(); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url;
const safe = (title || "presentation").replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-").slice(0, 50) || "presentation";
a.download = `${safe}.pptx`; a.click(); URL.revokeObjectURL(url);
}
export async function exportDocx(token, markdown, title) {
const res = await fetch(`${BASE}/export/docx`, { method: "POST", headers: headers(token), body: JSON.stringify({ markdown, title }) });
if (!res.ok) { const err = await res.json().catch(() => ({})); throw new Error(extractError(err, "DOCX export failed")); }
const blob = await res.blob(); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url;
const safe = (title || "document").replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-").slice(0, 50) || "document";
a.download = `${safe}.docx`; a.click(); URL.revokeObjectURL(url);
}
// Utilities
const CODE_BLOCK_RE = /```(\S*?)(?::(\S+?))?\s*?\n([\s\S]*?)```/g;
export function extractCodeBlocks(md) { if (!md) return []; const blocks = []; let m; const re = new RegExp(CODE_BLOCK_RE.source, "g"); while ((m = re.exec(md)) !== null) { const lang = (m[1] || "text").toLowerCase(); const fn = m[2] || null; const code = (m[3] || "").trim(); if (code) blocks.push({ language: lang, filename: fn, code }); } return blocks; }
// GitLab
export const gitlabGetSettings = (t) => request("GET", "/gitlab/settings", t);
export const gitlabUpdateSettings = (t, d) => request("PUT", "/gitlab/settings", t, d);
export const gitlabTestConnection = (t) => request("POST", "/gitlab/test-connection", t);
export const gitlabSearchProjects = (t, s, o) => request("GET", `/gitlab/projects?search=${encodeURIComponent(s || "")}&owned=${o || false}`, t);
export const gitlabCreateProject = (t, d) => request("POST", "/gitlab/projects", t, d);
export const gitlabListRepos = (t) => request("GET", "/gitlab/repos", t);
export const gitlabLinkRepo = (t, pid) => request("POST", "/gitlab/repos", t, { gitlab_project_id: pid });
export const gitlabUnlinkRepo = (t, id) => request("DELETE", `/gitlab/repos/${id}`, t);
export const gitlabGetTree = (t, id, p, r) => request("GET", `/gitlab/repos/${id}/tree?path=${encodeURIComponent(p || "")}&ref=${encodeURIComponent(r || "")}`, t);
export const gitlabGetFile = (t, id, p, r) => request("GET", `/gitlab/repos/${id}/file?path=${encodeURIComponent(p)}&ref=${encodeURIComponent(r || "")}`, t);
export const gitlabGetBranches = (t, id) => request("GET", `/gitlab/repos/${id}/branches`, t);
export const gitlabCreateBranch = (t, id, d) => request("POST", `/gitlab/repos/${id}/branches`, t, d);
export const gitlabCommit = (t, id, d) => request("POST", `/gitlab/repos/${id}/commit`, t, d);
export const gitlabCommitSingle = (t, id, d) => request("POST", `/gitlab/repos/${id}/commit-single`, t, d);
export const gitlabCreateMR = (t, id, d) => request("POST", `/gitlab/repos/${id}/merge-request`, t, d);
export const gitlabReanalyzeRepo = (t, id) => request("POST", `/gitlab/repos/${id}/analyze`, t);
export const gitlabGetRepoMap = (t, id) => request("GET", `/gitlab/repos/${id}/map`, t);
export const gitlabListActions = (t, s) => request("GET", `/gitlab/actions?status=${s || "pending"}`, t);
export const gitlabCreateAction = (t, d) => request("POST", "/gitlab/actions", t, d);
export const gitlabApproveAction = (t, id) => request("POST", `/gitlab/actions/${id}/approve`, t);
export const gitlabRejectAction = (t, id) => request("POST", `/gitlab/actions/${id}/reject`, t);
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment