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,81 @@
/*
* 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 React, { useRef, useState } from 'react';
import { type IntelligenceData } from '@coze-arch/idl/intelligence_api';
import { I18n } from '@coze-arch/i18n';
import { Modal, Search } from '@coze-arch/coze-design';
import { useIntelligenceSearch } from '../hooks/use-case/use-intelligence-search';
import { IntelligenceList } from './intelligence-list';
export interface SelectIntelligenceModalProps {
visible: boolean;
spaceId: string;
onSelect?: (intelligence: IntelligenceData) => void;
onCancel: () => void;
}
export const SelectIntelligenceModal: React.FC<
SelectIntelligenceModalProps
> = ({ visible, onCancel, onSelect, spaceId }) => {
const containerRef = useRef<HTMLDivElement>(null);
const [searchValue, setSearchValue] = useState('');
const { loading, data, loadingMore, noMore } = useIntelligenceSearch({
spaceId,
searchValue,
containerRef,
});
return (
<Modal
visible={visible}
onCancel={onCancel}
width={640}
height={588}
className="[&_.semi-modal-header]:flex [&_.semi-modal-header]:items-center [&_.semi-modal-header]:px-3 [&_.semi-modal-content]:!px-3 [&_.semi-modal-content]:!coz-bg-max"
footer={null}
title={
<div className="flex items-center justify-between w-full mr-4">
<div className="coz-fg-plus text-[20px] font-medium">
{I18n.t('select_agent_title')}
</div>
<Search
placeholder={I18n.t('Search')}
value={searchValue}
onChange={setSearchValue}
showClear
/>
</div>
}
>
<div ref={containerRef} className="max-h-[480px] h-full overflow-auto">
<IntelligenceList
loading={loading}
loadingMore={loadingMore}
noMore={noMore}
data={data}
searchValue={searchValue}
onSelect={intelligenceData => {
onSelect?.(intelligenceData);
}}
/>
</div>
</Modal>
);
};

View File

@@ -0,0 +1,89 @@
/*
* 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 React from 'react';
import { type IntelligenceData } from '@coze-arch/idl/intelligence_api';
import { I18n } from '@coze-arch/i18n';
import { Typography } from '@coze-arch/coze-design';
import { formatDate, getFormatDateType } from '@coze-arch/bot-utils';
import { highlightService } from '../services/use-case-services/highlight-text.service';
interface IntelligenceItemProps {
intelligence: IntelligenceData;
searchValue: string;
onClick: () => void;
}
export const IntelligenceItem: React.FC<IntelligenceItemProps> = ({
intelligence,
searchValue,
onClick,
}) => {
const { basic_info } = intelligence;
return (
<div
className="rounded-lg hover:coz-mg-secondary-hovered cursor-pointer h-[80px] box-border"
onClick={onClick}
>
<div className="flex items-center gap-[14px] px-3 h-full">
<img
src={basic_info?.icon_url}
className="flex-shrink-0 w-[52px] h-[52px] rounded-lg"
alt={basic_info?.name}
/>
<div className="w-full overflow-hidden flex flex-col gap-1 justify-center border-b-[0.6px] border-solid border-0 coz-stroke-primary pb-3 pt-2 h-full">
<div className="font-medium text-sm">
<Typography.Text className="text-[16px] !font-medium w-full">
{highlightService.highlightText(
basic_info?.name || '',
searchValue,
)}
</Typography.Text>
</div>
{basic_info?.description ? (
<div className="text-sm leading-4 coz-fg-secondary">
<Typography.Text
className="text-sm w-full"
ellipsis={{
rows: 1,
}}
>
{highlightService.highlightText(
basic_info?.description || '',
searchValue,
)}
</Typography.Text>
</div>
) : null}
<div className="text-xs coz-fg-secondary flex items-center gap-1">
<div className="text-xs coz-fg-secondary">
{I18n.t('bot_list_rank_tag_edited')}
</div>
<div className="text-xs coz-fg-secondary">
{formatDate(
Number(basic_info?.update_time),
getFormatDateType(Number(basic_info?.update_time)),
)}
</div>
</div>
</div>
</div>
</div>
);
};

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 React from 'react';
import { type IntelligenceData } from '@coze-arch/idl/intelligence_api';
import { I18n } from '@coze-arch/i18n';
import { IconCozLoading, IconCozEmpty } from '@coze-arch/coze-design/icons';
import { Spin, IconButton } from '@coze-arch/coze-design';
import { IntelligenceItem } from './intelligence-item';
interface IntelligenceListProps {
loading: boolean;
loadingMore: boolean;
noMore: boolean;
data?: {
list: IntelligenceData[];
hasMore: boolean;
};
searchValue: string;
onSelect: (intelligence: IntelligenceData) => void;
}
export const IntelligenceList: React.FC<IntelligenceListProps> = ({
loading,
loadingMore,
noMore,
data,
searchValue,
onSelect,
}) => {
if (loading) {
return (
<div className="flex justify-center py-8">
<Spin />
</div>
);
}
if (!data?.list.length) {
return (
<div className="flex flex-col justify-center items-center w-full h-full">
<IconCozEmpty className="w-[48px] h-[48px] coz-fg-dim" />
<div className="text-sm coz-fg-primary mt-2">
{I18n.t('select_agent_no_result')}
</div>
</div>
);
}
return (
<div className="relative h-full">
{/* 上遮罩 */}
<div className="sticky top-0 left-0 right-0 h-[20px] bg-gradient-to-b from-[rgba(255,255,255,1)] to-transparent pointer-events-none z-10" />
{/* 列表内容 */}
<div className="styled-scrollbar">
{data.list.map(intelligence => (
<IntelligenceItem
key={intelligence.basic_info?.id}
intelligence={intelligence}
searchValue={searchValue}
onClick={() => onSelect(intelligence)}
/>
))}
{loadingMore ? (
<div className="flex items-center justify-center h-[38px] my-[20px] text-[12px]">
<IconButton icon={<IconCozLoading />} loading color="secondary" />
<div>{I18n.t('Loading')}...</div>
</div>
) : null}
{noMore && data.list.length > 0 ? (
<div className="h-[38px] my-[20px]" />
) : null}
</div>
{/* 下遮罩 */}
<div className="sticky bottom-0 left-0 right-0 h-[20px] bg-gradient-to-t from-[rgba(255,255,255,1)] to-transparent pointer-events-none z-10" />
</div>
);
};

View File

@@ -0,0 +1,45 @@
/*
* 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 { useInfiniteScroll } from 'ahooks';
import { type IntelligenceList } from '../../services/use-case-services/intelligence-search.service';
import { intelligenceSearchService } from '../../services/use-case-services/intelligence-search.service';
interface UseIntelligenceSearchProps {
spaceId: string;
searchValue: string;
containerRef: React.RefObject<HTMLElement>;
}
export const useIntelligenceSearch = ({
spaceId,
searchValue,
containerRef,
}: UseIntelligenceSearchProps) =>
useInfiniteScroll<IntelligenceList>(
async d =>
await intelligenceSearchService.searchIntelligence({
spaceId,
searchValue,
cursorId: d?.nextCursorId,
}),
{
target: containerRef,
isNoMore: d => !d?.hasMore,
reloadDeps: [searchValue],
},
);

View File

@@ -0,0 +1,52 @@
/*
* 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 { useState } from 'react';
import { type IntelligenceData } from '@coze-arch/idl/intelligence_api';
import { SelectIntelligenceModal } from '../../components';
interface ModalProps {
spaceId: string;
onSelect?: (intelligence: IntelligenceData) => void;
onCancel?: () => void;
}
export const useModal = (props: ModalProps) => {
const [visible, setVisible] = useState(false);
const close = () => {
setVisible(false);
};
const open = () => {
setVisible(true);
};
return {
node: visible ? (
<SelectIntelligenceModal
visible={visible}
spaceId={props.spaceId}
onSelect={props.onSelect}
onCancel={close}
/>
) : null,
close,
open,
};
};

View File

@@ -0,0 +1,18 @@
/*
* 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 { SelectIntelligenceModal } from './components';
export { useModal } from './hooks/use-case/use-modal';

View File

@@ -0,0 +1,43 @@
/*
* 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 React from 'react';
export class HighlightTextService {
highlightText(text: string, keyword: string) {
if (!keyword) {
return text;
}
const parts = text.split(new RegExp(`(${keyword})`, 'gi'));
return (
<>
{parts.map((part, i) =>
part.toLowerCase() === keyword.toLowerCase() ? (
<span key={i} className="coz-fg-hglt-yellow">
{part}
</span>
) : (
part
),
)}
</>
);
}
}
export const highlightService = new HighlightTextService();

View File

@@ -0,0 +1,71 @@
/*
* 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 {
search,
IntelligenceStatus,
type IntelligenceData,
SearchScope,
IntelligenceType,
BotMode,
} from '@coze-arch/idl/intelligence_api';
import { intelligenceApi } from '@coze-arch/bot-api';
export interface IntelligenceList {
list: IntelligenceData[];
hasMore: boolean;
nextCursorId?: string;
}
export const intelligenceSearchService = {
async searchIntelligence(params: {
spaceId: string;
searchValue: string;
cursorId?: string;
}): Promise<IntelligenceList> {
const resp = await intelligenceApi.GetDraftIntelligenceList({
space_id: params.spaceId,
name: params.searchValue,
size: 20,
cursor_id: params.cursorId,
order_by: search.OrderBy.UpdateTime,
types: [IntelligenceType.Bot],
status: [IntelligenceStatus.Using],
search_scope: SearchScope.CreateByMe,
option: {
need_replica: true,
},
});
const intelligenceList = resp?.data?.intelligences ?? [];
// 只保留single mode bot
const singleModeBotList = intelligenceList.filter(
intelligence => intelligence.other_info?.bot_mode === BotMode.SingleMode,
);
if (resp?.code === 0 && resp?.data) {
return {
list: singleModeBotList,
hasMore: Boolean(resp.data.has_more),
nextCursorId: resp.data.next_cursor_id,
};
}
return {
list: [],
hasMore: false,
};
},
};