mirror of
https://github.com/run-llama/create-llama.git
synced 2026-07-16 11:04:26 -04:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bfde30deed | |||
| 96fdb83abf | |||
| 81bc340dda | |||
| ddf3aef7dc | |||
| 48188ca3f9 |
@@ -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),
|
||||
})),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
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>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
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;
|
||||
};
|
||||
Reference in New Issue
Block a user