Skip to content
Snippets Groups Projects
Unverified Commit 0ad22076 authored by Huu Le's avatar Huu Le Committed by GitHub
Browse files

Merge pull request #98 from run-llama/feat/construct-resource-url-from-backend

feat: construct resource url from backend
parents b7e0072c bfde30de
No related branches found
No related tags found
No related merge requests found
......@@ -15,7 +15,7 @@ export type InterpreterToolParams = {
fileServerURLPrefix?: string;
};
export type InterpreterToolOuput = {
export type InterpreterToolOutput = {
isError: boolean;
logs: Logs;
extraResult: InterpreterExtraResult[];
......@@ -88,7 +88,7 @@ export class InterpreterTool implements BaseTool<InterpreterParameter> {
return this.codeInterpreter;
}
public async codeInterpret(code: string): Promise<InterpreterToolOuput> {
public async codeInterpret(code: string): Promise<InterpreterToolOutput> {
console.log(
`\n${"=".repeat(50)}\n> Running following AI-generated code:\n${code}\n${"=".repeat(50)}`,
);
......@@ -96,7 +96,7 @@ export class InterpreterTool implements BaseTool<InterpreterParameter> {
const exec = await interpreter.notebook.execCell(code);
if (exec.error) console.error("[Code Interpreter error]", exec.error);
const extraResult = await this.getExtraResult(exec.results[0]);
const result: InterpreterToolOuput = {
const result: InterpreterToolOutput = {
isError: !!exec.error,
logs: exec.logs,
extraResult,
......@@ -104,7 +104,7 @@ export class InterpreterTool implements BaseTool<InterpreterParameter> {
return result;
}
async call(input: InterpreterParameter): Promise<InterpreterToolOuput> {
async call(input: InterpreterParameter): Promise<InterpreterToolOutput> {
const result = await this.codeInterpret(input.code);
await this.codeInterpreter?.close();
return result;
......
......@@ -17,6 +17,22 @@ export function appendImageData(data: StreamData, imageUrl?: string) {
});
}
function getNodeUrl(metadata: Metadata) {
const url = metadata["URL"];
if (url) return url;
const fileName = metadata["file_name"];
if (!process.env.FILESERVER_URL_PREFIX) {
console.warn(
"FILESERVER_URL_PREFIX is not set. File URLs will not be generated.",
);
return undefined;
}
if (fileName) {
return `${process.env.FILESERVER_URL_PREFIX}/data/${fileName}`;
}
return undefined;
}
export function appendSourceData(
data: StreamData,
sourceNodes?: NodeWithScore<Metadata>[],
......@@ -29,6 +45,7 @@ export function appendSourceData(
...node.node.toMutableJSON(),
id: node.node.id_,
score: node.score ?? null,
url: getNodeUrl(node.node.metadata),
})),
},
});
......
import os
import logging
from pydantic import BaseModel
from typing import List, Any, Optional, Dict, Tuple
from fastapi import APIRouter, Depends, HTTPException, Request, status
......@@ -11,6 +13,7 @@ from aiostream import stream
chat_router = r = APIRouter()
logger = logging.getLogger("uvicorn")
class _Message(BaseModel):
role: MessageRole
......@@ -38,14 +41,27 @@ class _SourceNodes(BaseModel):
metadata: Dict[str, Any]
score: Optional[float]
text: str
url: Optional[str]
@classmethod
def from_source_node(cls, source_node: NodeWithScore):
metadata = source_node.node.metadata
url = metadata.get("URL")
if not url:
file_name = metadata.get("file_name")
url_prefix = os.getenv("FILESERVER_URL_PREFIX")
if not url_prefix:
logger.warning("Warning: FILESERVER_URL_PREFIX not set in environment variables")
if file_name and url_prefix:
url = f"{url_prefix}/data/{file_name}"
return cls(
id=source_node.node.node_id,
metadata=source_node.node.metadata,
metadata=metadata,
score=source_node.score,
text=source_node.node.text, # type: ignore
url=url
)
@classmethod
......
......@@ -17,6 +17,22 @@ export function appendImageData(data: StreamData, imageUrl?: string) {
});
}
function getNodeUrl(metadata: Metadata) {
const url = metadata["URL"];
if (url) return url;
const fileName = metadata["file_name"];
if (!process.env.FILESERVER_URL_PREFIX) {
console.warn(
"FILESERVER_URL_PREFIX is not set. File URLs will not be generated.",
);
return undefined;
}
if (fileName) {
return `${process.env.FILESERVER_URL_PREFIX}/data/${fileName}`;
}
return undefined;
}
export function appendSourceData(
data: StreamData,
sourceNodes?: NodeWithScore<Metadata>[],
......@@ -29,6 +45,7 @@ export function appendSourceData(
...node.node.toMutableJSON(),
id: node.node.id_,
score: node.score ?? null,
url: getNodeUrl(node.node.metadata),
})),
},
});
......
......@@ -2,12 +2,10 @@ import { Check, Copy } from "lucide-react";
import { useMemo } from "react";
import { Button } from "../button";
import { HoverCard, HoverCardContent, HoverCardTrigger } from "../hover-card";
import { getStaticFileDataUrl } from "../lib/url";
import { SourceData, SourceNode } from "./index";
import { SourceData } from "./index";
import { useCopyToClipboard } from "./use-copy-to-clipboard";
import PdfDialog from "./widgets/PdfDialog";
const DATA_SOURCE_FOLDER = "data";
const SCORE_THRESHOLD = 0.3;
function SourceNumberButton({ index }: { index: number }) {
......@@ -18,46 +16,11 @@ function SourceNumberButton({ index }: { index: number }) {
);
}
enum NODE_TYPE {
URL,
FILE,
UNKNOWN,
}
type NodeInfo = {
id: string;
type: NODE_TYPE;
path?: string;
url?: string;
};
function getNodeInfo(node: SourceNode): NodeInfo {
if (typeof node.metadata["URL"] === "string") {
const url = node.metadata["URL"];
return {
id: node.id,
type: NODE_TYPE.URL,
path: url,
url,
};
}
if (typeof node.metadata["file_path"] === "string") {
const fileName = node.metadata["file_name"] as string;
const filePath = `${DATA_SOURCE_FOLDER}/${fileName}`;
return {
id: node.id,
type: NODE_TYPE.FILE,
path: node.metadata["file_path"],
url: getStaticFileDataUrl(filePath),
};
}
return {
id: node.id,
type: NODE_TYPE.UNKNOWN,
};
}
export function ChatSources({ data }: { data: SourceData }) {
const sources: NodeInfo[] = useMemo(() => {
// aggregate nodes by url or file_path (get the highest one by score)
......@@ -67,8 +30,11 @@ export function ChatSources({ data }: { data: SourceData }) {
.filter((node) => (node.score ?? 1) > SCORE_THRESHOLD)
.sort((a, b) => (b.score ?? 1) - (a.score ?? 1))
.forEach((node) => {
const nodeInfo = getNodeInfo(node);
const key = nodeInfo.path ?? nodeInfo.id; // use id as key for UNKNOWN type
const nodeInfo = {
id: node.id,
url: node.url,
};
const key = nodeInfo.url ?? nodeInfo.id; // use id as key for UNKNOWN type
if (!nodesByPath[key]) {
nodesByPath[key] = nodeInfo;
}
......@@ -84,13 +50,12 @@ export function ChatSources({ data }: { data: SourceData }) {
<span className="font-semibold">Sources:</span>
<div className="inline-flex gap-1 items-center">
{sources.map((nodeInfo: NodeInfo, index: number) => {
if (nodeInfo.path?.endsWith(".pdf")) {
if (nodeInfo.url?.endsWith(".pdf")) {
return (
<PdfDialog
key={nodeInfo.id}
documentId={nodeInfo.id}
url={nodeInfo.url!}
path={nodeInfo.path}
trigger={<SourceNumberButton index={index} />}
/>
);
......@@ -116,16 +81,16 @@ export function ChatSources({ data }: { data: SourceData }) {
function NodeInfo({ nodeInfo }: { nodeInfo: NodeInfo }) {
const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 1000 });
if (nodeInfo.type !== NODE_TYPE.UNKNOWN) {
if (nodeInfo.url) {
// this is a node generated by the web loader or file loader,
// add a link to view its URL and a button to copy the URL to the clipboard
return (
<div className="flex items-center my-2">
<a className="hover:text-blue-900" href={nodeInfo.url} target="_blank">
<span>{nodeInfo.path}</span>
<span>{nodeInfo.url}</span>
</a>
<Button
onClick={() => copyToClipboard(nodeInfo.path!)}
onClick={() => copyToClipboard(nodeInfo.url!)}
size="icon"
variant="ghost"
className="h-12 w-12 shrink-0"
......
......@@ -21,6 +21,7 @@ export type SourceNode = {
metadata: Record<string, unknown>;
score?: number;
text: string;
url?: string;
};
export type SourceData = {
......
......@@ -12,7 +12,6 @@ import {
export interface PdfDialogProps {
documentId: string;
path: string;
url: string;
trigger: React.ReactNode;
}
......@@ -26,13 +25,13 @@ export default function PdfDialog(props: PdfDialogProps) {
<div className="space-y-2">
<DrawerTitle>PDF Content</DrawerTitle>
<DrawerDescription>
File path:{" "}
File URL:{" "}
<a
className="hover:text-blue-900"
href={props.url}
target="_blank"
>
{props.path}
{props.url}
</a>
</DrawerDescription>
</div>
......
const staticFileAPI = "/api/files";
export const getStaticFileDataUrl = (filePath: string) => {
const isUsingBackend = !!process.env.NEXT_PUBLIC_CHAT_API;
const fileUrl = `${staticFileAPI}/${filePath}`;
if (isUsingBackend) {
const backendOrigin = new URL(process.env.NEXT_PUBLIC_CHAT_API!).origin;
return `${backendOrigin}${fileUrl}`;
}
return fileUrl;
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment