feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="coz_chat">
<g id="Union">
<path d="M5.24283 5.83325C5.24283 5.51109 5.504 5.24992 5.82617 5.24992H9.9095C10.2317 5.24992 10.4928 5.51109 10.4928 5.83325C10.4928 6.15542 10.2317 6.41659 9.9095 6.41659H5.82617C5.504 6.41659 5.24283 6.15542 5.24283 5.83325Z" fill="#5147FF"/>
<path d="M5.82617 7.58325C5.504 7.58325 5.24283 7.84442 5.24283 8.16659C5.24283 8.48875 5.504 8.74992 5.82617 8.74992H8.1595C8.48167 8.74992 8.74283 8.48875 8.74283 8.16659C8.74283 7.84442 8.48167 7.58325 8.1595 7.58325H5.82617Z" fill="#5147FF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.9928 6.99992C13.9928 10.5437 11.12 13.4166 7.57617 13.4166H3.27002C2.83638 13.4166 2.55434 12.9602 2.74827 12.5724L3.19186 11.6852C1.94111 10.5143 1.1595 8.84839 1.1595 6.99992C1.1595 3.45609 4.03234 0.583252 7.57617 0.583252C11.12 0.583252 13.9928 3.45609 13.9928 6.99992ZM4.62464 11.4284L4.21387 12.2499H7.57617C10.4757 12.2499 12.8262 9.89941 12.8262 6.99992C12.8262 4.10042 10.4757 1.74992 7.57617 1.74992C4.67667 1.74992 2.32617 4.10042 2.32617 6.99992C2.32617 8.51237 2.96421 9.87396 3.98918 10.8335L4.62464 11.4284Z" fill="#5147FF"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,9 @@
<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Component 2">
<g id="Union">
<path d="M11.7377 5.08875V10.3246C11.7377 10.4212 11.6594 10.4996 11.5627 10.4996H8.39925L8.39859 10.4996H3.41023C3.25433 10.4996 3.17625 10.3111 3.28649 10.2008L5.78067 7.70667C5.84901 7.63833 5.95982 7.63833 6.02816 7.70667L7.36272 9.04124L11.439 4.965C11.5492 4.85476 11.7377 4.93284 11.7377 5.08875Z" fill="#4E40E5"/>
<path d="M4.73779 5.83293C5.38213 5.83293 5.90446 5.31059 5.90446 4.66626C5.90446 4.02193 5.38213 3.49959 4.73779 3.49959C4.09346 3.49959 3.57113 4.02193 3.57113 4.66626C3.57113 5.31059 4.09346 5.83293 4.73779 5.83293Z" fill="#4E40E5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.23779 2.33293C1.23779 1.69126 1.76279 1.16626 2.40446 1.16626H12.9045C13.5461 1.16626 14.0711 1.69126 14.0711 2.33293V11.6663C14.0711 12.3079 13.5461 12.8329 12.9045 12.8329H2.40446C1.76279 12.8329 1.23779 12.3079 1.23779 11.6663V2.33293ZM12.9045 2.33293H2.40446V11.6663H12.9045V2.33293Z" fill="#4E40E5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,12 @@
<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Component 2">
<g id="Union">
<path d="M1.47082 1.39238C1.72422 1.27387 1.92817 1.06992 2.04668 0.816521C2.05886 0.790463 2.07015 0.763881 2.08049 0.736816C2.08663 0.720755 2.09243 0.704523 2.0979 0.688128L2.25921 0.204192C2.34996 -0.0680642 2.73506 -0.0680639 2.82581 0.204192L2.98712 0.688129C2.99259 0.704523 2.99839 0.720755 3.00453 0.736816C3.01487 0.763881 3.02615 0.790463 3.03834 0.816521C3.15685 1.06992 3.3608 1.27387 3.6142 1.39238C3.64026 1.40457 3.66684 1.41585 3.6939 1.42619C3.70997 1.43233 3.7262 1.43813 3.74259 1.4436L4.22653 1.60491C4.49878 1.69566 4.49878 2.08076 4.22653 2.17151L3.74259 2.33283C3.7262 2.33829 3.70997 2.3441 3.6939 2.35023C3.66684 2.36057 3.64026 2.37186 3.6142 2.38404C3.40047 2.484 3.22192 2.64474 3.10015 2.84466C3.07754 2.88177 3.05689 2.92023 3.03834 2.9599C3.02615 2.98596 3.01487 3.01254 3.00453 3.03961C2.99839 3.05567 2.99259 3.0719 2.98712 3.08829L2.82581 3.57223C2.78611 3.69134 2.69006 3.75834 2.58698 3.77323C2.45444 3.79238 2.31026 3.72538 2.25921 3.57223L2.0979 3.08829C2.09243 3.0719 2.08662 3.05567 2.08049 3.03961C2.07015 3.01254 2.05886 2.98596 2.04668 2.9599C1.92816 2.7065 1.72422 2.50256 1.47082 2.38404C1.44476 2.37186 1.41818 2.36057 1.39111 2.35023C1.37505 2.3441 1.35882 2.33829 1.34243 2.33282L0.858488 2.17151C0.586233 2.08076 0.586233 1.69566 0.858489 1.60491L1.34243 1.4436C1.35882 1.43813 1.37505 1.43233 1.39111 1.42619C1.41818 1.41585 1.44476 1.40457 1.47082 1.39238Z" fill="#4E40E5"/>
<path d="M11.6944 11.616C11.9478 11.4974 12.1517 11.2935 12.2702 11.0401C12.2824 11.014 12.2937 10.9875 12.3041 10.9604C12.3102 10.9443 12.316 10.9281 12.3215 10.9117L12.4828 10.4278C12.5735 10.1555 12.9586 10.1555 13.0494 10.4278L13.2107 10.9117C13.2162 10.9281 13.222 10.9443 13.2281 10.9604C13.2384 10.9875 13.2497 11.014 13.2619 11.0401C13.3804 11.2935 13.5844 11.4974 13.8378 11.616C13.8638 11.6281 13.8904 11.6394 13.9175 11.6498C13.9335 11.6559 13.9498 11.6617 13.9662 11.6672L14.4501 11.8285C14.7224 11.9192 14.7224 12.3043 14.4501 12.3951L13.9662 12.5564C13.9498 12.5619 13.9335 12.5677 13.9175 12.5738C13.8904 12.5841 13.8638 12.5954 13.8378 12.6076C13.624 12.7076 13.4455 12.8683 13.3237 13.0682C13.3011 13.1053 13.2805 13.1438 13.2619 13.1835C13.2497 13.2095 13.2384 13.2361 13.2281 13.2632C13.222 13.2792 13.2162 13.2955 13.2107 13.3119L13.0494 13.7958C13.0097 13.9149 12.9136 13.9819 12.8105 13.9968C12.678 14.0159 12.5338 13.9489 12.4828 13.7958L12.3215 13.3119C12.316 13.2955 12.3102 13.2792 12.3041 13.2632C12.2937 13.2361 12.2824 13.2095 12.2702 13.1835C12.1517 12.9301 11.9478 12.7261 11.6944 12.6076C11.6683 12.5954 11.6417 12.5841 11.6147 12.5738C11.5986 12.5677 11.5824 12.5619 11.566 12.5564L11.0821 12.3951C10.8098 12.3043 10.8098 11.9192 11.0821 11.8285L11.566 11.6672C11.5824 11.6617 11.5986 11.6559 11.6147 11.6498C11.6417 11.6394 11.6683 11.6281 11.6944 11.616Z" fill="#4E40E5"/>
<path d="M2.54396 4.39184C2.36112 4.39184 2.18523 4.33824 2.03515 4.23662C1.95199 4.18032 1.88005 4.1116 1.82096 4.03265V11.6667C1.82096 12.3083 2.34596 12.8333 2.98763 12.8333H10.6203C10.5422 12.7745 10.4743 12.7031 10.4186 12.6208C10.3171 12.4708 10.2633 12.2949 10.2633 12.1119C10.2633 11.9542 10.3032 11.8018 10.3792 11.6667H2.98763V4.27615C2.98011 4.28037 2.97254 4.28448 2.96492 4.28847C2.83574 4.3562 2.69174 4.39168 2.54306 4.39168L2.54396 4.39184Z" fill="#4E40E5"/>
<path d="M13.4876 2.33333V9.96567C13.347 9.77914 13.1375 9.65251 12.8977 9.61784C12.8545 9.61165 12.8104 9.60848 12.7667 9.60848C12.6088 9.60848 12.4562 9.64838 12.321 9.7245V2.33333H4.93076C5.00684 2.19813 5.04667 2.04568 5.04667 1.88807C5.04667 1.70508 4.99307 1.52919 4.89161 1.37926C4.83597 1.29697 4.7681 1.22563 4.69018 1.16688L12.321 1.16667C12.9626 1.16667 13.4876 1.69167 13.4876 2.33333Z" fill="#4E40E5"/>
<path d="M6.56987 10.4932L4.07594 10.4872C3.97929 10.487 3.90113 10.4084 3.90136 10.3118C3.90147 10.2655 3.9199 10.2212 3.95262 10.1884L6.36342 7.77765C6.45454 7.68652 6.60228 7.68652 6.6934 7.77765L7.80586 8.89011L10.756 5.94C10.8471 5.84888 10.9948 5.84888 11.086 5.94C11.1297 5.98376 11.1543 6.04311 11.1543 6.10499V10.325C11.1543 10.4217 11.0759 10.5 10.9793 10.5H6.61845C6.6016 10.5 6.5853 10.4976 6.56987 10.4932Z" fill="#4E40E5"/>
<path d="M4.1543 3.5H5.9043V5.25H4.1543V3.5Z" fill="#4E40E5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { default as pluginIcon } from './plugin.svg';
export { default as workflowIcon } from './workflow.svg';
export { default as imageflowIcon } from './imageflow.svg';
export { default as tableIcon } from './table.svg';
export { default as textIcon } from './text.svg';
export { default as imageIcon } from './image.svg';
export { default as chatflowIcon } from './chatflow.svg';

View File

@@ -0,0 +1,5 @@
<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Component 2">
<path id="Union" fill-rule="evenodd" clip-rule="evenodd" d="M6.90598 0.696144C7.36911 0.428759 7.9397 0.428759 8.40282 0.696144L12.7393 3.19983C13.2025 3.46722 13.4878 3.96136 13.4878 4.49613V9.50351C13.4878 10.0383 13.2025 10.5324 12.7393 10.7998L8.40282 13.3035C7.9397 13.5709 7.3691 13.5709 6.90598 13.3035L2.56946 10.7998C2.10634 10.5324 1.82104 10.0383 1.82104 9.50351V4.49613C1.82104 3.96136 2.10634 3.46722 2.56946 3.19983L6.90598 0.696144ZM11.9063 4.06604L7.81949 1.70651C7.71733 1.64753 7.59147 1.64753 7.48931 1.70651L3.40043 4.06722L7.65235 6.52207L11.9063 4.06604ZM2.98771 5.17609V9.50351C2.98771 9.62147 3.05064 9.73047 3.1528 9.78945L7.06901 12.0505V7.53243L2.98771 5.17609ZM8.23568 12.0528L12.156 9.78945C12.2582 9.73047 12.3211 9.62147 12.3211 9.50351V5.17372L8.23568 7.53243V12.0528Z" fill="#4E40E5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 948 B

View File

@@ -0,0 +1,5 @@
<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Component 2">
<path id="Union" d="M1.82104 2.33342C1.82104 1.68908 2.34338 1.16675 2.98771 1.16675H12.321C12.9654 1.16675 13.4877 1.68908 13.4877 2.33341V11.6667C13.4877 12.3111 12.9654 12.8334 12.321 12.8334H2.98771C2.34338 12.8334 1.82104 12.3111 1.82104 11.6667V2.33342ZM6.19605 2.33341V4.66675L12.321 4.66675V2.33341H6.19605ZM6.19605 5.83341V8.16675L12.321 8.16675V5.83342L6.19605 5.83341ZM5.02938 8.16675V5.83341H2.98771V8.16675H5.02938ZM2.98771 9.33342V11.6667H5.02938V9.33342H2.98771ZM6.19605 9.33342V11.6667H12.321V9.33342H6.19605ZM5.02938 2.33341H2.98771V4.66675L5.02938 4.66675V2.33341Z" fill="#4E40E5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 730 B

View File

@@ -0,0 +1,10 @@
<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Component 2">
<g id="Union">
<path d="M4.73763 4.9585C4.73763 4.63633 4.9988 4.37516 5.32096 4.37516H7.6543C7.97646 4.37516 8.23763 4.63633 8.23763 4.9585C8.23763 5.28066 7.97646 5.54183 7.6543 5.54183H5.32096C4.9988 5.54183 4.73763 5.28066 4.73763 4.9585Z" fill="#4E40E5"/>
<path d="M4.73763 7.29183C4.73763 6.96966 4.9988 6.7085 5.32096 6.7085H9.98763C10.3098 6.7085 10.571 6.96966 10.571 7.29183C10.571 7.614 10.3098 7.87516 9.98763 7.87516H5.32096C4.9988 7.87516 4.73763 7.614 4.73763 7.29183Z" fill="#4E40E5"/>
<path d="M4.73763 9.62516C4.73763 9.303 4.9988 9.04183 5.32096 9.04183H9.98763C10.3098 9.04183 10.571 9.303 10.571 9.62516C10.571 9.94733 10.3098 10.2085 9.98763 10.2085H5.32096C4.9988 10.2085 4.73763 9.94733 4.73763 9.62516Z" fill="#4E40E5"/>
<path d="M2.4043 1.75016C2.4043 1.10583 2.92663 0.583496 3.57096 0.583496H11.7376C12.382 0.583496 12.9043 1.10583 12.9043 1.75016V12.2502C12.9043 12.8945 12.382 13.4168 11.7376 13.4168H3.57096C2.92663 13.4168 2.4043 12.8945 2.4043 12.2502V1.75016ZM3.57096 1.75016V12.2502H11.7376V1.75016H3.57096Z" fill="#4E40E5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,5 @@
<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Component 2">
<path id="Union" fill-rule="evenodd" clip-rule="evenodd" d="M9.06505 5.38087C9.27846 5.65615 9.61235 5.83333 9.98763 5.83333H12.6126C13.257 5.83333 13.7793 5.311 13.7793 4.66667V2.04167C13.7793 1.39733 13.257 0.875 12.6126 0.875H9.98763C9.3433 0.875 8.82096 1.39733 8.82096 2.04167V4.19269L8.80691 4.16985L6.48763 5.5971V5.25C6.48763 4.60567 5.9653 4.08333 5.32096 4.08333L2.69596 4.08333C2.05163 4.08333 1.5293 4.60567 1.5293 5.25V8.75C1.5293 9.39433 2.05163 9.91667 2.69596 9.91667H5.32096C5.9653 9.91667 6.48763 9.39433 6.48763 8.75V8.36035L8.82096 9.72859V11.9583C8.82096 12.6027 9.3433 13.125 9.98763 13.125H12.6126C13.257 13.125 13.7793 12.6027 13.7793 11.9583V9.33333C13.7793 8.689 13.257 8.16667 12.6126 8.16667H9.98763C9.64375 8.16667 9.33463 8.31544 9.12111 8.55214L6.48763 7.00789V6.96697L9.06505 5.38087ZM9.98763 2.04167V4.66667H12.6126V2.04167H9.98763ZM9.98763 9.33333V11.9583H12.6126V9.33333H9.98763ZM2.69596 8.75L2.69596 5.25L5.32096 5.25L5.32096 8.75H2.69596Z" fill="#4E40E5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,30 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useLayoutEffect } from 'react';
import { useInjector } from '@coze-editor/editor/react';
import { keymap, type EditorView } from '@codemirror/view';
export const useKeymap = (
keyMap: string[],
run: (view: EditorView) => boolean,
) => {
const injector = useInjector();
useLayoutEffect(
() => injector.inject([keymap.of(keyMap.map(key => ({ key, run })))]),
[injector, keyMap, run],
);
};

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { LibraryBlockWidget } from './library-block-widget';
export { LibrarySearchPopover } from './library-search-popover';
export {
type ILibraryList,
type ILibraryItems,
type ILibraryItem,
type LibraryBlockInfo,
type LibraryType,
getReferenceType,
} from './types';

View File

@@ -0,0 +1,31 @@
.cm-content {
.library-block-container {
cursor: default;
display: inline-block;
margin-right: 4px;
padding: 0 5px;
color: #4E40E5;
background-color: rgba(186, 192, 255, 20%);
border-radius: 4px;
}
.library-block-icon {
margin-right: 2px;
vertical-align: sub;
}
.library-block-content {
word-break: break-all;
}
}
.library-block-tooltip {
.semi-tooltip-content {
width: 100%;
}
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useEffect, useLayoutEffect, useRef } from 'react';
import { useInjector, useEditor } from '@coze-editor/editor/react';
import { type EditorAPI } from '@coze-editor/editor/preset-prompt';
import { astDecorator } from '@coze-editor/editor';
import { getLibraryStatus } from '../utils/get-library-status';
import {
getLibraryBlockInfoFromTemplate,
getLibraryInfoByBlockInfo,
} from '../utils/get-library-info';
import type { ILibraryItem, ILibraryList } from '../types';
import { TemplateParser } from '../../shared/utils/template-parser';
import { LibraryBlockWidgetType } from './library-block-widget-type';
import './index.css';
const templateParser = new TemplateParser({ mark: 'LibraryBlock' });
function useLatest<T>(value: T) {
const ref = useRef<T>(value);
ref.current = value;
return ref;
}
interface LibraryBlockWidgetProps {
librarys: ILibraryList;
readonly?: boolean;
spaceId?: string;
className?: string;
onAddLibrary?: (
library: ILibraryItem,
pos?: { from: number; to: number },
) => void;
projectId?: string;
avatarBotId?: string;
onRename?: (pos: { from: number; to: number }) => void;
disabledTooltips?: boolean;
}
export const LibraryBlockWidget = (props: LibraryBlockWidgetProps) => {
const ref = useLatest(props);
const editor = useEditor<EditorAPI>();
const editorRef = useLatest(editor);
const injector = useInjector();
useLayoutEffect(
() =>
injector.inject([
astDecorator.whole.of((cursor, state) => {
const {
librarys,
readonly = false,
spaceId,
className,
projectId,
avatarBotId,
onRename,
disabledTooltips,
} = ref.current;
if (templateParser.isOpenNode(cursor.node, state)) {
const open = cursor.node;
const close = templateParser.findCloseNode(open, state);
if (close) {
const openTemplate = state.sliceDoc(open.from, open.to);
const contentFrom = open.to;
const contentTo = close.from;
const content = state.sliceDoc(contentFrom, contentTo);
const dataInfo = getLibraryBlockInfoFromTemplate({
template: openTemplate,
templateParser,
});
const { libraryStatus } = getLibraryStatus({
librarys,
libraryBlockInfo: dataInfo,
content,
});
const libraryInfo = dataInfo
? getLibraryInfoByBlockInfo(librarys, dataInfo)
: null;
return [
{
type: 'replace',
widget: new LibraryBlockWidgetType({
editorRef,
blockDataInfo: dataInfo,
libraryItem: libraryInfo,
readonly,
content,
spaceId,
className,
hightlight: libraryStatus === 'existing',
libraryStatus,
onAddLibrary(library) {
if (typeof ref.current.onAddLibrary === 'function') {
ref.current.onAddLibrary(library, {
from: open.from,
to: close.to,
});
}
},
range: {
left: open.to,
right: close.from,
},
projectId,
avatarBotId,
onRename,
disabledTooltips,
}),
atomicRange: true,
from: open.from,
to: close.to,
},
];
}
}
}),
templateParser.markInfoField,
]),
[injector],
);
useEffect(() => {
if (!editor) {
return;
}
editor?.updateWholeDecorations();
}, [editor, props.librarys]);
return null;
};

View File

@@ -0,0 +1,289 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type RefObject, useLayoutEffect, useState } from 'react';
import { type Root, createRoot } from 'react-dom/client';
import cls from 'classnames';
import { type EditorAPI } from '@coze-editor/editor/preset-prompt';
import { Tooltip } from '@coze-arch/coze-design';
import { WidgetType } from '@codemirror/view';
import { getLibraryTooltip } from '../utils/get-library-tooltip';
import { type LibraryStatus } from '../utils/get-library-status';
import { type LibraryBlockInfo, type ILibraryItem } from '../types';
interface LibraryBlockWidgetOptions {
editorRef: RefObject<EditorAPI>;
blockDataInfo: LibraryBlockInfo | null;
libraryItem: ILibraryItem | null;
content: string;
hightlight: boolean;
libraryStatus: LibraryStatus;
readonly: boolean;
spaceId?: string;
className?: string;
onAddLibrary?: (library: ILibraryItem) => void;
range: {
left: number;
right: number;
};
projectId?: string;
avatarBotId?: string;
onRename?: (pos: { from: number; to: number }) => void;
disabledTooltips?: boolean;
}
function createElement(
name: string,
attributes: Record<string, string>,
children: (HTMLElement | string)[] = [],
) {
const el = document.createElement(name);
for (const [key, value] of Object.entries(attributes)) {
el.setAttribute(key, value);
}
for (const child of children) {
if (typeof child === 'string') {
const text = document.createTextNode(child);
el.appendChild(text);
} else {
el.appendChild(child);
}
}
return el;
}
export class LibraryBlockWidgetType extends WidgetType {
private options: LibraryBlockWidgetOptions | null;
private container: HTMLSpanElement;
private root: Root | null;
private dom: HTMLSpanElement | undefined;
private mounted: boolean;
constructor(options: LibraryBlockWidgetOptions | null) {
super();
this.options = options;
this.container = document.createElement('span');
this.root = null;
this.mounted = false;
}
toDOM() {
if (!this.options) {
return this.container;
}
if (this.root) {
this.destroy();
}
if (!this.mounted) {
// 同步渲染,避免抖动
this.renderLibraryBlock(this.options);
this.renderTooltip(this.options);
this.mounted = true;
}
return this.container;
}
renderLibraryBlock(options: LibraryBlockWidgetOptions) {
const dom = createElement(
'span',
{
class: cls('library-block-container leading-5', options.className, {
'!coz-mg-hglt !text-[#9498F7] !text-opacity-70': !options.hightlight,
}),
},
[
createElement('img', {
src: options.blockDataInfo?.icon || '',
class: cls('library-block-icon', {
'!opacity-70': !options.hightlight,
}),
}),
createElement(
'span',
{
class: 'library-block-content',
},
[options.content],
),
],
);
this.dom = dom;
this.container.appendChild(dom);
}
renderTooltip(options: LibraryBlockWidgetOptions) {
const tooltipTriggerDOM = document.createElement('span');
this.root = createRoot(tooltipTriggerDOM);
this.container.appendChild(tooltipTriggerDOM);
this.root.render(
<LibraryBlockWidgetReactCom
editorRef={options.editorRef}
blockDataInfo={options?.blockDataInfo}
libraryItem={options.libraryItem}
content={options.content}
hightlight={options.hightlight}
readonly={options.readonly}
libraryStatus={options.libraryStatus}
onAddLibrary={options.onAddLibrary}
onRename={options.onRename}
spaceId={options.spaceId ?? ''}
range={options.range}
beforeMount={() => {
const element = this.dom;
if (element?.parentNode) {
element.parentNode.removeChild(element);
}
}}
projectId={options.projectId}
avatarBotId={options.avatarBotId}
disabledTooltips={options.disabledTooltips}
/>,
);
}
destroy() {
this.mounted = false;
if (this.root) {
/**
* Fix React warning: Attempted to synchronously unmount a root while React was already rendering
* https://stackoverflow.com/questions/73043828/how-to-unmount-something-created-with-createroot-properly
*/
setTimeout(() => {
this.root?.unmount();
}, 0);
this.root = null;
}
if (this.dom) {
this.dom.remove();
this.dom = undefined;
}
this.options = null;
}
}
export const LibraryBlockWidgetReactCom = (props: {
editorRef: RefObject<EditorAPI>;
blockDataInfo: LibraryBlockInfo | null;
libraryItem: ILibraryItem | null;
content: string;
hightlight?: boolean;
readonly: boolean;
libraryStatus: LibraryStatus;
onAddLibrary?: (library: ILibraryItem) => void;
spaceId: string;
range: {
left: number;
right: number;
};
beforeMount: () => void;
className?: string;
projectId?: string;
avatarBotId?: string;
onRename?: (pos: { from: number; to: number }) => void;
disabledTooltips?: boolean;
}) => {
const {
blockDataInfo,
libraryItem,
content,
hightlight = true,
libraryStatus,
onAddLibrary,
readonly,
spaceId,
range,
editorRef,
beforeMount,
className,
projectId,
avatarBotId,
onRename,
disabledTooltips,
} = props;
const [tooltipConfig, setTooltipConfig] = useState<
React.ComponentProps<typeof Tooltip> | undefined
>(undefined);
const loadTooltipConfig = async () => {
if (!editorRef.current) {
return;
}
const res = await getLibraryTooltip({
editorRef,
libraryStatus,
readonly,
libraryItem,
blockDataInfo,
onAddLibrary,
spaceId,
range,
projectId,
avatarBotId,
onRename,
disabled: disabledTooltips,
});
setTooltipConfig(res.tooltipConfig);
};
useLayoutEffect(() => {
beforeMount();
}, []);
const baseElement = (
<span
className={cls('library-block-container leading-5', className, {
'!coz-mg-hglt !text-[#9498F7] !text-opacity-70': !hightlight,
})}
onMouseEnter={loadTooltipConfig}
>
<img
src={blockDataInfo?.icon || ''}
className={cls('library-block-icon', {
'!opacity-70': !hightlight,
})}
/>
<span className="library-block-content">{content}</span>
</span>
);
// 只在tooltipConfig存在时渲染Tooltip
if (!tooltipConfig) {
return baseElement;
}
return (
<Tooltip
position="bottomLeft"
spacing={{ y: 4, x: 0 }}
showArrow={false}
{...tooltipConfig}
>
{baseElement}
</Tooltip>
);
};

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { I18n } from '@coze-arch/i18n';
import { Button } from '@coze-arch/coze-design';
import { type ILibraryItem } from '../../types';
interface AddLibraryActionProps {
library: ILibraryItem;
onClick: (library: ILibraryItem) => void;
}
export const AddLibraryAction = ({
library,
onClick,
}: AddLibraryActionProps) => (
<Button onClick={() => onClick(library)} color="primary" size="small">
{I18n.t('add')}
</Button>
);

View File

@@ -0,0 +1,58 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type RefObject } from 'react';
import { type EditorAPI } from '@coze-editor/editor/preset-prompt';
import { I18n } from '@coze-arch/i18n';
import { Button } from '@coze-arch/coze-design';
import { type ILibraryItem } from '../../types';
interface RenameLibraryActionProps {
editorRef: RefObject<EditorAPI>;
library: ILibraryItem;
range: {
left: number;
right: number;
};
onRename?: (pos: { from: number; to: number }) => void;
}
export const RenameLibraryAction = ({
editorRef,
library,
range,
onRename,
}: RenameLibraryActionProps) => {
const handleRename = () => {
if (!editorRef.current) {
return;
}
editorRef.current?.$view.dispatch({
changes: {
from: range.left,
to: range.right,
insert: library.name,
},
});
onRename?.({ from: range.left, to: range.right });
};
return (
<Button onClick={handleRename} color="primary" size="small">
{I18n.t('edit_block_api_rename')}
</Button>
);
};

View File

@@ -0,0 +1,140 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useEffect, useState } from 'react';
import { nanoid } from 'nanoid';
import {
PositionMirror,
useChangeListener,
useEditor,
} from '@coze-editor/editor/react';
import { type EditorAPI } from '@coze-editor/editor/preset-prompt';
import { I18n } from '@coze-arch/i18n';
import { Popover } from '@coze-arch/coze-design';
import { type ViewUpdate } from '@codemirror/view';
import { syntaxTree } from '@codemirror/language';
import type { ILibraryList } from '../types';
import { TemplateParser } from '../../shared/utils/template-parser';
import { LibraryList } from './library-list';
interface LibrarySearchPopoverProps {
librarys: ILibraryList;
direction?: React.ComponentProps<typeof Popover>['position'];
}
const templateParser = new TemplateParser({ mark: 'LibraryBlock' });
export const LibrarySearchPopover = ({
librarys,
direction = 'bottomLeft',
}: LibrarySearchPopoverProps) => {
const [reposKey, setReposKey] = useState('');
const [visible, setVisible] = useState(false);
const [position, setPosition] = useState(-1);
const [toPosition, setToPosition] = useState(-1);
const emptyLibrary = librarys.map(item => item.items).flat().length === 0;
const editor = useEditor<EditorAPI>();
useChangeListener(e => {
const [_fromA, _toA, fromB, toB, inserted] = e.change;
if (['{}', '{'].includes(inserted.toString())) {
const node = syntaxTree(e.view.state).resolve(
inserted.toString() === '{}' ? toB - 1 : toB,
);
if (node.name === 'JinjaText') {
setPosition(fromB);
setToPosition(toB);
setVisible(true);
} else {
// 一些场景输入 { 不应该弹出提示面板
// 如:光标在 {{ }} 内,或在 {% %} 内,或在 {# #} 内
setVisible(false);
}
} else {
setVisible(false);
}
});
useEffect(() => {
if (!editor) {
return;
}
function handleViewUpdate(update: ViewUpdate) {
if (update.transactions.some(tr => tr.isUserEvent('select'))) {
setVisible(false);
}
}
editor.$on('viewUpdate', handleViewUpdate);
return () => {
editor.$off('viewUpdate', handleViewUpdate);
};
}, [editor]);
return (
<>
<Popover
rePosKey={reposKey}
visible={visible}
trigger="custom"
position={emptyLibrary ? 'bottomLeft' : direction}
onClickOutSide={() => setVisible(false)}
autoAdjustOverflow
content={
!emptyLibrary ? (
<div className="flex flex-col p-1 w-[352px] max-h-[330px] overflow-y-auto">
<LibraryList
librarys={librarys}
onInsert={insertLibrary => {
const { name = '', id = '', type } = insertLibrary;
const uuid = nanoid();
const template = templateParser.generateTemplate({
content: name,
data: {
id,
uuid,
type,
...(type === 'plugin' && {
apiId: insertLibrary.api_id,
}),
},
});
// 去除插入的快捷键, 因为原来自动添加了
templateParser.insertTemplateByRange(editor, template, {
from: position,
to: toPosition,
});
setVisible(false);
}}
/>
</div>
) : (
<div className="coz-fg-primary text-sm font-medium px-3 py-2">
{I18n.t('edit_block_api_empty')}
</div>
)
}
>
<PositionMirror
position={position}
onChange={() => setReposKey(String(Math.random()))}
/>
</Popover>
</>
);
};

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type FC, type ReactNode } from 'react';
import classNames from 'classnames';
import { Typography, Highlight } from '@coze-arch/coze-design';
interface LibraryItemProps {
title: string;
description?: string;
avatar: string;
icons?: ReactNode;
actions?: ReactNode;
className?: string;
searchWords?: string[];
}
export const LibraryItem: FC<LibraryItemProps> = ({
title,
description,
avatar,
icons,
actions,
className,
searchWords,
}) => (
<div
className={classNames(
'w-full flex flex-row items-center coz-bg-max rounded-[8px]',
' gap-3',
className,
)}
>
<div
className={classNames(
'flex flex-row flex-1 min-w-[0px] justify-center items-center ',
)}
>
{avatar ? (
<img
src={avatar}
className={classNames(
'w-[24px] h-[24px] rounded-[5px]',
'overflow-hidden',
)}
/>
) : null}
<div
className={classNames(
'ml-[8px]',
'flex flex-col flex-1 min-w-[0px] w-0',
)}
>
<div className="flex flex-row items-center overflow-hidden">
<Typography.Paragraph
ellipsis={{
showTooltip: true,
}}
className={classNames(
'text-[14px] leading-[20px]',
'coz-fg-primary truncate flex-1 font-medium',
)}
>
<Highlight
sourceString={title}
searchWords={searchWords}
highlightStyle={{
color: 'var(--coz-fg-hglt-yellow, #FF7300)',
backgroundColor: 'transparent',
}}
/>
</Typography.Paragraph>
<div className="justify-self-end grid grid-flow-col gap-x-[2px]">
{icons}
</div>
</div>
</div>
</div>
<div className={classNames('grid grid-flow-col gap-x-[2px]')}>
{actions}
</div>
</div>
);

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import classnames from 'classnames';
import { I18n } from '@coze-arch/i18n';
import { Typography } from '@coze-arch/coze-design';
import {
type ILibraryList,
type ILibraryItem,
type LibraryType,
} from '../types';
import { LibraryItem } from './library-item';
import { AddLibraryAction } from './actions/add-library-action';
interface LibraryListProps {
librarys: ILibraryList;
onInsert?: (library: ILibraryItem) => void;
libraryItemClassName?: string;
searchWords?: string[];
}
const LibraryTypeTextMap: Record<LibraryType, string> = {
plugin: I18n.t('edit_block_api_plugin'),
workflow: I18n.t('edit_block_api_workflow'),
imageflow: I18n.t('edit_block_api_imageflow'),
text: I18n.t('edit_block_api_knowledge_text'),
image: I18n.t('edit_block_api_knowledge_image'),
table: I18n.t('edit_block_api_knowledge_table'),
};
export const LibraryList = ({
librarys,
onInsert,
libraryItemClassName,
searchWords,
}: LibraryListProps) => (
<div className="flex flex-col gap-2">
{Object.values(librarys).map(library => {
const { items, type } = library;
if (items.length === 0) {
return null;
}
return (
<div key={type} className="flex flex-col">
<Typography.Text className="coz-fg-tertiary text-xs mb-1 px-2 pt-2 pb-1">
{LibraryTypeTextMap[type]}
</Typography.Text>
{items.map(item => {
const { name, desc, icon_url } = item;
return (
<LibraryItem
searchWords={searchWords}
key={name}
title={name || ''}
description={desc || ''}
avatar={icon_url || ''}
className={classnames('p-[8px]', libraryItemClassName)}
actions={
<AddLibraryAction
library={{ ...item, type }}
onClick={libraryItem => {
onInsert?.(libraryItem);
}}
/>
}
/>
);
})}
</div>
);
})}
</div>
);

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
PromptReferenceType,
type PromptReferenceInfo,
} from '@coze-arch/idl/playground_api';
export type LibraryType =
| 'plugin'
| 'workflow'
| 'imageflow'
| 'table'
| 'text'
| 'image';
export interface ILibraryItems {
type: LibraryType;
items: ILibraryItem[];
}
export type ILibraryList = ILibraryItems[];
export type ILibraryItem = PromptReferenceInfo & {
type: LibraryType;
id: string;
icon_url: string;
name: string;
desc: string;
};
export const getReferenceType = (type: LibraryType): PromptReferenceType => {
switch (type) {
case 'plugin':
return PromptReferenceType.Plugin;
case 'workflow':
return PromptReferenceType.Workflow;
case 'imageflow':
return PromptReferenceType.ImageFlow;
case 'text':
return PromptReferenceType.Knowledge;
case 'image':
return PromptReferenceType.Knowledge;
case 'table':
return PromptReferenceType.Knowledge;
default:
return PromptReferenceType.Plugin;
}
};
export interface LibraryBlockInfo {
[key: string]: string | undefined;
icon: string;
type: LibraryType;
id: string;
uuid: string;
apiId?: string;
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { merge } from 'lodash-es';
import {
type LibraryType,
type ILibraryList,
type ILibraryItem,
type LibraryBlockInfo,
} from '../types';
import {
pluginIcon,
workflowIcon,
imageflowIcon,
tableIcon,
textIcon,
imageIcon,
} from '../assets';
import { type TemplateParser } from '../../shared/utils/template-parser';
import { checkLibraryId } from './library-validate';
const defaultLibraryBlockInfo: Record<
LibraryType,
{
icon: string;
}
> = {
plugin: {
icon: pluginIcon,
},
workflow: {
icon: workflowIcon,
},
imageflow: {
icon: imageflowIcon,
},
table: {
icon: tableIcon,
},
text: {
icon: textIcon,
},
image: {
icon: imageIcon,
},
};
// 根据资源类型获取对应的信息
export const getLibraryBlockInfoFromTemplate = (props: {
template: string;
templateParser: TemplateParser;
}): LibraryBlockInfo | null => {
const { template, templateParser } = props;
const data = templateParser.getData(template);
if (!data) {
return null;
}
const { type, ...rest } = data as LibraryBlockInfo;
const libraryBlockInfo = merge({}, defaultLibraryBlockInfo[type], {
type,
...rest,
});
return libraryBlockInfo;
};
export const getLibraryInfoByBlockInfo = (
librarys: ILibraryList,
blockInfo: LibraryBlockInfo,
): ILibraryItem | null => {
if (!librarys || !blockInfo) {
return null;
}
const libraryTypeList = librarys.find(
library => library.type === blockInfo.type,
);
return (
(libraryTypeList?.items as ILibraryItem[])?.find(item =>
checkLibraryId(item, blockInfo),
) ?? null
);
};

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type ILibraryList, type LibraryBlockInfo } from '../types';
import { findTargetLibrary, isLibraryNameOutDate } from './library-validate';
interface LibraryBlockTooltipProps {
librarys: ILibraryList;
libraryBlockInfo: LibraryBlockInfo | null;
content: string;
}
export type LibraryStatus = 'disabled' | 'existing' | 'outdated';
export const getLibraryStatus = ({
librarys,
libraryBlockInfo,
content,
}: LibraryBlockTooltipProps): {
libraryStatus: LibraryStatus;
} => {
let libraryStatus: LibraryStatus = 'disabled';
if (!libraryBlockInfo) {
return {
libraryStatus: 'disabled',
};
}
const targetLibrary = findTargetLibrary(librarys, libraryBlockInfo);
if (!targetLibrary) {
return {
libraryStatus: 'disabled',
};
}
const isOutdated = isLibraryNameOutDate(content, targetLibrary);
if (isOutdated) {
libraryStatus = 'outdated';
} else {
libraryStatus = 'existing';
}
return {
libraryStatus,
};
};

View File

@@ -0,0 +1,247 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type RefObject } from 'react';
import { type EditorAPI } from '@coze-editor/editor/preset-prompt';
import { I18n } from '@coze-arch/i18n';
import { type Tooltip } from '@coze-arch/coze-design';
import { type ILibraryItem, type LibraryBlockInfo } from '../types';
import { LibraryItem } from '../library-search-popover/library-item';
import { RenameLibraryAction } from '../library-search-popover/actions/rename-library-action';
import { AddLibraryAction } from '../library-search-popover/actions/add-library-action';
import { requestLibraryInfo } from './library-validate';
import type { LibraryStatus } from './get-library-status';
interface GetLibraryTooltipProps {
editorRef: RefObject<EditorAPI>;
libraryStatus: LibraryStatus;
libraryItem: ILibraryItem | null;
readonly: boolean;
blockDataInfo: LibraryBlockInfo | null;
spaceId: string;
onAddLibrary?: (library: ILibraryItem) => void;
range: {
left: number;
right: number;
};
projectId?: string;
avatarBotId?: string;
onRename?: (pos: { from: number; to: number }) => void;
disabled?: boolean;
}
export const getLibraryTooltip = async ({
editorRef,
libraryStatus,
libraryItem,
blockDataInfo,
onAddLibrary,
readonly,
spaceId,
range,
projectId,
avatarBotId,
onRename,
disabled,
}: GetLibraryTooltipProps): Promise<{
tooltipConfig?: React.ComponentProps<typeof Tooltip>;
}> => {
try {
if (disabled) {
return getHiddenLibraryTooltip();
}
if (readonly) {
return getDisabledLibraryTooltip();
}
const { type, id } = blockDataInfo ?? {};
if (!blockDataInfo || !type || !id) {
return getDisabledLibraryTooltip();
}
if (libraryStatus === 'disabled' || !libraryItem) {
return await getPublicLibraryTooltip({
blockDataInfo,
spaceId,
onAddLibrary,
projectId,
avatarBotId,
});
}
if (libraryStatus === 'outdated') {
return getOutdatedLibraryTooltip({
editorRef,
item: libraryItem,
range,
onRename,
});
}
if (libraryStatus === 'existing') {
return getExistingLibraryTooltip(libraryItem);
}
return getDisabledLibraryTooltip();
} catch (error) {
const errorMsg = (error as { msg: string }).msg;
if (errorMsg) {
return {
tooltipConfig: {
content: errorMsg,
},
};
}
return getDisabledLibraryTooltip();
}
};
const getExistingLibraryTooltip = (
libraryItem: ILibraryItem,
): {
tooltipConfig?: React.ComponentProps<typeof Tooltip>;
} => ({
tooltipConfig: {
content: (
<LibraryItem
title={libraryItem.name ?? ''}
description={libraryItem.desc ?? ''}
avatar={libraryItem.icon_url ?? ''}
className="!p-0"
/>
),
className: 'library-block-tooltip !w-[310px] !p-2',
},
});
const getDisabledLibraryTooltip = (
msg?: string,
): {
tooltipConfig?: React.ComponentProps<typeof Tooltip>;
} => ({
tooltipConfig: {
content: msg ?? I18n.t('edit_block_api_disable_tooltips'),
},
});
const getHiddenLibraryTooltip = (): {
tooltipConfig?: React.ComponentProps<typeof Tooltip>;
} => ({
tooltipConfig: {
visible: false,
trigger: 'custom',
},
});
const getOutdatedLibraryTooltip = ({
item,
range,
onRename,
editorRef,
}: {
editorRef: RefObject<EditorAPI>;
item: ILibraryItem;
range: {
left: number;
right: number;
};
onRename?: (pos: { from: number; to: number }) => void;
}): {
tooltipConfig?: React.ComponentProps<typeof Tooltip>;
} => ({
tooltipConfig: {
content: (
<LibraryItem
title={item.name ?? ''}
description={item.desc ?? ''}
avatar={item.icon_url ?? ''}
className="!p-0"
actions={
<RenameLibraryAction
library={item}
range={range}
editorRef={editorRef}
onRename={onRename}
/>
}
/>
),
className: 'library-block-tooltip !w-[310px] !p-2',
},
});
const getPublicLibraryTooltip = async ({
blockDataInfo,
spaceId,
onAddLibrary,
projectId,
avatarBotId,
}: {
blockDataInfo: LibraryBlockInfo | null;
spaceId: string;
onAddLibrary?: (library: ILibraryItem) => void;
projectId?: string;
avatarBotId?: string;
}): Promise<{
tooltipConfig?: React.ComponentProps<typeof Tooltip>;
}> => {
const { id, type } = blockDataInfo ?? {};
if (!id || !type) {
return getDisabledLibraryTooltip();
}
try {
const libraryInfo = await requestLibraryInfo({
id,
type,
...(type === 'plugin' && {
apiId: blockDataInfo?.apiId,
}),
spaceId,
projectId,
avatarBotId,
});
if (!libraryInfo) {
return getDisabledLibraryTooltip();
}
return {
tooltipConfig: {
content: (
<LibraryItem
title={libraryInfo.name ?? ''}
description={libraryInfo.desc ?? ''}
avatar={libraryInfo.icon_url ?? ''}
className="!p-0"
actions={
onAddLibrary ? (
<AddLibraryAction
library={libraryInfo}
onClick={onAddLibrary}
/>
) : undefined
}
/>
),
className: 'library-block-tooltip !w-[310px] !p-2',
showArrow: false,
},
};
} catch (error) {
return getDisabledLibraryTooltip((error as { msg: string }).msg);
}
};

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { PlaygroundApi } from '@coze-arch/bot-api';
import {
type ILibraryItem,
type ILibraryList,
type LibraryBlockInfo,
getReferenceType,
type LibraryType,
} from '../types';
export const findTargetLibrary = (
librarys: ILibraryList,
libraryCheck: LibraryBlockInfo,
): ILibraryItem | undefined => {
const { type: checkType } = libraryCheck;
const libraryGroup = librarys.find(
({ type, items }) =>
type === checkType &&
items.some(item => checkLibraryId(item, libraryCheck)),
);
const targetItem = libraryGroup?.items.find(item =>
checkLibraryId(item, libraryCheck),
);
if (!targetItem) {
return undefined;
}
return {
...targetItem,
type: checkType,
};
};
export const checkLibraryId = (
library: ILibraryItem,
libraryCheck: LibraryBlockInfo,
) => {
const { apiId: checkApiId, id: checkId } = libraryCheck;
if (checkApiId) {
return library.api_id === checkApiId;
}
return library.id === checkId;
};
export const isLibraryNameOutDate = (
content: string,
latestLibrary: ILibraryItem,
): boolean => content !== latestLibrary.name;
export const requestLibraryInfo = async (props: {
id: string;
type: LibraryType;
spaceId?: string;
apiId?: string;
projectId?: string;
avatarBotId?: string;
}): Promise<ILibraryItem | undefined> => {
const { id, type, apiId, spaceId, projectId, avatarBotId } = props;
const { data } = await PlaygroundApi.GetPromptReferenceInfo(
{
reference_id: id,
reference_type: getReferenceType(type),
api_id: apiId,
space_id: spaceId ?? '',
project_id: projectId,
avatar_bot_id: avatarBotId,
},
{
__disableErrorToast: true,
},
);
if (!data) {
return undefined;
}
return {
...(data as ILibraryItem),
type,
};
};