|
|
const { v4: uuidv4 } = require("uuid"); |
|
|
const { WorkspaceThread } = require("../../../models/workspaceThread"); |
|
|
const { Workspace } = require("../../../models/workspace"); |
|
|
const { validApiKey } = require("../../../utils/middleware/validApiKey"); |
|
|
const { reqBody, multiUserMode } = require("../../../utils/http"); |
|
|
const { VALID_CHAT_MODE } = require("../../../utils/chats/stream"); |
|
|
const { Telemetry } = require("../../../models/telemetry"); |
|
|
const { EventLogs } = require("../../../models/eventLogs"); |
|
|
const { |
|
|
writeResponseChunk, |
|
|
convertToChatHistory, |
|
|
} = require("../../../utils/helpers/chat/responses"); |
|
|
const { WorkspaceChats } = require("../../../models/workspaceChats"); |
|
|
const { User } = require("../../../models/user"); |
|
|
const { ApiChatHandler } = require("../../../utils/chats/apiChatHandler"); |
|
|
const { getModelTag } = require("../../utils"); |
|
|
|
|
|
function apiWorkspaceThreadEndpoints(app) { |
|
|
if (!app) return; |
|
|
|
|
|
app.post( |
|
|
"/v1/workspace/:slug/thread/new", |
|
|
[validApiKey], |
|
|
async (request, response) => { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
const wslug = request.params.slug; |
|
|
let { userId = null, name = null, slug = null } = reqBody(request); |
|
|
const workspace = await Workspace.get({ slug: wslug }); |
|
|
|
|
|
if (!workspace) { |
|
|
response.sendStatus(400).end(); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!response.locals.multiUserMode && !!userId) userId = null; |
|
|
|
|
|
const { thread, message } = await WorkspaceThread.new( |
|
|
workspace, |
|
|
userId ? Number(userId) : null, |
|
|
{ name, slug } |
|
|
); |
|
|
|
|
|
await Telemetry.sendTelemetry("workspace_thread_created", { |
|
|
multiUserMode: multiUserMode(response), |
|
|
LLMSelection: process.env.LLM_PROVIDER || "openai", |
|
|
Embedder: process.env.EMBEDDING_ENGINE || "inherit", |
|
|
VectorDbSelection: process.env.VECTOR_DB || "lancedb", |
|
|
TTSSelection: process.env.TTS_PROVIDER || "native", |
|
|
}); |
|
|
await EventLogs.logEvent("api_workspace_thread_created", { |
|
|
workspaceName: workspace?.name || "Unknown Workspace", |
|
|
}); |
|
|
response.status(200).json({ thread, message }); |
|
|
} catch (e) { |
|
|
console.error(e.message, e); |
|
|
response.sendStatus(500).end(); |
|
|
} |
|
|
} |
|
|
); |
|
|
|
|
|
app.post( |
|
|
"/v1/workspace/:slug/thread/:threadSlug/update", |
|
|
[validApiKey], |
|
|
async (request, response) => { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
const { slug, threadSlug } = request.params; |
|
|
const { name } = reqBody(request); |
|
|
const workspace = await Workspace.get({ slug }); |
|
|
const thread = await WorkspaceThread.get({ |
|
|
slug: threadSlug, |
|
|
workspace_id: workspace.id, |
|
|
}); |
|
|
|
|
|
if (!workspace || !thread) { |
|
|
response.sendStatus(400).end(); |
|
|
return; |
|
|
} |
|
|
|
|
|
const { thread: updatedThread, message } = await WorkspaceThread.update( |
|
|
thread, |
|
|
{ name } |
|
|
); |
|
|
response.status(200).json({ thread: updatedThread, message }); |
|
|
} catch (e) { |
|
|
console.error(e.message, e); |
|
|
response.sendStatus(500).end(); |
|
|
} |
|
|
} |
|
|
); |
|
|
|
|
|
app.delete( |
|
|
"/v1/workspace/:slug/thread/:threadSlug", |
|
|
[validApiKey], |
|
|
async (request, response) => { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
const { slug, threadSlug } = request.params; |
|
|
const workspace = await Workspace.get({ slug }); |
|
|
|
|
|
if (!workspace) { |
|
|
response.sendStatus(400).end(); |
|
|
return; |
|
|
} |
|
|
|
|
|
await WorkspaceThread.delete({ |
|
|
slug: threadSlug, |
|
|
workspace_id: workspace.id, |
|
|
}); |
|
|
response.sendStatus(200).end(); |
|
|
} catch (e) { |
|
|
console.error(e.message, e); |
|
|
response.sendStatus(500).end(); |
|
|
} |
|
|
} |
|
|
); |
|
|
|
|
|
app.get( |
|
|
"/v1/workspace/:slug/thread/:threadSlug/chats", |
|
|
[validApiKey], |
|
|
async (request, response) => { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
const { slug, threadSlug } = request.params; |
|
|
const workspace = await Workspace.get({ slug }); |
|
|
const thread = await WorkspaceThread.get({ |
|
|
slug: threadSlug, |
|
|
workspace_id: workspace.id, |
|
|
}); |
|
|
|
|
|
if (!workspace || !thread) { |
|
|
response.sendStatus(400).end(); |
|
|
return; |
|
|
} |
|
|
|
|
|
const history = await WorkspaceChats.where( |
|
|
{ |
|
|
workspaceId: workspace.id, |
|
|
thread_id: thread.id, |
|
|
api_session_id: null, |
|
|
include: true, |
|
|
}, |
|
|
null, |
|
|
{ id: "asc" } |
|
|
); |
|
|
|
|
|
response.status(200).json({ history: convertToChatHistory(history) }); |
|
|
} catch (e) { |
|
|
console.error(e.message, e); |
|
|
response.sendStatus(500).end(); |
|
|
} |
|
|
} |
|
|
); |
|
|
|
|
|
app.post( |
|
|
"/v1/workspace/:slug/thread/:threadSlug/chat", |
|
|
[validApiKey], |
|
|
async (request, response) => { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
const { slug, threadSlug } = request.params; |
|
|
const { |
|
|
message, |
|
|
mode = "query", |
|
|
userId, |
|
|
attachments = [], |
|
|
reset = false, |
|
|
} = reqBody(request); |
|
|
const workspace = await Workspace.get({ slug }); |
|
|
const thread = await WorkspaceThread.get({ |
|
|
slug: threadSlug, |
|
|
workspace_id: workspace.id, |
|
|
}); |
|
|
|
|
|
if (!workspace || !thread) { |
|
|
response.status(400).json({ |
|
|
id: uuidv4(), |
|
|
type: "abort", |
|
|
textResponse: null, |
|
|
sources: [], |
|
|
close: true, |
|
|
error: `Workspace ${slug} or thread ${threadSlug} is not valid.`, |
|
|
}); |
|
|
return; |
|
|
} |
|
|
|
|
|
if ((!message?.length || !VALID_CHAT_MODE.includes(mode)) && !reset) { |
|
|
response.status(400).json({ |
|
|
id: uuidv4(), |
|
|
type: "abort", |
|
|
textResponse: null, |
|
|
sources: [], |
|
|
close: true, |
|
|
error: !message?.length |
|
|
? "Message is empty" |
|
|
: `${mode} is not a valid mode.`, |
|
|
}); |
|
|
return; |
|
|
} |
|
|
|
|
|
const user = userId ? await User.get({ id: Number(userId) }) : null; |
|
|
const result = await ApiChatHandler.chatSync({ |
|
|
workspace, |
|
|
message, |
|
|
mode, |
|
|
user, |
|
|
thread, |
|
|
attachments, |
|
|
reset, |
|
|
}); |
|
|
await Telemetry.sendTelemetry("sent_chat", { |
|
|
LLMSelection: process.env.LLM_PROVIDER || "openai", |
|
|
Embedder: process.env.EMBEDDING_ENGINE || "inherit", |
|
|
VectorDbSelection: process.env.VECTOR_DB || "lancedb", |
|
|
TTSSelection: process.env.TTS_PROVIDER || "native", |
|
|
LLMModel: getModelTag(), |
|
|
}); |
|
|
await EventLogs.logEvent("api_sent_chat", { |
|
|
workspaceName: workspace?.name, |
|
|
chatModel: workspace?.chatModel || "System Default", |
|
|
threadName: thread?.name, |
|
|
userId: user?.id, |
|
|
}); |
|
|
response.status(200).json({ ...result }); |
|
|
} catch (e) { |
|
|
console.error(e.message, e); |
|
|
response.status(500).json({ |
|
|
id: uuidv4(), |
|
|
type: "abort", |
|
|
textResponse: null, |
|
|
sources: [], |
|
|
close: true, |
|
|
error: e.message, |
|
|
}); |
|
|
} |
|
|
} |
|
|
); |
|
|
|
|
|
app.post( |
|
|
"/v1/workspace/:slug/thread/:threadSlug/stream-chat", |
|
|
[validApiKey], |
|
|
async (request, response) => { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
const { slug, threadSlug } = request.params; |
|
|
const { |
|
|
message, |
|
|
mode = "query", |
|
|
userId, |
|
|
attachments = [], |
|
|
reset = false, |
|
|
} = reqBody(request); |
|
|
const workspace = await Workspace.get({ slug }); |
|
|
const thread = await WorkspaceThread.get({ |
|
|
slug: threadSlug, |
|
|
workspace_id: workspace.id, |
|
|
}); |
|
|
|
|
|
if (!workspace || !thread) { |
|
|
response.status(400).json({ |
|
|
id: uuidv4(), |
|
|
type: "abort", |
|
|
textResponse: null, |
|
|
sources: [], |
|
|
close: true, |
|
|
error: `Workspace ${slug} or thread ${threadSlug} is not valid.`, |
|
|
}); |
|
|
return; |
|
|
} |
|
|
|
|
|
if ((!message?.length || !VALID_CHAT_MODE.includes(mode)) && !reset) { |
|
|
response.status(400).json({ |
|
|
id: uuidv4(), |
|
|
type: "abort", |
|
|
textResponse: null, |
|
|
sources: [], |
|
|
close: true, |
|
|
error: !message?.length |
|
|
? "Message is empty" |
|
|
: `${mode} is not a valid mode.`, |
|
|
}); |
|
|
return; |
|
|
} |
|
|
|
|
|
const user = userId ? await User.get({ id: Number(userId) }) : null; |
|
|
|
|
|
response.setHeader("Cache-Control", "no-cache"); |
|
|
response.setHeader("Content-Type", "text/event-stream"); |
|
|
response.setHeader("Access-Control-Allow-Origin", "*"); |
|
|
response.setHeader("Connection", "keep-alive"); |
|
|
response.flushHeaders(); |
|
|
|
|
|
await ApiChatHandler.streamChat({ |
|
|
response, |
|
|
workspace, |
|
|
message, |
|
|
mode, |
|
|
user, |
|
|
thread, |
|
|
attachments, |
|
|
reset, |
|
|
}); |
|
|
await Telemetry.sendTelemetry("sent_chat", { |
|
|
LLMSelection: process.env.LLM_PROVIDER || "openai", |
|
|
Embedder: process.env.EMBEDDING_ENGINE || "inherit", |
|
|
VectorDbSelection: process.env.VECTOR_DB || "lancedb", |
|
|
TTSSelection: process.env.TTS_PROVIDER || "native", |
|
|
LLMModel: getModelTag(), |
|
|
}); |
|
|
await EventLogs.logEvent("api_sent_chat", { |
|
|
workspaceName: workspace?.name, |
|
|
chatModel: workspace?.chatModel || "System Default", |
|
|
threadName: thread?.name, |
|
|
userId: user?.id, |
|
|
}); |
|
|
response.end(); |
|
|
} catch (e) { |
|
|
console.error(e.message, e); |
|
|
writeResponseChunk(response, { |
|
|
id: uuidv4(), |
|
|
type: "abort", |
|
|
textResponse: null, |
|
|
sources: [], |
|
|
close: true, |
|
|
error: e.message, |
|
|
}); |
|
|
response.end(); |
|
|
} |
|
|
} |
|
|
); |
|
|
} |
|
|
|
|
|
module.exports = { apiWorkspaceThreadEndpoints }; |
|
|
|