add show debug (#7390)

### What problem does this PR solve?

add show debug
![Recording2025-04-28142829-ezgif
com-video-to-gif-converter](https://github.com/user-attachments/assets/0c67da34-c2b6-428f-ae9b-b5b21464885c)

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
so95 2025-05-12 16:15:19 +07:00 committed by GitHub
parent d05e8a173d
commit 514c08a932
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 267 additions and 65 deletions

2
.gitignore vendored
View File

@ -42,3 +42,5 @@ nltk_data/
# Exclude hash-like temporary files like 9b5ad71b2ce5302211f9c61530b329a4922fc6a4
*[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]*
.lh/
.venv
docker/data

View File

@ -17,7 +17,6 @@ import logging
import json
from copy import deepcopy
from functools import partial
import pandas as pd
from agent.component import component_class
@ -362,4 +361,7 @@ class Canvas:
return self.components["begin"]["obj"]._param.query
def get_component_input_elements(self, cpnnm):
return self.components[cpnnm]["obj"].get_input_elements()
return self.components[cpnnm]["obj"].get_input_elements()
def set_component_infor(self, cpn_id, infor):
self.components[cpn_id]["obj"].set_infor(infor)

View File

@ -34,6 +34,7 @@ _IS_RAW_CONF = "_is_raw_conf"
class ComponentParamBase(ABC):
def __init__(self):
self.output_var_name = "output"
self.infor_var_name = "infor"
self.message_history_window_size = 22
self.query = []
self.inputs = []
@ -462,6 +463,9 @@ class ComponentBase(ABC):
def set_output(self, v):
setattr(self._param, self._param.output_var_name, v)
def set_infor(self, v):
setattr(self._param, self._param.infor_var_name, v)
def _fetch_outputs_from(self, sources: list[dict[str, Any]]) -> list[pd.DataFrame]:
outs = []
for q in sources:
@ -488,7 +492,6 @@ class ComponentBase(ABC):
elif q.get("value"):
outs.append(pd.DataFrame([{"content": q["value"]}]))
return outs
def get_input(self):
if self._param.debug_inputs:
return pd.DataFrame([{"content": v["value"]} for v in self._param.debug_inputs if v.get("value")])

View File

@ -85,6 +85,8 @@ class Categorize(Generate, ABC):
input = self.get_input()
input = " - ".join(input["content"]) if "content" in input else ""
chat_mdl = LLMBundle(self._canvas.get_tenant_id(), LLMType.CHAT, self._param.llm_id)
self._canvas.set_component_infor(self._id, {"prompt":self._param.get_prompt(input),"messages": [{"role": "user", "content": "\nCategory: "}],"conf": self._param.gen_conf()})
ans = chat_mdl.chat(self._param.get_prompt(input), [{"role": "user", "content": "\nCategory: "}],
self._param.gen_conf())
logging.debug(f"input: {input}, answer: {str(ans)}")

View File

@ -201,7 +201,7 @@ class Generate(ComponentBase):
msg.append({"role": "user", "content": "Output: "})
ans = chat_mdl.chat(msg[0]["content"], msg[1:], self._param.gen_conf())
ans = re.sub(r"^.*</think>", "", ans, flags=re.DOTALL)
self._canvas.set_component_infor(self._id, {"prompt":msg[0]["content"],"messages": msg[1:],"conf": self._param.gen_conf()})
if self._param.cite and "chunks" in retrieval_res.columns:
res = self.set_cite(retrieval_res, ans)
return pd.DataFrame([res])
@ -234,7 +234,7 @@ class Generate(ComponentBase):
if self._param.cite and "chunks" in retrieval_res.columns:
res = self.set_cite(retrieval_res, answer)
yield res
self._canvas.set_component_infor(self._id, {"prompt":msg[0]["content"],"messages": msg[1:],"conf": self._param.gen_conf()})
self.set_output(Generate.be_output(res))
def debug(self, **kwargs):

View File

@ -58,6 +58,8 @@ class KeywordExtract(Generate, ABC):
chat_mdl = LLMBundle(self._canvas.get_tenant_id(), LLMType.CHAT, self._param.llm_id)
self._canvas.set_component_infor(self._id, {"prompt":self._param.get_prompt(),"messages": [{"role": "user", "content": query}],"conf": self._param.gen_conf()})
ans = chat_mdl.chat(self._param.get_prompt(), [{"role": "user", "content": query}],
self._param.gen_conf())

View File

@ -26,7 +26,6 @@ from api.utils.api_utils import get_json_result, server_error_response, validate
from agent.canvas import Canvas
from peewee import MySQLDatabase, PostgresqlDatabase
from api.db.db_models import APIToken
import logging
import time
@manager.route('/templates', methods=['GET']) # noqa: F821
@ -89,7 +88,6 @@ def save():
@login_required
def get(canvas_id):
e, c = UserCanvasService.get_by_tenant_id(canvas_id)
logging.info(f"get canvas_id: {canvas_id} c: {c}")
if not e:
return get_data_error_result(message="canvas not found.")
return get_json_result(data=c)

View File

@ -1258,6 +1258,7 @@ This delimiter is used to split the input text into several text pieces echo of
promptTip:
'Use the system prompt to describe the task for the LLM, specify how it should respond, and outline other miscellaneous requirements. The system prompt is often used in conjunction with keys (variables), which serve as various data inputs for the LLM. Use a forward slash `/` or the (x) button to show the keys to use.',
promptMessage: 'Prompt is required',
infor: 'Information run',
knowledgeBasesTip:
'Select the knowledge bases to associate with this chat assistant, or choose variables containing knowledge base IDs below.',
knowledgeBaseVars: 'Knowledge base variables',

View File

@ -1,11 +1,15 @@
import { useFetchFlow } from '@/hooks/flow-hooks';
import get from 'lodash/get';
import React, { MouseEventHandler, useCallback, useMemo } from 'react';
import React, {
MouseEventHandler,
useCallback,
useMemo,
useState,
} from 'react';
import JsonView from 'react18-json-view';
import 'react18-json-view/src/style.css';
import { useReplaceIdWithText } from '../../hooks';
import { useTheme } from '@/components/theme-provider';
import {
Popover,
PopoverContent,
@ -20,6 +24,17 @@ import {
TableRow,
} from '@/components/ui/table';
import { useTranslate } from '@/hooks/common-hooks';
import {
Button,
Card,
Col,
Input,
Row,
Space,
Tabs,
Typography,
message,
} from 'antd';
import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query';
interface IProps extends React.PropsWithChildren {
@ -31,7 +46,8 @@ export function NextNodePopover({ children, nodeId, name }: IProps) {
const { t } = useTranslate('flow');
const { data } = useFetchFlow();
const { theme } = useTheme();
console.log(data);
const component = useMemo(() => {
return get(data, ['dsl', 'components', nodeId], {});
}, [nodeId, data]);
@ -42,6 +58,11 @@ export function NextNodePopover({ children, nodeId, name }: IProps) {
[],
);
const output = get(component, ['obj', 'output'], {});
const { conf, messages, prompt } = get(
component,
['obj', 'params', 'infor'],
{},
);
const { replacedOutput } = useReplaceIdWithText(output);
const stopPropagation: MouseEventHandler = useCallback((e) => {
e.stopPropagation();
@ -49,6 +70,13 @@ export function NextNodePopover({ children, nodeId, name }: IProps) {
const getLabel = useGetComponentLabelByValue(nodeId);
const [inputPage, setInputPage] = useState(1);
const pageSize = 3;
const pagedInputs = inputs.slice(
(inputPage - 1) * pageSize,
inputPage * pageSize,
);
return (
<Popover>
<PopoverTrigger onClick={stopPropagation} asChild>
@ -59,62 +87,226 @@ export function NextNodePopover({ children, nodeId, name }: IProps) {
side={'right'}
sideOffset={20}
onClick={stopPropagation}
className="w-[400px]"
className="w-[800px] p-4"
style={{ maxHeight: 600, overflow: 'auto' }}
>
<div className="mb-3 font-semibold text-[16px]">
{name} {t('operationResults')}
</div>
<div className="flex w-full gap-4 flex-col">
<div className="flex flex-col space-y-1.5">
<span className="font-semibold text-[14px]">{t('input')}</span>
<div
style={
theme === 'dark'
? {
backgroundColor: 'rgba(150, 150, 150, 0.2)',
}
: {}
}
className={`bg-gray-100 p-1 rounded`}
>
<Table>
<TableHeader>
<TableRow>
<TableHead>{t('componentId')}</TableHead>
<TableHead className="w-[60px]">{t('content')}</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{inputs.map((x, idx) => (
<TableRow key={idx}>
<TableCell>{getLabel(x.component_id)}</TableCell>
<TableCell className="truncate">{x.content}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
<div className="flex flex-col space-y-1.5">
<span className="font-semibold text-[14px]">{t('output')}</span>
<div
style={
theme === 'dark'
? {
backgroundColor: 'rgba(150, 150, 150, 0.2)',
}
: {}
}
className="bg-gray-100 p-1 rounded"
>
<JsonView
src={replacedOutput}
displaySize={30}
className="w-full max-h-[300px] break-words overflow-auto"
/>
</div>
</div>
</div>
<Card
bordered={false}
style={{ marginBottom: 16, padding: 0 }}
bodyStyle={{ padding: 0 }}
>
<Typography.Title
level={5}
style={{
marginBottom: 16,
fontWeight: 600,
fontSize: 18,
borderBottom: '1px solid #f0f0f0',
paddingBottom: 8,
}}
>
{name} {t('operationResults')}
</Typography.Title>
</Card>
<Tabs
defaultActiveKey="input"
items={[
{
key: 'input',
label: t('input'),
children: (
<Card
size="small"
className="bg-gray-50 dark:bg-gray-800"
style={{ borderRadius: 8, border: '1px solid #e5e7eb' }}
bodyStyle={{ padding: 16 }}
>
<Table>
<TableHeader>
<TableRow>
<TableHead>{t('componentId')}</TableHead>
<TableHead className="w-[60px]">
{t('content')}
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{pagedInputs.map((x, idx) => (
<TableRow key={idx + (inputPage - 1) * pageSize}>
<TableCell>{getLabel(x.component_id)}</TableCell>
<TableCell className="truncate">
{x.content}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
{/* Pagination */}
{inputs.length > pageSize && (
<Row justify="end" style={{ marginTop: 8 }}>
<Space>
<Button
size="small"
disabled={inputPage === 1}
onClick={() => setInputPage(inputPage - 1)}
>
Prev
</Button>
<span className="mx-2 text-sm">
{inputPage} / {Math.ceil(inputs.length / pageSize)}
</span>
<Button
size="small"
disabled={
inputPage === Math.ceil(inputs.length / pageSize)
}
onClick={() => setInputPage(inputPage + 1)}
>
Next
</Button>
</Space>
</Row>
)}
</Card>
),
},
{
key: 'output',
label: t('output'),
children: (
<Card
size="small"
className="bg-gray-50 dark:bg-gray-800"
style={{ borderRadius: 8, border: '1px solid #e5e7eb' }}
bodyStyle={{ padding: 16 }}
>
<JsonView
src={replacedOutput}
displaySize={30}
className="w-full max-h-[300px] break-words overflow-auto"
/>
</Card>
),
},
{
key: 'infor',
label: t('infor'),
children: (
<Card
size="small"
className="bg-gray-50 dark:bg-gray-800"
style={{ borderRadius: 8, border: '1px solid #e5e7eb' }}
bodyStyle={{ padding: 16 }}
>
<Row gutter={16}>
<Col span={12}>
{conf && (
<Card
size="small"
bordered={false}
style={{
marginBottom: 16,
background: 'transparent',
}}
bodyStyle={{ padding: 0 }}
>
<Typography.Text
strong
style={{
color: '#888',
marginBottom: 8,
display: 'block',
}}
>
Configuration:
</Typography.Text>
<JsonView
src={conf}
displaySize={30}
className="w-full max-h-[120px] break-words overflow-auto"
/>
</Card>
)}
{prompt && (
<Card
size="small"
bordered={false}
style={{ background: 'transparent' }}
bodyStyle={{ padding: 0 }}
>
<Row
align="middle"
justify="space-between"
style={{ marginBottom: 8 }}
>
<Col>
<Typography.Text strong style={{ color: '#888' }}>
Prompt:
</Typography.Text>
</Col>
<Col>
<Button
size="small"
onClick={() => {
const inlineString = prompt
.replace(/\s+/g, ' ')
.trim();
navigator.clipboard.writeText(inlineString);
message.success(
'Prompt copied as single line!',
);
}}
>
Copy as single line
</Button>
</Col>
</Row>
<Input.TextArea
value={prompt}
readOnly
autoSize={{ minRows: 2, maxRows: 6 }}
className="bg-white dark:bg-gray-900 border-gray-200 dark:border-gray-700"
/>
</Card>
)}
</Col>
<Col span={12}>
{messages && (
<Card
size="small"
bordered={false}
style={{
marginBottom: 16,
background: 'transparent',
}}
bodyStyle={{ padding: 0 }}
>
<Typography.Text
strong
style={{
color: '#888',
marginBottom: 8,
display: 'block',
}}
>
Messages:
</Typography.Text>
<div className="max-h-[300px] overflow-auto">
<JsonView
src={messages}
displaySize={30}
className="w-full break-words"
/>
</div>
</Card>
)}
</Col>
</Row>
</Card>
),
},
]}
/>
</PopoverContent>
</Popover>
);