mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-02-02 16:45:08 +08:00
Compare commits
5 Commits
3ee9653170
...
618d6bc924
| Author | SHA1 | Date | |
|---|---|---|---|
| 618d6bc924 | |||
| 762aa4b8c4 | |||
| 9cd09488ca | |||
| f2806a8332 | |||
| b6e34e3aa7 |
@ -108,7 +108,9 @@ class Retrieval(ToolBase, ABC):
|
||||
if self._param.rerank_id:
|
||||
rerank_mdl = LLMBundle(kbs[0].tenant_id, LLMType.RERANK, self._param.rerank_id)
|
||||
|
||||
query = kwargs["query"]
|
||||
vars = self.get_input_elements_from_text(kwargs["query"])
|
||||
vars = {k:o["value"] for k,o in vars.items()}
|
||||
query = self.string_format(kwargs["query"], vars)
|
||||
if self._param.cross_languages:
|
||||
query = cross_languages(kbs[0].tenant_id, None, query, self._param.cross_languages)
|
||||
|
||||
|
||||
@ -73,7 +73,7 @@ dependencies = [
|
||||
"pyclipper==1.3.0.post5",
|
||||
"pycryptodomex==3.20.0",
|
||||
"pymysql>=1.1.1,<2.0.0",
|
||||
"pypdf>=5.0.0,<6.0.0",
|
||||
"pypdf==6.0.0",
|
||||
"python-dotenv==1.0.1",
|
||||
"python-dateutil==2.8.2",
|
||||
"python-pptx>=1.0.2,<2.0.0",
|
||||
|
||||
@ -68,7 +68,7 @@ class Base(ABC):
|
||||
pmpt.append({
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": f"data:image/jpeg;base64,{img}" if img[:4] != "data" else img
|
||||
"url": img if isinstance(img, str) and img.startswith("data:") else f"data:image/png;base64,{img}"
|
||||
}
|
||||
})
|
||||
return pmpt
|
||||
@ -109,16 +109,33 @@ class Base(ABC):
|
||||
|
||||
@staticmethod
|
||||
def image2base64(image):
|
||||
# Return a data URL with the correct MIME to avoid provider mismatches
|
||||
if isinstance(image, bytes):
|
||||
return base64.b64encode(image).decode("utf-8")
|
||||
# Best-effort magic number sniffing
|
||||
mime = "image/png"
|
||||
if len(image) >= 2 and image[0] == 0xFF and image[1] == 0xD8:
|
||||
mime = "image/jpeg"
|
||||
b64 = base64.b64encode(image).decode("utf-8")
|
||||
return f"data:{mime};base64,{b64}"
|
||||
if isinstance(image, BytesIO):
|
||||
return base64.b64encode(image.getvalue()).decode("utf-8")
|
||||
data = image.getvalue()
|
||||
mime = "image/png"
|
||||
if len(data) >= 2 and data[0] == 0xFF and data[1] == 0xD8:
|
||||
mime = "image/jpeg"
|
||||
b64 = base64.b64encode(data).decode("utf-8")
|
||||
return f"data:{mime};base64,{b64}"
|
||||
buffered = BytesIO()
|
||||
fmt = "JPEG"
|
||||
try:
|
||||
image.save(buffered, format="JPEG")
|
||||
except Exception:
|
||||
buffered = BytesIO() # reset buffer before saving PNG
|
||||
image.save(buffered, format="PNG")
|
||||
return base64.b64encode(buffered.getvalue()).decode("utf-8")
|
||||
fmt = "PNG"
|
||||
data = buffered.getvalue()
|
||||
b64 = base64.b64encode(data).decode("utf-8")
|
||||
mime = f"image/{fmt.lower()}"
|
||||
return f"data:{mime};base64,{b64}"
|
||||
|
||||
def prompt(self, b64):
|
||||
return [
|
||||
@ -372,6 +389,16 @@ class OllamaCV(Base):
|
||||
self.keep_alive = kwargs.get("ollama_keep_alive", int(os.environ.get("OLLAMA_KEEP_ALIVE", -1)))
|
||||
Base.__init__(self, **kwargs)
|
||||
|
||||
|
||||
def _clean_img(self, img):
|
||||
if not isinstance(img, str):
|
||||
return img
|
||||
|
||||
#remove the header like "data/*;base64,"
|
||||
if img.startswith("data:") and ";base64," in img:
|
||||
img = img.split(";base64,")[1]
|
||||
return img
|
||||
|
||||
def _clean_conf(self, gen_conf):
|
||||
options = {}
|
||||
if "temperature" in gen_conf:
|
||||
@ -390,9 +417,12 @@ class OllamaCV(Base):
|
||||
hist.insert(0, {"role": "system", "content": system})
|
||||
if not images:
|
||||
return hist
|
||||
temp_images = []
|
||||
for img in images:
|
||||
temp_images.append(self._clean_img(img))
|
||||
for his in hist:
|
||||
if his["role"] == "user":
|
||||
his["images"] = images
|
||||
his["images"] = temp_images
|
||||
break
|
||||
return hist
|
||||
|
||||
@ -661,8 +691,8 @@ class AnthropicCV(Base):
|
||||
"type": "image",
|
||||
"source": {
|
||||
"type": "base64",
|
||||
"media_type": "image/jpeg" if img[:4] != "data" else img.split(":")[1].split(";")[0],
|
||||
"data": img if img[:4] != "data" else img.split(",")[1]
|
||||
"media_type": (img.split(":")[1].split(";")[0] if isinstance(img, str) and img[:4] == "data" else "image/png"),
|
||||
"data": (img.split(",")[1] if isinstance(img, str) and img[:4] == "data" else img)
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
87
uv.lock
generated
87
uv.lock
generated
@ -1,5 +1,4 @@
|
||||
version = 1
|
||||
revision = 1
|
||||
requires-python = ">=3.10, <3.13"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.12' and sys_platform == 'darwin'",
|
||||
@ -1190,6 +1189,9 @@ name = "datrie"
|
||||
version = "0.8.2"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/9d/fe/db74bd405d515f06657f11ad529878fd389576dca4812bea6f98d9b31574/datrie-0.8.2.tar.gz", hash = "sha256:525b08f638d5cf6115df6ccd818e5a01298cd230b2dac91c8ff2e6499d18765d" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/44/02/53f0cf0bf0cd629ba6c2cc13f2f9db24323459e9c19463783d890a540a96/datrie-0.8.2-pp273-pypy_73-win32.whl", hash = "sha256:b07bd5fdfc3399a6dab86d6e35c72b1dbd598e80c97509c7c7518ab8774d3fda" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "debugpy"
|
||||
@ -2162,37 +2164,37 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "greenlet"
|
||||
version = "3.2.3"
|
||||
version = "3.2.4"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/c9/92/bb85bd6e80148a4d2e0c59f7c0c2891029f8fd510183afc7d8d2feeed9b6/greenlet-3.2.3.tar.gz", hash = "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/92/db/b4c12cff13ebac2786f4f217f06588bccd8b53d260453404ef22b121fc3a/greenlet-3.2.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:1afd685acd5597349ee6d7a88a8bec83ce13c106ac78c196ee9dde7c04fe87be" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/52/61/75b4abd8147f13f70986df2801bf93735c1bd87ea780d70e3b3ecda8c165/greenlet-3.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:761917cac215c61e9dc7324b2606107b3b292a8349bdebb31503ab4de3f559ac" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/35/aa/6894ae299d059d26254779a5088632874b80ee8cf89a88bca00b0709d22f/greenlet-3.2.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a433dbc54e4a37e4fff90ef34f25a8c00aed99b06856f0119dcf09fbafa16392" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/30/64/e01a8261d13c47f3c082519a5e9dbf9e143cc0498ed20c911d04e54d526c/greenlet-3.2.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:72e77ed69312bab0434d7292316d5afd6896192ac4327d44f3d613ecb85b037c" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/47/48/ff9ca8ba9772d083a4f5221f7b4f0ebe8978131a9ae0909cf202f94cd879/greenlet-3.2.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:68671180e3849b963649254a882cd544a3c75bfcd2c527346ad8bb53494444db" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e9/45/626e974948713bc15775b696adb3eb0bd708bec267d6d2d5c47bb47a6119/greenlet-3.2.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49c8cfb18fb419b3d08e011228ef8a25882397f3a859b9fe1436946140b6756b" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/b1/8e/8b6f42c67d5df7db35b8c55c9a850ea045219741bb14416255616808c690/greenlet-3.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:efc6dc8a792243c31f2f5674b670b3a95d46fa1c6a912b8e310d6f542e7b0712" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/05/46/ab58828217349500a7ebb81159d52ca357da747ff1797c29c6023d79d798/greenlet-3.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:731e154aba8e757aedd0781d4b240f1225b075b4409f1bb83b05ff410582cf00" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/68/7f/d1b537be5080721c0f0089a8447d4ef72839039cdb743bdd8ffd23046e9a/greenlet-3.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:96c20252c2f792defe9a115d3287e14811036d51e78b3aaddbee23b69b216302" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/fc/2e/d4fcb2978f826358b673f779f78fa8a32ee37df11920dc2bb5589cbeecef/greenlet-3.2.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:784ae58bba89fa1fa5733d170d42486580cab9decda3484779f4759345b29822" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/16/24/929f853e0202130e4fe163bc1d05a671ce8dcd604f790e14896adac43a52/greenlet-3.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0921ac4ea42a5315d3446120ad48f90c3a6b9bb93dd9b3cf4e4d84a66e42de83" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d1/b2/0320715eb61ae70c25ceca2f1d5ae620477d246692d9cc284c13242ec31c/greenlet-3.2.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d2971d93bb99e05f8c2c0c2f4aa9484a18d98c4c3bd3c62b65b7e6ae33dfcfaf" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/bd/49/445fd1a210f4747fedf77615d941444349c6a3a4a1135bba9701337cd966/greenlet-3.2.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c667c0bf9d406b77a15c924ef3285e1e05250948001220368e039b6aa5b5034b" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/7e/c8/ca19760cf6eae75fa8dc32b487e963d863b3ee04a7637da77b616703bc37/greenlet-3.2.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:592c12fb1165be74592f5de0d70f82bc5ba552ac44800d632214b76089945147" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/65/89/77acf9e3da38e9bcfca881e43b02ed467c1dedc387021fc4d9bd9928afb8/greenlet-3.2.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29e184536ba333003540790ba29829ac14bb645514fbd7e32af331e8202a62a5" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/97/c6/ae244d7c95b23b7130136e07a9cc5aadd60d59b5951180dc7dc7e8edaba7/greenlet-3.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:93c0bb79844a367782ec4f429d07589417052e621aa39a5ac1fb99c5aa308edc" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/89/5f/b16dec0cbfd3070658e0d744487919740c6d45eb90946f6787689a7efbce/greenlet-3.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:751261fc5ad7b6705f5f76726567375bb2104a059454e0226e1eef6c756748ba" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/66/77/d48fb441b5a71125bcac042fc5b1494c806ccb9a1432ecaa421e72157f77/greenlet-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:83a8761c75312361aa2b5b903b79da97f13f556164a7dd2d5448655425bd4c34" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/f3/94/ad0d435f7c48debe960c53b8f60fb41c2026b1d0fa4a99a1cb17c3461e09/greenlet-3.2.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:25ad29caed5783d4bd7a85c9251c651696164622494c00802a139c00d639242d" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/93/5d/7c27cf4d003d6e77749d299c7c8f5fd50b4f251647b5c2e97e1f20da0ab5/greenlet-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88cd97bf37fe24a6710ec6a3a7799f3f81d9cd33317dcf565ff9950c83f55e0b" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/c6/7e/807e1e9be07a125bb4c169144937910bf59b9d2f6d931578e57f0bce0ae2/greenlet-3.2.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:baeedccca94880d2f5666b4fa16fc20ef50ba1ee353ee2d7092b383a243b0b0d" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/9d/ab/158c1a4ea1068bdbc78dba5a3de57e4c7aeb4e7fa034320ea94c688bfb61/greenlet-3.2.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:be52af4b6292baecfa0f397f3edb3c6092ce071b499dd6fe292c9ac9f2c8f264" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/cc/0d/93729068259b550d6a0288da4ff72b86ed05626eaf1eb7c0d3466a2571de/greenlet-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0cc73378150b8b78b0c9fe2ce56e166695e67478550769536a6742dca3651688" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/f6/f6/c82ac1851c60851302d8581680573245c8fc300253fc1ff741ae74a6c24d/greenlet-3.2.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:706d016a03e78df129f68c4c9b4c4f963f7d73534e48a24f5f5a7101ed13dbbb" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/98/82/d022cf25ca39cf1200650fc58c52af32c90f80479c25d1cbf57980ec3065/greenlet-3.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:419e60f80709510c343c57b4bb5a339d8767bf9aef9b8ce43f4f143240f88b7c" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/f5/e1/25297f70717abe8104c20ecf7af0a5b82d2f5a980eb1ac79f65654799f9f/greenlet-3.2.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:93d48533fade144203816783373f27a97e4193177ebaaf0fc396db19e5d61163" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/1f/8f/8f9e56c5e82eb2c26e8cde787962e66494312dc8cb261c460e1f3a9c88bc/greenlet-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:7454d37c740bb27bdeddfc3f358f26956a07d5220818ceb467a483197d84f849" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/7d/ed/6bfa4109fcb23a58819600392564fea69cdc6551ffd5e69ccf1d52a40cbc/greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/2a/fc/102ec1a2fc015b3a7652abab7acf3541d58c04d3d17a8d3d6a44adae1eb1/greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/c5/26/80383131d55a4ac0fb08d71660fd77e7660b9db6bdb4e8884f46d9f2cc04/greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/9f/7c/e7833dbcd8f376f3326bd728c845d31dcde4c84268d3921afcae77d90d08/greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e9/49/547b93b7c0428ede7b3f309bc965986874759f7d89e4e04aeddbc9699acb/greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/7f/91/ae2eb6b7979e2f9b035a9f612cf70f1bf54aad4e1d125129bef1eae96f19/greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/f7/85/433de0c9c0252b22b16d413c9407e6cb3b41df7389afc366ca204dbc1393/greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/a1/8d/88f3ebd2bc96bf7747093696f4335a0a8a4c5acfcf1b757717c0d2474ba3/greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d6/6f/b60b0291d9623c496638c582297ead61f43c4b72eef5e9c926ef4565ec13/greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/09/16/2c3792cba130000bf2a31c5272999113f4764fd9d874fb257ff588ac779a/greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/ae/8f/95d48d7e3d433e6dae5b1682e4292242a53f22df82e6d3dda81b1701a960/greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d5/5e/405965351aef8c76b8ef7ad370e5da58d57ef6068df197548b015464001a/greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/25/5d/382753b52006ce0218297ec1b628e048c4e64b155379331f25a7316eb749/greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/3f/cc/b07000438a29ac5cfb2194bfc128151d52f333cee74dd7dfe3fb733fc16c/greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/3b/16/035dcfcc48715ccd345f3a93183267167cdd162ad123cd93067d86f27ce4/greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/31/da/0386695eef69ffae1ad726881571dfe28b41970173947e7c558d9998de0f/greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/68/88/69bf19fd4dc19981928ceacbc5fd4bb6bc2215d53199e367832e98d1d8fe/greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2877,7 +2879,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "litellm"
|
||||
version = "1.75.0"
|
||||
version = "1.75.5.post1"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
@ -2892,9 +2894,9 @@ dependencies = [
|
||||
{ name = "tiktoken" },
|
||||
{ name = "tokenizers" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/1b/28/50837cb0246c42a8caac45610572883de7f478543cf4d143e84f099c0234/litellm-1.75.0.tar.gz", hash = "sha256:ec7fbfe79e1b9cd4a2b36ca9e71e71959d8fc43305b222e5f257aced1a0d1d63" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/10/97/6091a020895102a20f1da204ebe68c1293123555476b38e749f95ba5981c/litellm-1.75.5.post1.tar.gz", hash = "sha256:e40a0e4b25032755dc5df7f02742abe9e3b8836236363f605f3bdd363cb5a0d0" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/db/43/e10905870d42e927de3b095a9248f2764156c7eb45ec172d72be35cd2bb4/litellm-1.75.0-py3-none-any.whl", hash = "sha256:1657472f37d291b366050dd2035e3640eebd96142d6fa0f935ceb290a0e1d5ad" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/8f/76/780f68a3b26227136a5147c76860aacedcae9bf1b7afc1c991ec9cad11bc/litellm-1.75.5.post1-py3-none-any.whl", hash = "sha256:1c72809a9c8f6e132ad06eb7e628f674c775b0ce6bfb58cbd37e8b585d929cb7" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3803,7 +3805,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "openai"
|
||||
version = "1.99.1"
|
||||
version = "1.99.9"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
@ -3815,9 +3817,9 @@ dependencies = [
|
||||
{ name = "tqdm" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/03/30/f0fb7907a77e733bb801c7bdcde903500b31215141cdb261f04421e6fbec/openai-1.99.1.tar.gz", hash = "sha256:2c9d8e498c298f51bb94bcac724257a3a6cac6139ccdfc1186c6708f7a93120f" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/8a/d2/ef89c6f3f36b13b06e271d3cc984ddd2f62508a0972c1cbcc8485a6644ff/openai-1.99.9.tar.gz", hash = "sha256:f2082d155b1ad22e83247c3de3958eb4255b20ccf4a1de2e6681b6957b554e92" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/54/15/9c85154ffd283abfc43309ff3aaa63c3fd02f7767ee684e73670f6c5ade2/openai-1.99.1-py3-none-any.whl", hash = "sha256:8eeccc69e0ece1357b51ca0d9fb21324afee09b20c3e5b547d02445ca18a4e03" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e8/fb/df274ca10698ee77b07bff952f302ea627cc12dac6b85289485dd77db6de/openai-1.99.9-py3-none-any.whl", hash = "sha256:9dbcdb425553bae1ac5d947147bebbd630d91bbfc7788394d4c4f3a35682ab3a" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4662,6 +4664,8 @@ wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/59/fe/aae679b64363eb78326c7fdc9d06ec3de18bac68be4b612fc1fe8902693c/pycryptodome-3.23.0-cp37-abi3-win32.whl", hash = "sha256:507dbead45474b62b2bbe318eb1c4c8ee641077532067fec9c1aa82c31f84886" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/54/2f/e97a1b8294db0daaa87012c24a7bb714147c7ade7656973fd6c736b484ff/pycryptodome-3.23.0-cp37-abi3-win_amd64.whl", hash = "sha256:c75b52aacc6c0c260f204cbdd834f76edc9fb0d8e0da9fbf8352ef58202564e2" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/18/3d/f9441a0d798bf2b1e645adc3265e55706aead1255ccdad3856dbdcffec14/pycryptodome-3.23.0-cp37-abi3-win_arm64.whl", hash = "sha256:11eeeb6917903876f134b56ba11abe95c0b0fd5e3330def218083c7d98bbcb3c" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/9f/7c/f5b0556590e7b4e710509105e668adb55aa9470a9f0e4dea9c40a4a11ce1/pycryptodome-3.23.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:350ebc1eba1da729b35ab7627a833a1a355ee4e852d8ba0447fafe7b14504d56" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/33/38/dcc795578d610ea1aaffef4b148b8cafcfcf4d126b1e58231ddc4e475c70/pycryptodome-3.23.0-pp27-pypy_73-win32.whl", hash = "sha256:93837e379a3e5fd2bb00302a47aee9fdf7940d83595be3915752c74033d17ca7" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d9/12/e33935a0709c07de084d7d58d330ec3f4daf7910a18e77937affdb728452/pycryptodome-3.23.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ddb95b49df036ddd264a0ad246d1be5b672000f12d6961ea2c267083a5e19379" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/22/0b/aa8f9419f25870889bebf0b26b223c6986652bdf071f000623df11212c90/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e95564beb8782abfd9e431c974e14563a794a4944c29d6d3b7b5ea042110b4" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/d4/5e/63f5cbde2342b7f70a39e591dbe75d9809d6338ce0b07c10406f1a140cdc/pycryptodome-3.23.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14e15c081e912c4b0d75632acd8382dfce45b258667aa3c67caf7a4d4c13f630" },
|
||||
@ -4685,6 +4689,8 @@ wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/48/7d/0f2b09490b98cc6a902ac15dda8760c568b9c18cfe70e0ef7a16de64d53a/pycryptodomex-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:7a7a8f33a1f1fb762ede6cc9cbab8f2a9ba13b196bfaf7bc6f0b39d2ba315a43" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/b0/1c/375adb14b71ee1c8d8232904e928b3e7af5bbbca7c04e4bec94fe8e90c3d/pycryptodomex-3.20.0-cp35-abi3-win32.whl", hash = "sha256:c39778fd0548d78917b61f03c1fa8bfda6cfcf98c767decf360945fe6f97461e" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/b2/e8/1b92184ab7e5595bf38000587e6f8cf9556ebd1bf0a583619bee2057afbd/pycryptodomex-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:2a47bcc478741b71273b917232f521fd5704ab4b25d301669879e7273d3586cc" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/e7/c5/9140bb867141d948c8e242013ec8a8011172233c898dfdba0a2417c3169a/pycryptodomex-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:1be97461c439a6af4fe1cf8bf6ca5936d3db252737d2f379cc6b2e394e12a458" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/5e/6a/04acb4978ce08ab16890c70611ebc6efd251681341617bbb9e53356dee70/pycryptodomex-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:19764605feea0df966445d46533729b645033f134baeb3ea26ad518c9fdf212c" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/eb/df/3f1ea084e43b91e6d2b6b3493cc948864c17ea5d93ff1261a03812fbfd1a/pycryptodomex-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e497413560e03421484189a6b65e33fe800d3bd75590e6d78d4dfdb7accf3b" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/c9/f3/83ffbdfa0c8f9154bcd8866895f6cae5a3ec749da8b0840603cf936c4412/pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48217c7901edd95f9f097feaa0388da215ed14ce2ece803d3f300b4e694abea" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/c9/9d/c113e640aaf02af5631ae2686b742aac5cd0e1402b9d6512b1c7ec5ef05d/pycryptodomex-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d00fe8596e1cc46b44bf3907354e9377aa030ec4cd04afbbf6e899fc1e2a7781" },
|
||||
@ -4920,14 +4926,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pypdf"
|
||||
version = "5.9.0"
|
||||
version = "6.0.0"
|
||||
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/89/3a/584b97a228950ed85aec97c811c68473d9b8d149e6a8c155668287cf1a28/pypdf-5.9.0.tar.gz", hash = "sha256:30f67a614d558e495e1fbb157ba58c1de91ffc1718f5e0dfeb82a029233890a1" }
|
||||
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/20/ac/a300a03c3b34967c050677ccb16e7a4b65607ee5df9d51e8b6d713de4098/pypdf-6.0.0.tar.gz", hash = "sha256:282a99d2cc94a84a3a3159f0d9358c0af53f85b4d28d76ea38b96e9e5ac2a08d" }
|
||||
wheels = [
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/48/d9/6cff57c80a6963e7dd183bf09e9f21604a77716644b1e580e97b259f7612/pypdf-5.9.0-py3-none-any.whl", hash = "sha256:be10a4c54202f46d9daceaa8788be07aa8cd5ea8c25c529c50dd509206382c35" },
|
||||
{ url = "https://mirrors.aliyun.com/pypi/packages/2c/83/2cacc506eb322bb31b747bc06ccb82cc9aa03e19ee9c1245e538e49d52be/pypdf-6.0.0-py3-none-any.whl", hash = "sha256:56ea60100ce9f11fc3eec4f359da15e9aec3821b036c1f06d2b660d35683abb8" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5486,7 +5492,7 @@ requires-dist = [
|
||||
{ name = "pyicu", specifier = ">=2.13.1,<3.0.0" },
|
||||
{ name = "pymysql", specifier = ">=1.1.1,<2.0.0" },
|
||||
{ name = "pyodbc", specifier = ">=5.2.0,<6.0.0" },
|
||||
{ name = "pypdf", specifier = ">=5.0.0,<6.0.0" },
|
||||
{ name = "pypdf", specifier = "===6.0.0" },
|
||||
{ name = "pypdf2", specifier = ">=3.0.1,<4.0.0" },
|
||||
{ name = "python-calamine", specifier = ">=0.4.0" },
|
||||
{ name = "python-dateutil", specifier = "==2.8.2" },
|
||||
@ -5533,7 +5539,6 @@ requires-dist = [
|
||||
{ name = "yfinance", specifier = "==0.2.65" },
|
||||
{ name = "zhipuai", specifier = "==2.0.1" },
|
||||
]
|
||||
provides-extras = ["full"]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
test = [
|
||||
|
||||
@ -202,7 +202,7 @@ export const useSendMessageWithSse = (
|
||||
[Authorization]: getAuthorization(),
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
body: JSON.stringify(omit(body, 'chatBoxId')),
|
||||
signal: controller?.signal || sseRef.current?.signal,
|
||||
});
|
||||
|
||||
@ -228,6 +228,7 @@ export const useSendMessageWithSse = (
|
||||
setAnswer({
|
||||
...d,
|
||||
conversationId: body?.conversation_id,
|
||||
chatBoxId: body.chatBoxId,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@ -82,6 +82,7 @@ export interface Message {
|
||||
audio_binary?: string;
|
||||
data?: any;
|
||||
files?: File[];
|
||||
chatBoxId?: string;
|
||||
}
|
||||
|
||||
export interface IReferenceChunk {
|
||||
@ -117,6 +118,7 @@ export interface IAnswer {
|
||||
id?: string;
|
||||
audio_binary?: string;
|
||||
data?: any;
|
||||
chatBoxId?: string;
|
||||
}
|
||||
|
||||
export interface Docagg {
|
||||
|
||||
@ -38,6 +38,7 @@ export default {
|
||||
previousPage: '上一页',
|
||||
nextPage: '下一页',
|
||||
add: '添加',
|
||||
promptPlaceholder: '请输入或使用 / 快速插入变量。',
|
||||
},
|
||||
login: {
|
||||
login: '登录',
|
||||
|
||||
56
web/src/pages/agent/canvas/context.tsx
Normal file
56
web/src/pages/agent/canvas/context.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import {
|
||||
createContext,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useContext,
|
||||
useRef,
|
||||
} from 'react';
|
||||
|
||||
interface DropdownContextType {
|
||||
canShowDropdown: () => boolean;
|
||||
setActiveDropdown: (type: 'handle' | 'drag') => void;
|
||||
clearActiveDropdown: () => void;
|
||||
}
|
||||
|
||||
const DropdownContext = createContext<DropdownContextType | null>(null);
|
||||
|
||||
export const useDropdownManager = () => {
|
||||
const context = useContext(DropdownContext);
|
||||
if (!context) {
|
||||
throw new Error('useDropdownManager must be used within DropdownProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface DropdownProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export const DropdownProvider = ({ children }: DropdownProviderProps) => {
|
||||
const activeDropdownRef = useRef<'handle' | 'drag' | null>(null);
|
||||
|
||||
const canShowDropdown = useCallback(() => {
|
||||
const current = activeDropdownRef.current;
|
||||
return !current;
|
||||
}, []);
|
||||
|
||||
const setActiveDropdown = useCallback((type: 'handle' | 'drag') => {
|
||||
activeDropdownRef.current = type;
|
||||
}, []);
|
||||
|
||||
const clearActiveDropdown = useCallback(() => {
|
||||
activeDropdownRef.current = null;
|
||||
}, []);
|
||||
|
||||
const value: DropdownContextType = {
|
||||
canShowDropdown,
|
||||
setActiveDropdown,
|
||||
clearActiveDropdown,
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownContext.Provider value={value}>
|
||||
{children}
|
||||
</DropdownContext.Provider>
|
||||
);
|
||||
};
|
||||
@ -4,17 +4,20 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
Connection,
|
||||
ConnectionMode,
|
||||
ControlButton,
|
||||
Controls,
|
||||
NodeTypes,
|
||||
Position,
|
||||
ReactFlow,
|
||||
} from '@xyflow/react';
|
||||
import '@xyflow/react/dist/style.css';
|
||||
import { NotebookPen } from 'lucide-react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ChatSheet } from '../chat/chat-sheet';
|
||||
import { AgentBackground } from '../components/background';
|
||||
@ -22,7 +25,9 @@ import {
|
||||
AgentChatContext,
|
||||
AgentChatLogContext,
|
||||
AgentInstanceContext,
|
||||
HandleContext,
|
||||
} from '../context';
|
||||
|
||||
import FormSheet from '../form-sheet/next';
|
||||
import {
|
||||
useHandleDrop,
|
||||
@ -33,6 +38,8 @@ import { useAddNode } from '../hooks/use-add-node';
|
||||
import { useBeforeDelete } from '../hooks/use-before-delete';
|
||||
import { useCacheChatLog } from '../hooks/use-cache-chat-log';
|
||||
import { useMoveNote } from '../hooks/use-move-note';
|
||||
import { useDropdownManager } from './context';
|
||||
|
||||
import {
|
||||
useHideFormSheetOnNodeDeletion,
|
||||
useShowDrawer,
|
||||
@ -46,6 +53,7 @@ import { RagNode } from './node';
|
||||
import { AgentNode } from './node/agent-node';
|
||||
import { BeginNode } from './node/begin-node';
|
||||
import { CategorizeNode } from './node/categorize-node';
|
||||
import { InnerNextStepDropdown } from './node/dropdown/next-step-dropdown';
|
||||
import { GenerateNode } from './node/generate-node';
|
||||
import { InvokeNode } from './node/invoke-node';
|
||||
import { IterationNode, IterationStartNode } from './node/iteration-node';
|
||||
@ -96,7 +104,7 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
const {
|
||||
nodes,
|
||||
edges,
|
||||
onConnect,
|
||||
onConnect: originalOnConnect,
|
||||
onEdgesChange,
|
||||
onNodesChange,
|
||||
onSelectionChange,
|
||||
@ -147,14 +155,6 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
|
||||
const { theme } = useTheme();
|
||||
|
||||
const onPaneClick = useCallback(() => {
|
||||
hideFormDrawer();
|
||||
if (imgVisible) {
|
||||
addNoteNode(mouse);
|
||||
hideImage();
|
||||
}
|
||||
}, [addNoteNode, hideFormDrawer, hideImage, imgVisible, mouse]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!chatVisible) {
|
||||
clearEventList();
|
||||
@ -172,6 +172,73 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
|
||||
useHideFormSheetOnNodeDeletion({ hideFormDrawer });
|
||||
|
||||
const { visible, hideModal, showModal } = useSetModalState();
|
||||
const [dropdownPosition, setDropdownPosition] = useState({ x: 0, y: 0 });
|
||||
|
||||
const isConnectedRef = useRef(false);
|
||||
const connectionStartRef = useRef<{
|
||||
nodeId: string;
|
||||
handleId: string;
|
||||
} | null>(null);
|
||||
|
||||
const preventCloseRef = useRef(false);
|
||||
|
||||
const { setActiveDropdown, clearActiveDropdown } = useDropdownManager();
|
||||
|
||||
const onPaneClick = useCallback(() => {
|
||||
hideFormDrawer();
|
||||
if (visible && !preventCloseRef.current) {
|
||||
hideModal();
|
||||
clearActiveDropdown();
|
||||
}
|
||||
if (imgVisible) {
|
||||
addNoteNode(mouse);
|
||||
hideImage();
|
||||
}
|
||||
}, [
|
||||
hideFormDrawer,
|
||||
visible,
|
||||
hideModal,
|
||||
imgVisible,
|
||||
addNoteNode,
|
||||
mouse,
|
||||
hideImage,
|
||||
clearActiveDropdown,
|
||||
]);
|
||||
|
||||
const onConnect = (connection: Connection) => {
|
||||
originalOnConnect(connection);
|
||||
isConnectedRef.current = true;
|
||||
};
|
||||
|
||||
const OnConnectStart = (event: any, params: any) => {
|
||||
isConnectedRef.current = false;
|
||||
|
||||
if (params && params.nodeId && params.handleId) {
|
||||
connectionStartRef.current = {
|
||||
nodeId: params.nodeId,
|
||||
handleId: params.handleId,
|
||||
};
|
||||
} else {
|
||||
connectionStartRef.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
const OnConnectEnd = (event: MouseEvent | TouchEvent) => {
|
||||
if ('clientX' in event && 'clientY' in event) {
|
||||
const { clientX, clientY } = event;
|
||||
setDropdownPosition({ x: clientX, y: clientY });
|
||||
if (!isConnectedRef.current) {
|
||||
setActiveDropdown('drag');
|
||||
showModal();
|
||||
preventCloseRef.current = true;
|
||||
setTimeout(() => {
|
||||
preventCloseRef.current = false;
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.canvasWrapper}>
|
||||
<svg
|
||||
@ -206,6 +273,8 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
nodeTypes={nodeTypes}
|
||||
edgeTypes={edgeTypes}
|
||||
onDrop={onDrop}
|
||||
onConnectStart={OnConnectStart}
|
||||
onConnectEnd={OnConnectEnd}
|
||||
onDragOver={onDragOver}
|
||||
onNodeClick={onNodeClick}
|
||||
onPaneClick={onPaneClick}
|
||||
@ -243,6 +312,27 @@ function AgentCanvas({ drawerVisible, hideDrawer }: IProps) {
|
||||
</ControlButton>
|
||||
</Controls>
|
||||
</ReactFlow>
|
||||
{visible && (
|
||||
<HandleContext.Provider
|
||||
value={{
|
||||
nodeId: connectionStartRef.current?.nodeId || '',
|
||||
id: connectionStartRef.current?.handleId || '',
|
||||
type: 'source',
|
||||
position: Position.Right,
|
||||
isFromConnectionDrag: true,
|
||||
}}
|
||||
>
|
||||
<InnerNextStepDropdown
|
||||
hideModal={() => {
|
||||
hideModal();
|
||||
clearActiveDropdown();
|
||||
}}
|
||||
position={dropdownPosition}
|
||||
>
|
||||
<span></span>
|
||||
</InnerNextStepDropdown>
|
||||
</HandleContext.Provider>
|
||||
)}
|
||||
</AgentInstanceContext.Provider>
|
||||
<NotebookPen
|
||||
className={cn('hidden absolute size-6', { block: imgVisible })}
|
||||
|
||||
@ -20,55 +20,111 @@ import { IModalProps } from '@/interfaces/common';
|
||||
import { Operator } from '@/pages/agent/constant';
|
||||
import { AgentInstanceContext, HandleContext } from '@/pages/agent/context';
|
||||
import OperatorIcon from '@/pages/agent/operator-icon';
|
||||
import { Position } from '@xyflow/react';
|
||||
import { lowerFirst } from 'lodash';
|
||||
import { PropsWithChildren, createContext, memo, useContext } from 'react';
|
||||
import {
|
||||
PropsWithChildren,
|
||||
createContext,
|
||||
memo,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type OperatorItemProps = { operators: Operator[] };
|
||||
type OperatorItemProps = {
|
||||
operators: Operator[];
|
||||
isCustomDropdown?: boolean;
|
||||
mousePosition?: { x: number; y: number };
|
||||
};
|
||||
|
||||
const HideModalContext = createContext<IModalProps<any>['showModal']>(() => {});
|
||||
const OnNodeCreatedContext = createContext<
|
||||
((newNodeId: string) => void) | undefined
|
||||
>(undefined);
|
||||
|
||||
function OperatorItemList({ operators }: OperatorItemProps) {
|
||||
function OperatorItemList({
|
||||
operators,
|
||||
isCustomDropdown = false,
|
||||
mousePosition,
|
||||
}: OperatorItemProps) {
|
||||
const { addCanvasNode } = useContext(AgentInstanceContext);
|
||||
const { nodeId, id, position } = useContext(HandleContext);
|
||||
const handleContext = useContext(HandleContext);
|
||||
const hideModal = useContext(HideModalContext);
|
||||
const onNodeCreated = useContext(OnNodeCreatedContext);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<ul className="space-y-2">
|
||||
{operators.map((x) => {
|
||||
return (
|
||||
<Tooltip key={x}>
|
||||
<TooltipTrigger asChild>
|
||||
<DropdownMenuItem
|
||||
key={x}
|
||||
className="hover:bg-bg-card py-1 px-3 cursor-pointer rounded-sm flex gap-2 items-center justify-start"
|
||||
onClick={addCanvasNode(x, {
|
||||
nodeId,
|
||||
id,
|
||||
position,
|
||||
})}
|
||||
onSelect={() => hideModal?.()}
|
||||
>
|
||||
<OperatorIcon name={x}></OperatorIcon>
|
||||
{t(`flow.${lowerFirst(x)}`)}
|
||||
</DropdownMenuItem>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>{t(`flow.${lowerFirst(x)}Description`)}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
);
|
||||
const handleClick = (operator: Operator) => {
|
||||
const contextData = handleContext || {
|
||||
nodeId: '',
|
||||
id: '',
|
||||
type: 'source' as const,
|
||||
position: Position.Right,
|
||||
isFromConnectionDrag: true,
|
||||
};
|
||||
|
||||
const mockEvent = mousePosition
|
||||
? {
|
||||
clientX: mousePosition.x,
|
||||
clientY: mousePosition.y,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const newNodeId = addCanvasNode(operator, contextData)(mockEvent);
|
||||
|
||||
if (onNodeCreated && newNodeId) {
|
||||
onNodeCreated(newNodeId);
|
||||
}
|
||||
|
||||
hideModal?.();
|
||||
};
|
||||
|
||||
const renderOperatorItem = (operator: Operator) => {
|
||||
const commonContent = (
|
||||
<div className="hover:bg-background-card py-1 px-3 cursor-pointer rounded-sm flex gap-2 items-center justify-start">
|
||||
<OperatorIcon name={operator} />
|
||||
{t(`flow.${lowerFirst(operator)}`)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Tooltip key={operator}>
|
||||
<TooltipTrigger asChild>
|
||||
{isCustomDropdown ? (
|
||||
<li onClick={() => handleClick(operator)}>{commonContent}</li>
|
||||
) : (
|
||||
<DropdownMenuItem
|
||||
key={operator}
|
||||
className="hover:bg-background-card py-1 px-3 cursor-pointer rounded-sm flex gap-2 items-center justify-start"
|
||||
onClick={() => handleClick(operator)}
|
||||
onSelect={() => hideModal?.()}
|
||||
>
|
||||
<OperatorIcon name={operator} />
|
||||
{t(`flow.${lowerFirst(operator)}`)}
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="right">
|
||||
<p>{t(`flow.${lowerFirst(operator)}Description`)}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
return <ul className="space-y-2">{operators.map(renderOperatorItem)}</ul>;
|
||||
}
|
||||
|
||||
function AccordionOperators() {
|
||||
function AccordionOperators({
|
||||
isCustomDropdown = false,
|
||||
mousePosition,
|
||||
}: {
|
||||
isCustomDropdown?: boolean;
|
||||
mousePosition?: { x: number; y: number };
|
||||
}) {
|
||||
return (
|
||||
<Accordion
|
||||
type="multiple"
|
||||
className="px-2 text-text-primary max-h-[45vh] overflow-auto"
|
||||
className="px-2 text-text-title max-h-[45vh] overflow-auto"
|
||||
defaultValue={['item-1', 'item-2', 'item-3', 'item-4', 'item-5']}
|
||||
>
|
||||
<AccordionItem value="item-1">
|
||||
@ -76,6 +132,8 @@ function AccordionOperators() {
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||
<OperatorItemList
|
||||
operators={[Operator.Agent, Operator.Retrieval]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
@ -84,6 +142,8 @@ function AccordionOperators() {
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||
<OperatorItemList
|
||||
operators={[Operator.Message, Operator.UserFillUp]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
@ -96,6 +156,8 @@ function AccordionOperators() {
|
||||
Operator.Iteration,
|
||||
Operator.Categorize,
|
||||
]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
@ -106,6 +168,8 @@ function AccordionOperators() {
|
||||
<AccordionContent className="flex flex-col gap-4 text-balance">
|
||||
<OperatorItemList
|
||||
operators={[Operator.Code, Operator.StringTransform]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
@ -129,6 +193,8 @@ function AccordionOperators() {
|
||||
Operator.Invoke,
|
||||
Operator.WenCai,
|
||||
]}
|
||||
isCustomDropdown={isCustomDropdown}
|
||||
mousePosition={mousePosition}
|
||||
></OperatorItemList>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
@ -139,9 +205,69 @@ function AccordionOperators() {
|
||||
export function InnerNextStepDropdown({
|
||||
children,
|
||||
hideModal,
|
||||
}: PropsWithChildren & IModalProps<any>) {
|
||||
position,
|
||||
onNodeCreated,
|
||||
}: PropsWithChildren &
|
||||
IModalProps<any> & {
|
||||
position?: { x: number; y: number };
|
||||
onNodeCreated?: (newNodeId: string) => void;
|
||||
}) {
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (position && hideModal) {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') {
|
||||
hideModal();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}
|
||||
}, [position, hideModal]);
|
||||
|
||||
if (position) {
|
||||
return (
|
||||
<div
|
||||
ref={dropdownRef}
|
||||
style={{
|
||||
position: 'fixed',
|
||||
left: position.x,
|
||||
top: position.y + 10,
|
||||
zIndex: 1000,
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="w-[300px] font-semibold bg-white border border-border rounded-md shadow-lg">
|
||||
<div className="px-3 py-2 border-b border-border">
|
||||
<div className="text-sm font-medium">Next Step</div>
|
||||
</div>
|
||||
<HideModalContext.Provider value={hideModal}>
|
||||
<OnNodeCreatedContext.Provider value={onNodeCreated}>
|
||||
<AccordionOperators
|
||||
isCustomDropdown={true}
|
||||
mousePosition={position}
|
||||
></AccordionOperators>
|
||||
</OnNodeCreatedContext.Provider>
|
||||
</HideModalContext.Provider>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu open onOpenChange={hideModal}>
|
||||
<DropdownMenu
|
||||
open={true}
|
||||
onOpenChange={(open) => {
|
||||
if (!open && hideModal) {
|
||||
hideModal();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
|
||||
@ -4,6 +4,7 @@ import { Handle, HandleProps } from '@xyflow/react';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
import { HandleContext } from '../../context';
|
||||
import { useDropdownManager } from '../context';
|
||||
import { InnerNextStepDropdown } from './dropdown/next-step-dropdown';
|
||||
|
||||
export function CommonHandle({
|
||||
@ -13,12 +14,16 @@ export function CommonHandle({
|
||||
}: HandleProps & { nodeId: string }) {
|
||||
const { visible, hideModal, showModal } = useSetModalState();
|
||||
|
||||
const { canShowDropdown, setActiveDropdown, clearActiveDropdown } =
|
||||
useDropdownManager();
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
nodeId,
|
||||
id: props.id,
|
||||
id: props.id || undefined,
|
||||
type: props.type,
|
||||
position: props.position,
|
||||
isFromConnectionDrag: false,
|
||||
}),
|
||||
[nodeId, props.id, props.position, props.type],
|
||||
);
|
||||
@ -33,12 +38,23 @@ export function CommonHandle({
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (!canShowDropdown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveDropdown('handle');
|
||||
showModal();
|
||||
}}
|
||||
>
|
||||
<Plus className="size-3 pointer-events-none text-text-title-invert" />
|
||||
{visible && (
|
||||
<InnerNextStepDropdown hideModal={hideModal}>
|
||||
<InnerNextStepDropdown
|
||||
hideModal={() => {
|
||||
hideModal();
|
||||
clearActiveDropdown();
|
||||
}}
|
||||
>
|
||||
<span></span>
|
||||
</InnerNextStepDropdown>
|
||||
)}
|
||||
|
||||
@ -42,6 +42,7 @@ export type HandleContextType = {
|
||||
id?: string;
|
||||
type: HandleType;
|
||||
position: Position;
|
||||
isFromConnectionDrag: boolean;
|
||||
};
|
||||
|
||||
export const HandleContext = createContext<HandleContextType>(
|
||||
|
||||
@ -163,9 +163,9 @@ export function PromptEditor({
|
||||
'absolute top-1 left-2 text-text-secondary pointer-events-none',
|
||||
{
|
||||
'truncate w-[90%]': !multiLine,
|
||||
'translate-y-10': multiLine,
|
||||
},
|
||||
)}
|
||||
data-xxx
|
||||
>
|
||||
{placeholder || t('common.promptPlaceholder')}
|
||||
</div>
|
||||
|
||||
@ -2,6 +2,7 @@ import { Collapse } from '@/components/collapse';
|
||||
import { CrossLanguageFormField } from '@/components/cross-language-form-field';
|
||||
import { FormContainer } from '@/components/form-container';
|
||||
import { KnowledgeBaseFormField } from '@/components/knowledge-base-item';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { RerankFormFields } from '@/components/rerank';
|
||||
import { SimilaritySliderFormField } from '@/components/similarity-slider';
|
||||
import { TopNFormField } from '@/components/top-n-item';
|
||||
@ -25,7 +26,7 @@ import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
||||
import { INextOperatorForm } from '../../interface';
|
||||
import { FormWrapper } from '../components/form-wrapper';
|
||||
import { Output } from '../components/output';
|
||||
import { QueryVariable } from '../components/query-variable';
|
||||
import { PromptEditor } from '../components/prompt-editor';
|
||||
import { useValues } from './use-values';
|
||||
|
||||
export const RetrievalPartialSchema = {
|
||||
@ -74,6 +75,8 @@ export function EmptyResponseField() {
|
||||
}
|
||||
|
||||
function RetrievalForm({ node }: INextOperatorForm) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const outputList = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
@ -96,7 +99,9 @@ function RetrievalForm({ node }: INextOperatorForm) {
|
||||
<Form {...form}>
|
||||
<FormWrapper>
|
||||
<FormContainer>
|
||||
<QueryVariable></QueryVariable>
|
||||
<RAGFlowFormItem name="query" label={t('flow.query')}>
|
||||
<PromptEditor></PromptEditor>
|
||||
</RAGFlowFormItem>
|
||||
<KnowledgeBaseFormField showVariable></KnowledgeBaseFormField>
|
||||
</FormContainer>
|
||||
<Collapse title={<div>Advanced Settings</div>}>
|
||||
|
||||
@ -208,7 +208,7 @@ function useAddToolNode() {
|
||||
);
|
||||
|
||||
const addToolNode = useCallback(
|
||||
(newNode: Node<any>, nodeId?: string) => {
|
||||
(newNode: Node<any>, nodeId?: string): boolean => {
|
||||
const agentNode = getNode(nodeId);
|
||||
|
||||
if (agentNode) {
|
||||
@ -222,7 +222,7 @@ function useAddToolNode() {
|
||||
childToolNodeIds.length > 0 &&
|
||||
nodes.some((x) => x.id === childToolNodeIds[0])
|
||||
) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
newNode.position = {
|
||||
@ -239,7 +239,9 @@ function useAddToolNode() {
|
||||
targetHandle: NodeHandleId.End,
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[addEdge, addNode, edges, getNode, nodes],
|
||||
);
|
||||
@ -295,13 +297,17 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) {
|
||||
const addCanvasNode = useCallback(
|
||||
(
|
||||
type: string,
|
||||
params: { nodeId?: string; position: Position; id?: string } = {
|
||||
params: {
|
||||
nodeId?: string;
|
||||
position: Position;
|
||||
id?: string;
|
||||
isFromConnectionDrag?: boolean;
|
||||
} = {
|
||||
position: Position.Right,
|
||||
},
|
||||
) =>
|
||||
(event?: CanvasMouseEvent) => {
|
||||
(event?: CanvasMouseEvent): string | undefined => {
|
||||
const nodeId = params.nodeId;
|
||||
|
||||
const node = getNode(nodeId);
|
||||
|
||||
// reactFlowInstance.project was renamed to reactFlowInstance.screenToFlowPosition
|
||||
@ -312,7 +318,11 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) {
|
||||
y: event?.clientY || 0,
|
||||
});
|
||||
|
||||
if (params.position === Position.Right && type !== Operator.Note) {
|
||||
if (
|
||||
params.position === Position.Right &&
|
||||
type !== Operator.Note &&
|
||||
!params.isFromConnectionDrag
|
||||
) {
|
||||
position = calculateNewlyBackChildPosition(nodeId, params.id);
|
||||
}
|
||||
|
||||
@ -371,6 +381,7 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) {
|
||||
targetHandle: NodeHandleId.End,
|
||||
});
|
||||
}
|
||||
return newNode.id;
|
||||
} else if (
|
||||
type === Operator.Agent &&
|
||||
params.position === Position.Bottom
|
||||
@ -406,8 +417,10 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) {
|
||||
targetHandle: NodeHandleId.AgentTop,
|
||||
});
|
||||
}
|
||||
return newNode.id;
|
||||
} else if (type === Operator.Tool) {
|
||||
addToolNode(newNode, params.nodeId);
|
||||
const toolNodeAdded = addToolNode(newNode, params.nodeId);
|
||||
return toolNodeAdded ? newNode.id : undefined;
|
||||
} else {
|
||||
addNode(newNode);
|
||||
addChildEdge(params.position, {
|
||||
@ -416,6 +429,8 @@ export function useAddNode(reactFlowInstance?: ReactFlowInstance<any, any>) {
|
||||
sourceHandle: params.id,
|
||||
});
|
||||
}
|
||||
|
||||
return newNode.id;
|
||||
},
|
||||
[
|
||||
addChildEdge,
|
||||
|
||||
@ -34,6 +34,7 @@ import { ComponentPropsWithoutRef, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'umi';
|
||||
import AgentCanvas from './canvas';
|
||||
import { DropdownProvider } from './canvas/context';
|
||||
import EmbedDialog from './embed-dialog';
|
||||
import { useHandleExportOrImportJsonFile } from './hooks/use-export-json';
|
||||
import { useFetchDataOnMount } from './hooks/use-fetch-data';
|
||||
@ -185,10 +186,12 @@ export default function Agent() {
|
||||
</div>
|
||||
</PageHeader>
|
||||
<ReactFlowProvider>
|
||||
<AgentCanvas
|
||||
drawerVisible={chatDrawerVisible}
|
||||
hideDrawer={hideChatDrawer}
|
||||
></AgentCanvas>
|
||||
<DropdownProvider>
|
||||
<AgentCanvas
|
||||
drawerVisible={chatDrawerVisible}
|
||||
hideDrawer={hideChatDrawer}
|
||||
></AgentCanvas>
|
||||
</DropdownProvider>
|
||||
</ReactFlowProvider>
|
||||
{fileUploadVisible && (
|
||||
<UploadAgentDialog
|
||||
|
||||
@ -1,13 +1,17 @@
|
||||
import { LargeModelFormFieldWithoutFilter } from '@/components/large-model-form-field';
|
||||
import { LlmSettingSchema } from '@/components/llm-setting-items/next';
|
||||
import { NextMessageInput } from '@/components/message-input/next';
|
||||
import MessageItem from '@/components/message-item';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { MessageType } from '@/constants/chat';
|
||||
import { useScrollToBottom } from '@/hooks/logic-hooks';
|
||||
import {
|
||||
useFetchConversation,
|
||||
useFetchDialog,
|
||||
@ -15,16 +19,20 @@ import {
|
||||
} from '@/hooks/use-chat-request';
|
||||
import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
|
||||
import { buildMessageUuidWithRole } from '@/utils/chat';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { ListCheck, Plus, Trash2 } from 'lucide-react';
|
||||
import { useCallback } from 'react';
|
||||
import { forwardRef, useCallback, useImperativeHandle, useRef } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
useGetSendButtonDisabled,
|
||||
useSendButtonDisabled,
|
||||
} from '../../hooks/use-button-disabled';
|
||||
import { useCreateConversationBeforeUploadDocument } from '../../hooks/use-create-conversation';
|
||||
import { useSendMessage } from '../../hooks/use-send-chat-message';
|
||||
import { useSendMultipleChatMessage } from '../../hooks/use-send-multiple-message';
|
||||
import { buildMessageItemReference } from '../../utils';
|
||||
import { LLMSelectForm } from '../llm-select-form';
|
||||
import { IMessage } from '../interface';
|
||||
import { useAddChatBox } from '../use-add-box';
|
||||
|
||||
type MultipleChatBoxProps = {
|
||||
@ -35,31 +43,42 @@ type MultipleChatBoxProps = {
|
||||
'removeChatBox' | 'addChatBox' | 'chatBoxIds'
|
||||
>;
|
||||
|
||||
type ChatCardProps = { id: string; idx: number } & Pick<
|
||||
type ChatCardProps = {
|
||||
id: string;
|
||||
idx: number;
|
||||
derivedMessages: IMessage[];
|
||||
} & Pick<
|
||||
MultipleChatBoxProps,
|
||||
'controller' | 'removeChatBox' | 'addChatBox' | 'chatBoxIds'
|
||||
>;
|
||||
|
||||
function ChatCard({
|
||||
controller,
|
||||
removeChatBox,
|
||||
id,
|
||||
idx,
|
||||
addChatBox,
|
||||
chatBoxIds,
|
||||
}: ChatCardProps) {
|
||||
const {
|
||||
value,
|
||||
// scrollRef,
|
||||
messageContainerRef,
|
||||
sendLoading,
|
||||
const ChatCard = forwardRef(function ChatCard(
|
||||
{
|
||||
controller,
|
||||
removeChatBox,
|
||||
id,
|
||||
idx,
|
||||
addChatBox,
|
||||
chatBoxIds,
|
||||
derivedMessages,
|
||||
handleInputChange,
|
||||
handlePressEnter,
|
||||
regenerateMessage,
|
||||
removeMessageById,
|
||||
stopOutputMessage,
|
||||
} = useSendMessage(controller);
|
||||
}: ChatCardProps,
|
||||
ref,
|
||||
) {
|
||||
const { sendLoading, regenerateMessage, removeMessageById } =
|
||||
useSendMessage(controller);
|
||||
|
||||
const messageContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { scrollRef } = useScrollToBottom(derivedMessages, messageContainerRef);
|
||||
|
||||
const FormSchema = z.object(LlmSettingSchema);
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
llm_id: '',
|
||||
},
|
||||
});
|
||||
|
||||
const { data: userInfo } = useFetchUserInfo();
|
||||
const { data: currentDialog } = useFetchDialog();
|
||||
@ -71,13 +90,19 @@ function ChatCard({
|
||||
removeChatBox(id);
|
||||
}, [id, removeChatBox]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
getFormData: () => form.getValues(),
|
||||
}));
|
||||
|
||||
return (
|
||||
<Card className="bg-transparent border flex-1">
|
||||
<Card className="bg-transparent border flex-1 flex flex-col">
|
||||
<CardHeader className="border-b px-5 py-3">
|
||||
<CardTitle className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-base">{idx + 1}</span>
|
||||
<LLMSelectForm></LLMSelectForm>
|
||||
<Form {...form}>
|
||||
<LargeModelFormFieldWithoutFilter></LargeModelFormFieldWithoutFilter>
|
||||
</Form>
|
||||
</div>
|
||||
<div className="space-x-2">
|
||||
<Tooltip>
|
||||
@ -102,8 +127,8 @@ function ChatCard({
|
||||
</div>
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div ref={messageContainerRef} className="flex-1 overflow-auto min-h-0">
|
||||
<CardContent className="flex-1 min-h-0">
|
||||
<div ref={messageContainerRef} className="h-full overflow-auto">
|
||||
<div className="w-full">
|
||||
{derivedMessages?.map((message, i) => {
|
||||
return (
|
||||
@ -134,12 +159,12 @@ function ChatCard({
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{/* <div ref={scrollRef} /> */}
|
||||
<div ref={scrollRef} />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
export function MultipleChatBox({
|
||||
controller,
|
||||
@ -150,10 +175,12 @@ export function MultipleChatBox({
|
||||
const {
|
||||
value,
|
||||
sendLoading,
|
||||
messageRecord,
|
||||
handleInputChange,
|
||||
handlePressEnter,
|
||||
stopOutputMessage,
|
||||
} = useSendMessage(controller);
|
||||
setFormRef,
|
||||
} = useSendMultipleChatMessage(controller, chatBoxIds);
|
||||
|
||||
const { createConversationBeforeUploadDocument } =
|
||||
useCreateConversationBeforeUploadDocument();
|
||||
@ -163,7 +190,7 @@ export function MultipleChatBox({
|
||||
|
||||
return (
|
||||
<section className="h-full flex flex-col px-5">
|
||||
<div className="flex gap-4 flex-1 px-5 pb-14">
|
||||
<div className="flex gap-4 flex-1 px-5 pb-14 min-h-0">
|
||||
{chatBoxIds.map((id, idx) => (
|
||||
<ChatCard
|
||||
key={id}
|
||||
@ -173,6 +200,8 @@ export function MultipleChatBox({
|
||||
chatBoxIds={chatBoxIds}
|
||||
removeChatBox={removeChatBox}
|
||||
addChatBox={addChatBox}
|
||||
derivedMessages={messageRecord[id]}
|
||||
ref={setFormRef(id)}
|
||||
></ChatCard>
|
||||
))}
|
||||
</div>
|
||||
|
||||
48
web/src/pages/next-chats/hooks/use-build-form-refs.ts
Normal file
48
web/src/pages/next-chats/hooks/use-build-form-refs.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
|
||||
export function useBuildFormRefs(chatBoxIds: string[]) {
|
||||
const formRefs = useRef<Record<string, { getFormData: () => any }>>({});
|
||||
|
||||
const setFormRef = (id: string) => (ref: { getFormData: () => any }) => {
|
||||
formRefs.current[id] = ref;
|
||||
};
|
||||
|
||||
const cleanupFormRefs = useCallback(() => {
|
||||
const currentIds = new Set(chatBoxIds);
|
||||
Object.keys(formRefs.current).forEach((id) => {
|
||||
if (!currentIds.has(id)) {
|
||||
delete formRefs.current[id];
|
||||
}
|
||||
});
|
||||
}, [chatBoxIds]);
|
||||
|
||||
const getLLMConfigById = useCallback(
|
||||
(chatBoxId?: string) => {
|
||||
const llmConfig = chatBoxId
|
||||
? formRefs.current[chatBoxId].getFormData()
|
||||
: {};
|
||||
|
||||
return llmConfig;
|
||||
},
|
||||
[formRefs],
|
||||
);
|
||||
|
||||
const isLLMConfigEmpty = useCallback(
|
||||
(chatBoxId?: string) => {
|
||||
return isEmpty(getLLMConfigById(chatBoxId)?.llm_id);
|
||||
},
|
||||
[getLLMConfigById],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
cleanupFormRefs();
|
||||
}, [cleanupFormRefs]);
|
||||
|
||||
return {
|
||||
formRefs,
|
||||
setFormRef,
|
||||
getLLMConfigById,
|
||||
isLLMConfigEmpty,
|
||||
};
|
||||
}
|
||||
235
web/src/pages/next-chats/hooks/use-send-multiple-message.ts
Normal file
235
web/src/pages/next-chats/hooks/use-send-multiple-message.ts
Normal file
@ -0,0 +1,235 @@
|
||||
import { MessageType } from '@/constants/chat';
|
||||
import {
|
||||
useHandleMessageInputChange,
|
||||
useSendMessageWithSse,
|
||||
} from '@/hooks/logic-hooks';
|
||||
import { useGetChatSearchParams } from '@/hooks/use-chat-request';
|
||||
import { IAnswer, Message } from '@/interfaces/database/chat';
|
||||
import api from '@/utils/api';
|
||||
import { buildMessageUuid } from '@/utils/chat';
|
||||
import { trim } from 'lodash';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { IMessage } from '../chat/interface';
|
||||
import { useBuildFormRefs } from './use-build-form-refs';
|
||||
|
||||
export function useSendMultipleChatMessage(
|
||||
controller: AbortController,
|
||||
chatBoxIds: string[],
|
||||
) {
|
||||
const [messageRecord, setMessageRecord] = useState<
|
||||
Record<string, IMessage[]>
|
||||
>({});
|
||||
|
||||
const { conversationId } = useGetChatSearchParams();
|
||||
|
||||
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
|
||||
const { send, answer, done } = useSendMessageWithSse(
|
||||
api.completeConversation,
|
||||
);
|
||||
|
||||
const { setFormRef, getLLMConfigById, isLLMConfigEmpty } =
|
||||
useBuildFormRefs(chatBoxIds);
|
||||
|
||||
const stopOutputMessage = useCallback(() => {
|
||||
controller.abort();
|
||||
}, [controller]);
|
||||
|
||||
const addNewestQuestion = useCallback(
|
||||
(message: Message, answer: string = '') => {
|
||||
setMessageRecord((pre) => {
|
||||
const currentRecord = { ...pre };
|
||||
const chatBoxId = message.chatBoxId;
|
||||
if (typeof chatBoxId === 'string') {
|
||||
const currentChatMessages = currentRecord[chatBoxId];
|
||||
|
||||
const nextChatMessages = [
|
||||
...currentChatMessages,
|
||||
{
|
||||
...message,
|
||||
id: buildMessageUuid(message), // The message id is generated on the front end,
|
||||
// and the message id returned by the back end is the same as the question id,
|
||||
// so that the pair of messages can be deleted together when deleting the message
|
||||
},
|
||||
{
|
||||
role: MessageType.Assistant,
|
||||
content: answer,
|
||||
id: buildMessageUuid({ ...message, role: MessageType.Assistant }),
|
||||
},
|
||||
];
|
||||
|
||||
currentRecord[chatBoxId] = nextChatMessages;
|
||||
}
|
||||
|
||||
return currentRecord;
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
// Add the streaming message to the last item in the message list
|
||||
const addNewestAnswer = useCallback((answer: IAnswer) => {
|
||||
setMessageRecord((pre) => {
|
||||
const currentRecord = { ...pre };
|
||||
const chatBoxId = answer.chatBoxId;
|
||||
if (typeof chatBoxId === 'string') {
|
||||
const currentChatMessages = currentRecord[chatBoxId];
|
||||
|
||||
const nextChatMessages = [
|
||||
...(currentChatMessages?.slice(0, -1) ?? []),
|
||||
{
|
||||
role: MessageType.Assistant,
|
||||
content: answer.answer,
|
||||
reference: answer.reference,
|
||||
id: buildMessageUuid({
|
||||
id: answer.id,
|
||||
role: MessageType.Assistant,
|
||||
}),
|
||||
prompt: answer.prompt,
|
||||
audio_binary: answer.audio_binary,
|
||||
},
|
||||
];
|
||||
|
||||
currentRecord[chatBoxId] = nextChatMessages;
|
||||
}
|
||||
|
||||
return currentRecord;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const removeLatestMessage = useCallback((chatBoxId?: string) => {
|
||||
setMessageRecord((pre) => {
|
||||
const currentRecord = { ...pre };
|
||||
if (chatBoxId) {
|
||||
const currentChatMessages = currentRecord[chatBoxId];
|
||||
if (currentChatMessages) {
|
||||
currentRecord[chatBoxId] = currentChatMessages.slice(0, -1);
|
||||
}
|
||||
}
|
||||
return currentRecord;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const adjustRecordByChatBoxIds = useCallback(() => {
|
||||
setMessageRecord((pre) => {
|
||||
const currentRecord = { ...pre };
|
||||
chatBoxIds.forEach((chatBoxId) => {
|
||||
if (!currentRecord[chatBoxId]) {
|
||||
currentRecord[chatBoxId] = [];
|
||||
}
|
||||
});
|
||||
Object.keys(currentRecord).forEach((chatBoxId) => {
|
||||
if (!chatBoxIds.includes(chatBoxId)) {
|
||||
delete currentRecord[chatBoxId];
|
||||
}
|
||||
});
|
||||
return currentRecord;
|
||||
});
|
||||
}, [chatBoxIds, setMessageRecord]);
|
||||
|
||||
const sendMessage = useCallback(
|
||||
async ({
|
||||
message,
|
||||
currentConversationId,
|
||||
messages,
|
||||
chatBoxId,
|
||||
}: {
|
||||
message: Message;
|
||||
currentConversationId?: string;
|
||||
chatBoxId: string;
|
||||
messages?: Message[];
|
||||
}) => {
|
||||
let derivedMessages: IMessage[] = [];
|
||||
|
||||
derivedMessages = messageRecord[chatBoxId];
|
||||
|
||||
const res = await send(
|
||||
{
|
||||
chatBoxId,
|
||||
conversation_id: currentConversationId ?? conversationId,
|
||||
messages: [...(messages ?? derivedMessages ?? []), message],
|
||||
...getLLMConfigById(chatBoxId),
|
||||
},
|
||||
controller,
|
||||
);
|
||||
|
||||
if (res && (res?.response.status !== 200 || res?.data?.code !== 0)) {
|
||||
// cancel loading
|
||||
setValue(message.content);
|
||||
console.info('removeLatestMessage111');
|
||||
removeLatestMessage(chatBoxId);
|
||||
}
|
||||
},
|
||||
[
|
||||
send,
|
||||
conversationId,
|
||||
getLLMConfigById,
|
||||
controller,
|
||||
messageRecord,
|
||||
setValue,
|
||||
removeLatestMessage,
|
||||
],
|
||||
);
|
||||
|
||||
const handlePressEnter = useCallback(() => {
|
||||
if (trim(value) === '') return;
|
||||
const id = uuid();
|
||||
|
||||
chatBoxIds.forEach((chatBoxId) => {
|
||||
if (!isLLMConfigEmpty(chatBoxId)) {
|
||||
addNewestQuestion({
|
||||
content: value,
|
||||
id,
|
||||
role: MessageType.User,
|
||||
chatBoxId,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (done) {
|
||||
// TODO:
|
||||
setValue('');
|
||||
chatBoxIds.forEach((chatBoxId) => {
|
||||
if (!isLLMConfigEmpty(chatBoxId)) {
|
||||
sendMessage({
|
||||
message: {
|
||||
id,
|
||||
content: value.trim(),
|
||||
role: MessageType.User,
|
||||
},
|
||||
chatBoxId,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [
|
||||
value,
|
||||
chatBoxIds,
|
||||
done,
|
||||
isLLMConfigEmpty,
|
||||
addNewestQuestion,
|
||||
setValue,
|
||||
sendMessage,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (answer.answer && conversationId) {
|
||||
addNewestAnswer(answer);
|
||||
}
|
||||
}, [answer, addNewestAnswer, conversationId]);
|
||||
|
||||
useEffect(() => {
|
||||
adjustRecordByChatBoxIds();
|
||||
}, [adjustRecordByChatBoxIds]);
|
||||
|
||||
return {
|
||||
value,
|
||||
messageRecord,
|
||||
sendMessage,
|
||||
handleInputChange,
|
||||
handlePressEnter,
|
||||
stopOutputMessage,
|
||||
sendLoading: false,
|
||||
setFormRef,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user