mirror of
https://github.com/langgenius/dify.git
synced 2026-01-07 06:48:28 +00:00
Compare commits
9 Commits
feat/memor
...
deploy/tri
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7dfe615613 | ||
|
|
a1a3fa0283 | ||
|
|
ff7344f3d3 | ||
|
|
bcd33be22a | ||
|
|
991f31f195 | ||
|
|
f8b10c2272 | ||
|
|
369892634d | ||
|
|
8e5cb86409 | ||
|
|
a85afe4d07 |
@@ -1004,6 +1004,11 @@ class RagPipelineRecommendedPluginApi(Resource):
|
||||
@login_required
|
||||
@account_initialization_required
|
||||
def get(self):
|
||||
parser = reqparse.RequestParser()
|
||||
parser.add_argument('type', type=str, location='args', required=False, default='all')
|
||||
args = parser.parse_args()
|
||||
type = args["type"]
|
||||
|
||||
rag_pipeline_service = RagPipelineService()
|
||||
recommended_plugins = rag_pipeline_service.get_recommended_plugins()
|
||||
recommended_plugins = rag_pipeline_service.get_recommended_plugins(type)
|
||||
return recommended_plugins
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
"""Alter table pipeline_recommended_plugins add column type
|
||||
|
||||
Revision ID: 6bb0832495f0
|
||||
Revises: 7bb281b7a422
|
||||
Create Date: 2025-12-15 16:14:38.482072
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import models as models
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '6bb0832495f0'
|
||||
down_revision = '7bb281b7a422'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('app_triggers', schema=None) as batch_op:
|
||||
batch_op.alter_column('provider_name',
|
||||
existing_type=sa.VARCHAR(length=255),
|
||||
nullable=False,
|
||||
existing_server_default=sa.text("''::character varying"))
|
||||
|
||||
with op.batch_alter_table('operation_logs', schema=None) as batch_op:
|
||||
batch_op.alter_column('content',
|
||||
existing_type=postgresql.JSON(astext_type=sa.Text()),
|
||||
nullable=False)
|
||||
|
||||
with op.batch_alter_table('pipeline_recommended_plugins', schema=None) as batch_op:
|
||||
batch_op.add_column(sa.Column('type', sa.String(length=50), nullable=True))
|
||||
|
||||
with op.batch_alter_table('providers', schema=None) as batch_op:
|
||||
batch_op.alter_column('quota_used',
|
||||
existing_type=sa.BIGINT(),
|
||||
nullable=False)
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
with op.batch_alter_table('providers', schema=None) as batch_op:
|
||||
batch_op.alter_column('quota_used',
|
||||
existing_type=sa.BIGINT(),
|
||||
nullable=True)
|
||||
|
||||
with op.batch_alter_table('pipeline_recommended_plugins', schema=None) as batch_op:
|
||||
batch_op.drop_column('type')
|
||||
|
||||
with op.batch_alter_table('operation_logs', schema=None) as batch_op:
|
||||
batch_op.alter_column('content',
|
||||
existing_type=postgresql.JSON(astext_type=sa.Text()),
|
||||
nullable=True)
|
||||
|
||||
with op.batch_alter_table('app_triggers', schema=None) as batch_op:
|
||||
batch_op.alter_column('provider_name',
|
||||
existing_type=sa.VARCHAR(length=255),
|
||||
nullable=True,
|
||||
existing_server_default=sa.text("''::character varying"))
|
||||
|
||||
# ### end Alembic commands ###
|
||||
@@ -1458,6 +1458,7 @@ class PipelineRecommendedPlugin(TypeBase):
|
||||
)
|
||||
plugin_id: Mapped[str] = mapped_column(LongText, nullable=False)
|
||||
provider_name: Mapped[str] = mapped_column(LongText, nullable=False)
|
||||
type: Mapped[str] = mapped_column(sa.String(50), nullable=True)
|
||||
position: Mapped[int] = mapped_column(sa.Integer, nullable=False, default=0)
|
||||
active: Mapped[bool] = mapped_column(sa.Boolean, nullable=False, default=True)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
|
||||
@@ -19,7 +19,7 @@ class StringUUID(TypeDecorator[uuid.UUID | str | None]):
|
||||
def process_bind_param(self, value: uuid.UUID | str | None, dialect: Dialect) -> str | None:
|
||||
if value is None:
|
||||
return value
|
||||
elif dialect.name == "postgresql":
|
||||
elif dialect.name in ["postgresql", "mysql"]:
|
||||
return str(value)
|
||||
else:
|
||||
if isinstance(value, uuid.UUID):
|
||||
|
||||
@@ -907,19 +907,29 @@ class WorkflowNodeExecutionModel(Base): # This model is expected to have `offlo
|
||||
@property
|
||||
def extras(self) -> dict[str, Any]:
|
||||
from core.tools.tool_manager import ToolManager
|
||||
from core.trigger.trigger_manager import TriggerManager
|
||||
|
||||
extras: dict[str, Any] = {}
|
||||
if self.execution_metadata_dict:
|
||||
if self.node_type == NodeType.TOOL and "tool_info" in self.execution_metadata_dict:
|
||||
tool_info: dict[str, Any] = self.execution_metadata_dict["tool_info"]
|
||||
execution_metadata = self.execution_metadata_dict
|
||||
if execution_metadata:
|
||||
if self.node_type == NodeType.TOOL and "tool_info" in execution_metadata:
|
||||
tool_info: dict[str, Any] = execution_metadata["tool_info"]
|
||||
extras["icon"] = ToolManager.get_tool_icon(
|
||||
tenant_id=self.tenant_id,
|
||||
provider_type=tool_info["provider_type"],
|
||||
provider_id=tool_info["provider_id"],
|
||||
)
|
||||
elif self.node_type == NodeType.DATASOURCE and "datasource_info" in self.execution_metadata_dict:
|
||||
datasource_info = self.execution_metadata_dict["datasource_info"]
|
||||
elif self.node_type == NodeType.DATASOURCE and "datasource_info" in execution_metadata:
|
||||
datasource_info = execution_metadata["datasource_info"]
|
||||
extras["icon"] = datasource_info.get("icon")
|
||||
elif self.node_type == NodeType.TRIGGER_PLUGIN and "trigger_info" in execution_metadata:
|
||||
trigger_info = execution_metadata["trigger_info"] or {}
|
||||
provider_id = trigger_info.get("provider_id")
|
||||
if provider_id:
|
||||
extras["icon"] = TriggerManager.get_trigger_plugin_icon(
|
||||
tenant_id=self.tenant_id,
|
||||
provider_id=provider_id,
|
||||
)
|
||||
return extras
|
||||
|
||||
def _get_offload_by_type(self, type_: ExecutionOffLoadType) -> Optional["WorkflowNodeExecutionOffload"]:
|
||||
|
||||
@@ -1248,12 +1248,14 @@ class RagPipelineService:
|
||||
session.commit()
|
||||
return workflow_node_execution_db_model
|
||||
|
||||
def get_recommended_plugins(self) -> dict:
|
||||
def get_recommended_plugins(self, type: str) -> dict:
|
||||
# Query active recommended plugins
|
||||
query = db.session.query(PipelineRecommendedPlugin).where(PipelineRecommendedPlugin.active == True)
|
||||
if type and type != "all":
|
||||
query = query.where(PipelineRecommendedPlugin.type == type)
|
||||
|
||||
pipeline_recommended_plugins = (
|
||||
db.session.query(PipelineRecommendedPlugin)
|
||||
.where(PipelineRecommendedPlugin.active == True)
|
||||
.order_by(PipelineRecommendedPlugin.position.asc())
|
||||
query.order_by(PipelineRecommendedPlugin.position.asc())
|
||||
.all()
|
||||
)
|
||||
|
||||
|
||||
1422
api/tests/unit_tests/core/plugin/test_plugin_manager.py
Normal file
1422
api/tests/unit_tests/core/plugin/test_plugin_manager.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
||||
'use client'
|
||||
import { useState } from 'react'
|
||||
import useSWR from 'swr'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiGraduationCapFill,
|
||||
@@ -23,8 +22,9 @@ import PremiumBadge from '@/app/components/base/premium-badge'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import EmailChangeModal from './email-change-modal'
|
||||
import { validPassword } from '@/config'
|
||||
import { fetchAppList } from '@/service/apps'
|
||||
|
||||
import type { App } from '@/types/app'
|
||||
import { useAppList } from '@/service/use-apps'
|
||||
|
||||
const titleClassName = `
|
||||
system-sm-semibold text-text-secondary
|
||||
@@ -36,7 +36,7 @@ const descriptionClassName = `
|
||||
export default function AccountPage() {
|
||||
const { t } = useTranslation()
|
||||
const { systemFeatures } = useGlobalPublicStore()
|
||||
const { data: appList } = useSWR({ url: '/apps', params: { page: 1, limit: 100, name: '' } }, fetchAppList)
|
||||
const { data: appList } = useAppList({ page: 1, limit: 100, name: '' })
|
||||
const apps = appList?.data || []
|
||||
const { mutateUserProfile, userProfile } = useAppContext()
|
||||
const { isEducationAccount } = useProviderContext()
|
||||
|
||||
@@ -52,7 +52,6 @@ export type IGetAutomaticResProps = {
|
||||
editorId?: string
|
||||
currentPrompt?: string
|
||||
isBasicMode?: boolean
|
||||
hideTryIt?: boolean
|
||||
}
|
||||
|
||||
const TryLabel: FC<{
|
||||
@@ -81,7 +80,6 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
currentPrompt,
|
||||
isBasicMode,
|
||||
onFinished,
|
||||
hideTryIt,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const localModel = localStorage.getItem('auto-gen-model')
|
||||
@@ -307,7 +305,7 @@ const GetAutomaticRes: FC<IGetAutomaticResProps> = ({
|
||||
hideDebugWithMultipleModel
|
||||
/>
|
||||
</div>
|
||||
{isBasicMode && !hideTryIt && (
|
||||
{isBasicMode && (
|
||||
<div className='mt-4'>
|
||||
<div className='flex items-center'>
|
||||
<div className='mr-3 shrink-0 text-xs font-semibold uppercase leading-[18px] text-text-tertiary'>{t('appDebug.generate.tryIt')}</div>
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { FC } from 'react'
|
||||
import React from 'react'
|
||||
import ReactECharts from 'echarts-for-react'
|
||||
import type { EChartsOption } from 'echarts'
|
||||
import useSWR from 'swr'
|
||||
import type { Dayjs } from 'dayjs'
|
||||
import dayjs from 'dayjs'
|
||||
import { get } from 'lodash-es'
|
||||
@@ -13,7 +12,20 @@ import { formatNumber } from '@/utils/format'
|
||||
import Basic from '@/app/components/app-sidebar/basic'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import type { AppDailyConversationsResponse, AppDailyEndUsersResponse, AppDailyMessagesResponse, AppTokenCostsResponse } from '@/models/app'
|
||||
import { getAppDailyConversations, getAppDailyEndUsers, getAppDailyMessages, getAppStatistics, getAppTokenCosts, getWorkflowDailyConversations } from '@/service/apps'
|
||||
import {
|
||||
useAppAverageResponseTime,
|
||||
useAppAverageSessionInteractions,
|
||||
useAppDailyConversations,
|
||||
useAppDailyEndUsers,
|
||||
useAppDailyMessages,
|
||||
useAppSatisfactionRate,
|
||||
useAppTokenCosts,
|
||||
useAppTokensPerSecond,
|
||||
useWorkflowAverageInteractions,
|
||||
useWorkflowDailyConversations,
|
||||
useWorkflowDailyTerminals,
|
||||
useWorkflowTokenCosts,
|
||||
} from '@/service/use-apps'
|
||||
const valueFormatter = (v: string | number) => v
|
||||
|
||||
const COLOR_TYPE_MAP = {
|
||||
@@ -272,8 +284,8 @@ const getDefaultChartData = ({ start, end, key = 'count' }: { start: string; end
|
||||
|
||||
export const MessagesChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/daily-messages`, params: period.query }, getAppDailyMessages)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useAppDailyMessages(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -286,8 +298,8 @@ export const MessagesChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
|
||||
export const ConversationsChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/daily-conversations`, params: period.query }, getAppDailyConversations)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useAppDailyConversations(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -301,8 +313,8 @@ export const ConversationsChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
export const EndUsersChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/daily-end-users`, id, params: period.query }, getAppDailyEndUsers)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useAppDailyEndUsers(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -315,8 +327,8 @@ export const EndUsersChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
|
||||
export const AvgSessionInteractions: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/average-session-interactions`, params: period.query }, getAppStatistics)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useAppAverageSessionInteractions(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -331,8 +343,8 @@ export const AvgSessionInteractions: FC<IBizChartProps> = ({ id, period }) => {
|
||||
|
||||
export const AvgResponseTime: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/average-response-time`, params: period.query }, getAppStatistics)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useAppAverageResponseTime(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -348,8 +360,8 @@ export const AvgResponseTime: FC<IBizChartProps> = ({ id, period }) => {
|
||||
|
||||
export const TokenPerSecond: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/tokens-per-second`, params: period.query }, getAppStatistics)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useAppTokensPerSecond(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -366,8 +378,8 @@ export const TokenPerSecond: FC<IBizChartProps> = ({ id, period }) => {
|
||||
|
||||
export const UserSatisfactionRate: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/user-satisfaction-rate`, params: period.query }, getAppStatistics)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useAppSatisfactionRate(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -384,8 +396,8 @@ export const UserSatisfactionRate: FC<IBizChartProps> = ({ id, period }) => {
|
||||
export const CostChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/statistics/token-costs`, params: period.query }, getAppTokenCosts)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useAppTokenCosts(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -398,8 +410,8 @@ export const CostChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
|
||||
export const WorkflowMessagesChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/workflow/statistics/daily-conversations`, params: period.query }, getWorkflowDailyConversations)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useWorkflowDailyConversations(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -414,8 +426,8 @@ export const WorkflowMessagesChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
export const WorkflowDailyTerminalsChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/workflow/statistics/daily-terminals`, id, params: period.query }, getAppDailyEndUsers)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useWorkflowDailyTerminals(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -429,8 +441,8 @@ export const WorkflowDailyTerminalsChart: FC<IBizChartProps> = ({ id, period })
|
||||
export const WorkflowCostChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/workflow/statistics/token-costs`, params: period.query }, getAppTokenCosts)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useWorkflowTokenCosts(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
@@ -443,8 +455,8 @@ export const WorkflowCostChart: FC<IBizChartProps> = ({ id, period }) => {
|
||||
|
||||
export const AvgUserInteractions: FC<IBizChartProps> = ({ id, period }) => {
|
||||
const { t } = useTranslation()
|
||||
const { data: response } = useSWR({ url: `/apps/${id}/workflow/statistics/average-app-interactions`, params: period.query }, getAppStatistics)
|
||||
if (!response)
|
||||
const { data: response, isLoading } = useWorkflowAverageInteractions(id, period.query)
|
||||
if (isLoading || !response)
|
||||
return <Loading />
|
||||
const noDataFlag = !response.data || response.data.length === 0
|
||||
return <Chart
|
||||
|
||||
@@ -23,7 +23,7 @@ const Empty = () => {
|
||||
return (
|
||||
<>
|
||||
<DefaultCards />
|
||||
<div className='absolute inset-0 z-20 flex items-center justify-center bg-gradient-to-t from-background-body to-transparent pointer-events-none'>
|
||||
<div className='pointer-events-none absolute inset-0 z-20 flex items-center justify-center bg-gradient-to-t from-background-body to-transparent'>
|
||||
<span className='system-md-medium text-text-tertiary'>
|
||||
{t('app.newApp.noAppsFound')}
|
||||
</span>
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import {
|
||||
useRouter,
|
||||
} from 'next/navigation'
|
||||
import useSWRInfinite from 'swr/infinite'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import {
|
||||
@@ -19,8 +18,6 @@ import AppCard from './app-card'
|
||||
import NewAppCard from './new-app-card'
|
||||
import useAppsQueryState from './hooks/use-apps-query-state'
|
||||
import { useDSLDragDrop } from './hooks/use-dsl-drag-drop'
|
||||
import type { AppListResponse } from '@/models/app'
|
||||
import { fetchAppList } from '@/service/apps'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
|
||||
import { CheckModal } from '@/hooks/use-pay'
|
||||
@@ -35,6 +32,7 @@ import Empty from './empty'
|
||||
import Footer from './footer'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import { useInfiniteAppList } from '@/service/use-apps'
|
||||
|
||||
const TagManagementModal = dynamic(() => import('@/app/components/base/tag-management'), {
|
||||
ssr: false,
|
||||
@@ -43,30 +41,6 @@ const CreateFromDSLModal = dynamic(() => import('@/app/components/app/create-fro
|
||||
ssr: false,
|
||||
})
|
||||
|
||||
const getKey = (
|
||||
pageIndex: number,
|
||||
previousPageData: AppListResponse,
|
||||
activeTab: string,
|
||||
isCreatedByMe: boolean,
|
||||
tags: string[],
|
||||
keywords: string,
|
||||
) => {
|
||||
if (!pageIndex || previousPageData.has_more) {
|
||||
const params: any = { url: 'apps', params: { page: pageIndex + 1, limit: 30, name: keywords, is_created_by_me: isCreatedByMe } }
|
||||
|
||||
if (activeTab !== 'all')
|
||||
params.params.mode = activeTab
|
||||
else
|
||||
delete params.params.mode
|
||||
|
||||
if (tags.length)
|
||||
params.params.tag_ids = tags
|
||||
|
||||
return params
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const List = () => {
|
||||
const { t } = useTranslation()
|
||||
const { systemFeatures } = useGlobalPublicStore()
|
||||
@@ -102,16 +76,24 @@ const List = () => {
|
||||
enabled: isCurrentWorkspaceEditor,
|
||||
})
|
||||
|
||||
const { data, isLoading, error, setSize, mutate } = useSWRInfinite(
|
||||
(pageIndex: number, previousPageData: AppListResponse) => getKey(pageIndex, previousPageData, activeTab, isCreatedByMe, tagIDs, searchKeywords),
|
||||
fetchAppList,
|
||||
{
|
||||
revalidateFirstPage: true,
|
||||
shouldRetryOnError: false,
|
||||
dedupingInterval: 500,
|
||||
errorRetryCount: 3,
|
||||
},
|
||||
)
|
||||
const appListQueryParams = {
|
||||
page: 1,
|
||||
limit: 30,
|
||||
name: searchKeywords,
|
||||
tag_ids: tagIDs,
|
||||
is_created_by_me: isCreatedByMe,
|
||||
...(activeTab !== 'all' ? { mode: activeTab as AppModeEnum } : {}),
|
||||
}
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
isFetchingNextPage,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
error,
|
||||
refetch,
|
||||
} = useInfiniteAppList(appListQueryParams, { enabled: !isCurrentWorkspaceDatasetOperator })
|
||||
|
||||
const anchorRef = useRef<HTMLDivElement>(null)
|
||||
const options = [
|
||||
@@ -126,9 +108,9 @@ const List = () => {
|
||||
useEffect(() => {
|
||||
if (localStorage.getItem(NEED_REFRESH_APP_LIST_KEY) === '1') {
|
||||
localStorage.removeItem(NEED_REFRESH_APP_LIST_KEY)
|
||||
mutate()
|
||||
refetch()
|
||||
}
|
||||
}, [mutate, t])
|
||||
}, [refetch])
|
||||
|
||||
useEffect(() => {
|
||||
if (isCurrentWorkspaceDatasetOperator)
|
||||
@@ -136,7 +118,9 @@ const List = () => {
|
||||
}, [router, isCurrentWorkspaceDatasetOperator])
|
||||
|
||||
useEffect(() => {
|
||||
const hasMore = data?.at(-1)?.has_more ?? true
|
||||
if (isCurrentWorkspaceDatasetOperator)
|
||||
return
|
||||
const hasMore = hasNextPage ?? true
|
||||
let observer: IntersectionObserver | undefined
|
||||
|
||||
if (error) {
|
||||
@@ -151,8 +135,8 @@ const List = () => {
|
||||
const dynamicMargin = Math.max(100, Math.min(containerHeight * 0.2, 200)) // Clamps to 100-200px range, using 20% of container height as the base value
|
||||
|
||||
observer = new IntersectionObserver((entries) => {
|
||||
if (entries[0].isIntersecting && !isLoading && !error && hasMore)
|
||||
setSize((size: number) => size + 1)
|
||||
if (entries[0].isIntersecting && !isLoading && !isFetchingNextPage && !error && hasMore)
|
||||
fetchNextPage()
|
||||
}, {
|
||||
root: containerRef.current,
|
||||
rootMargin: `${dynamicMargin}px`,
|
||||
@@ -161,7 +145,7 @@ const List = () => {
|
||||
observer.observe(anchorRef.current)
|
||||
}
|
||||
return () => observer?.disconnect()
|
||||
}, [isLoading, setSize, data, error])
|
||||
}, [isLoading, isFetchingNextPage, fetchNextPage, error, hasNextPage, isCurrentWorkspaceDatasetOperator])
|
||||
|
||||
const { run: handleSearch } = useDebounceFn(() => {
|
||||
setSearchKeywords(keywords)
|
||||
@@ -185,6 +169,9 @@ const List = () => {
|
||||
setQuery(prev => ({ ...prev, isCreatedByMe: newValue }))
|
||||
}, [isCreatedByMe, setQuery])
|
||||
|
||||
const pages = data?.pages ?? []
|
||||
const hasAnyApp = (pages[0]?.total ?? 0) > 0
|
||||
|
||||
return (
|
||||
<>
|
||||
<div ref={containerRef} className='relative flex h-0 shrink-0 grow flex-col overflow-y-auto bg-background-body'>
|
||||
@@ -217,17 +204,17 @@ const List = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{(data && data[0].total > 0)
|
||||
{hasAnyApp
|
||||
? <div className='relative grid grow grid-cols-1 content-start gap-4 px-12 pt-2 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6'>
|
||||
{isCurrentWorkspaceEditor
|
||||
&& <NewAppCard ref={newAppCardRef} onSuccess={mutate} selectedAppType={activeTab} />}
|
||||
{data.map(({ data: apps }) => apps.map(app => (
|
||||
<AppCard key={app.id} app={app} onRefresh={mutate} />
|
||||
&& <NewAppCard ref={newAppCardRef} onSuccess={refetch} selectedAppType={activeTab} />}
|
||||
{pages.map(({ data: apps }) => apps.map(app => (
|
||||
<AppCard key={app.id} app={app} onRefresh={refetch} />
|
||||
)))}
|
||||
</div>
|
||||
: <div className='relative grid grow grid-cols-1 content-start gap-4 overflow-hidden px-12 pt-2 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6'>
|
||||
{isCurrentWorkspaceEditor
|
||||
&& <NewAppCard ref={newAppCardRef} className='z-10' onSuccess={mutate} selectedAppType={activeTab} />}
|
||||
&& <NewAppCard ref={newAppCardRef} className='z-10' onSuccess={refetch} selectedAppType={activeTab} />}
|
||||
<Empty />
|
||||
</div>}
|
||||
|
||||
@@ -261,7 +248,7 @@ const List = () => {
|
||||
onSuccess={() => {
|
||||
setShowCreateFromDSLModal(false)
|
||||
setDroppedDSLFile(undefined)
|
||||
mutate()
|
||||
refetch()
|
||||
}}
|
||||
droppedFile={droppedDSLFile}
|
||||
/>
|
||||
|
||||
@@ -7,7 +7,6 @@ import type {
|
||||
ChatConfig,
|
||||
ChatItemInTree,
|
||||
Feedback,
|
||||
Memory,
|
||||
} from '../types'
|
||||
import type { ThemeBuilder } from '../embedded-chatbot/theme/theme-context'
|
||||
import type {
|
||||
@@ -61,14 +60,6 @@ export type ChatWithHistoryContextValue = {
|
||||
name?: string
|
||||
avatar_url?: string
|
||||
}
|
||||
showChatMemory?: boolean
|
||||
setShowChatMemory: (state: boolean) => void
|
||||
memoryList: Memory[]
|
||||
clearAllMemory: () => void
|
||||
updateMemory: (memory: Memory, content: string) => void
|
||||
resetDefault: (memory: Memory) => void
|
||||
clearAllUpdateVersion: (memory: Memory) => void
|
||||
switchMemoryVersion: (memory: Memory, version: string) => void
|
||||
}
|
||||
|
||||
export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>({
|
||||
@@ -104,13 +95,5 @@ export const ChatWithHistoryContext = createContext<ChatWithHistoryContextValue>
|
||||
setCurrentConversationInputs: noop,
|
||||
allInputsHidden: false,
|
||||
initUserVariables: {},
|
||||
showChatMemory: false,
|
||||
setShowChatMemory: noop,
|
||||
memoryList: [],
|
||||
clearAllMemory: noop,
|
||||
updateMemory: noop,
|
||||
resetDefault: noop,
|
||||
clearAllUpdateVersion: noop,
|
||||
switchMemoryVersion: noop,
|
||||
})
|
||||
export const useChatWithHistoryContext = () => useContext(ChatWithHistoryContext)
|
||||
|
||||
@@ -28,8 +28,6 @@ const HeaderInMobile = () => {
|
||||
handleRenameConversation,
|
||||
conversationRenaming,
|
||||
inputsForms,
|
||||
showChatMemory,
|
||||
setShowChatMemory,
|
||||
} = useChatWithHistoryContext()
|
||||
const { t } = useTranslation()
|
||||
const isPin = pinnedConversationList.some(item => item.id === currentConversationId)
|
||||
@@ -62,9 +60,6 @@ const HeaderInMobile = () => {
|
||||
if (showRename)
|
||||
handleRenameConversation(showRename.id, newName, { onSuccess: handleCancelRename })
|
||||
}, [showRename, handleRenameConversation, handleCancelRename])
|
||||
const handleChatMemoryToggle = useCallback(() => {
|
||||
setShowChatMemory(!showChatMemory)
|
||||
}, [setShowChatMemory, showChatMemory])
|
||||
const [showSidebar, setShowSidebar] = useState(false)
|
||||
const [showChatSettings, setShowChatSettings] = useState(false)
|
||||
|
||||
@@ -103,7 +98,6 @@ const HeaderInMobile = () => {
|
||||
)}
|
||||
</div>
|
||||
<MobileOperationDropdown
|
||||
handleChatMemoryToggle={handleChatMemoryToggle}
|
||||
handleResetChat={handleNewConversation}
|
||||
handleViewChatSettings={() => setShowChatSettings(true)}
|
||||
hideViewChatSettings={inputsForms.length < 1}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiEditBoxLine,
|
||||
RiLayoutRight2Line,
|
||||
RiResetLeftLine,
|
||||
} from '@remixicon/react'
|
||||
import { Memory } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
useChatWithHistoryContext,
|
||||
} from '../context'
|
||||
@@ -35,8 +34,6 @@ const Header = () => {
|
||||
sidebarCollapseState,
|
||||
handleSidebarCollapse,
|
||||
isResponding,
|
||||
showChatMemory,
|
||||
setShowChatMemory,
|
||||
} = useChatWithHistoryContext()
|
||||
const { t } = useTranslation()
|
||||
const isSidebarCollapsed = sidebarCollapseState
|
||||
@@ -73,10 +70,6 @@ const Header = () => {
|
||||
handleRenameConversation(showRename.id, newName, { onSuccess: handleCancelRename })
|
||||
}, [showRename, handleRenameConversation, handleCancelRename])
|
||||
|
||||
const handleChatMemoryToggle = useCallback(() => {
|
||||
setShowChatMemory(!showChatMemory)
|
||||
}, [setShowChatMemory, showChatMemory])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='flex h-14 shrink-0 items-center justify-between p-3'>
|
||||
@@ -144,15 +137,6 @@ const Header = () => {
|
||||
{currentConversationId && inputsForms.length > 0 && (
|
||||
<ViewFormDropdown />
|
||||
)}
|
||||
{currentConversationId && (
|
||||
<Tooltip
|
||||
popupContent={t('share.chat.memory.actionButton')}
|
||||
>
|
||||
<ActionButton size='l' state={showChatMemory ? ActionButtonState.Active : ActionButtonState.Default} onClick={handleChatMemoryToggle}>
|
||||
<Memory className='h-[18px] w-[18px]' />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!!showConfirm && (
|
||||
|
||||
@@ -9,14 +9,12 @@ import ActionButton, { ActionButtonState } from '@/app/components/base/action-bu
|
||||
type Props = {
|
||||
handleResetChat: () => void
|
||||
handleViewChatSettings: () => void
|
||||
handleChatMemoryToggle?: () => void
|
||||
hideViewChatSettings?: boolean
|
||||
}
|
||||
|
||||
const MobileOperationDropdown = ({
|
||||
handleResetChat,
|
||||
handleViewChatSettings,
|
||||
handleChatMemoryToggle,
|
||||
hideViewChatSettings = false,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
@@ -46,9 +44,6 @@ const MobileOperationDropdown = ({
|
||||
<div className='system-md-regular flex cursor-pointer items-center space-x-1 rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover' onClick={handleResetChat}>
|
||||
<span className='grow'>{t('share.chat.resetChat')}</span>
|
||||
</div>
|
||||
<div className='system-md-regular flex cursor-pointer items-center space-x-1 rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover' onClick={handleChatMemoryToggle}>
|
||||
<span className='grow'>{t('share.chat.memory.actionButton')}</span>
|
||||
</div>
|
||||
{!hideViewChatSettings && (
|
||||
<div className='system-md-regular flex cursor-pointer items-center space-x-1 rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover' onClick={handleViewChatSettings}>
|
||||
<span className='grow'>{t('share.chat.viewChatSettings')}</span>
|
||||
|
||||
@@ -21,11 +21,8 @@ import { addFileInfos, sortAgentSorts } from '../../../tools/utils'
|
||||
import { getProcessedFilesFromResponse } from '@/app/components/base/file-uploader/utils'
|
||||
import {
|
||||
delConversation,
|
||||
deleteMemory,
|
||||
editMemory,
|
||||
fetchChatList,
|
||||
fetchConversations,
|
||||
fetchMemories,
|
||||
generationConversationName,
|
||||
pinConversation,
|
||||
renameConversation,
|
||||
@@ -44,9 +41,6 @@ import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { noop } from 'lodash-es'
|
||||
import { useWebAppStore } from '@/context/web-app-context'
|
||||
import type { Memory } from '@/app/components/base/chat/types'
|
||||
|
||||
import { mockMemoryList } from '@/app/components/base/chat/chat-with-history/memory/mock'
|
||||
|
||||
function getFormattedChatList(messages: any[]) {
|
||||
const newChatList: ChatItem[] = []
|
||||
@@ -532,61 +526,6 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
||||
notify({ type: 'success', message: t('common.api.success') })
|
||||
}, [isInstalledApp, appId, t, notify])
|
||||
|
||||
const [showChatMemory, setShowChatMemory] = useState(false)
|
||||
const [memoryList, setMemoryList] = useState<Memory[]>(mockMemoryList)
|
||||
|
||||
const getMemoryList = useCallback(async (currentConversationId: string) => {
|
||||
const memories = await fetchMemories(currentConversationId, '', '', isInstalledApp, appId)
|
||||
setMemoryList(memories)
|
||||
}, [isInstalledApp, appId])
|
||||
|
||||
const clearAllMemory = useCallback(async () => {
|
||||
await deleteMemory('', isInstalledApp, appId)
|
||||
notify({ type: 'success', message: t('common.api.success') })
|
||||
getMemoryList(currentConversationId)
|
||||
}, [currentConversationId, getMemoryList])
|
||||
|
||||
const resetDefault = useCallback(async (memory: Memory) => {
|
||||
try {
|
||||
await editMemory(memory.spec.id, memory.spec.template, isInstalledApp, appId)
|
||||
getMemoryList(currentConversationId)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to reset memory:', error)
|
||||
}
|
||||
}, [currentConversationId, getMemoryList, isInstalledApp, appId])
|
||||
|
||||
const clearAllUpdateVersion = useCallback(async (memory: Memory) => {
|
||||
await deleteMemory(memory.spec.id, isInstalledApp, appId)
|
||||
notify({ type: 'success', message: t('common.api.success') })
|
||||
getMemoryList(currentConversationId)
|
||||
}, [currentConversationId, getMemoryList])
|
||||
|
||||
const switchMemoryVersion = useCallback(async (memory: Memory, version: string) => {
|
||||
const memories = await fetchMemories(currentConversationId, memory.spec.id, version, isInstalledApp, appId)
|
||||
const newMemory = memories[0]
|
||||
const newList = produce(memoryList, (draft) => {
|
||||
const index = draft.findIndex(item => item.spec.id === memory.spec.id)
|
||||
if (index !== -1)
|
||||
draft[index] = newMemory
|
||||
})
|
||||
setMemoryList(newList)
|
||||
}, [memoryList, currentConversationId, isInstalledApp, appId])
|
||||
|
||||
const updateMemory = useCallback(async (memory: Memory, content: string) => {
|
||||
try {
|
||||
await editMemory(memory.spec.id, content, isInstalledApp, appId)
|
||||
getMemoryList(currentConversationId)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to reset memory:', error)
|
||||
}
|
||||
}, [getMemoryList, currentConversationId, isInstalledApp, appId])
|
||||
|
||||
useEffect(() => {
|
||||
getMemoryList(currentConversationId)
|
||||
}, [currentConversationId, getMemoryList])
|
||||
|
||||
return {
|
||||
isInstalledApp,
|
||||
appId,
|
||||
@@ -633,13 +572,5 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
|
||||
setCurrentConversationInputs,
|
||||
allInputsHidden,
|
||||
initUserVariables,
|
||||
showChatMemory,
|
||||
setShowChatMemory,
|
||||
memoryList,
|
||||
clearAllMemory,
|
||||
updateMemory,
|
||||
resetDefault,
|
||||
clearAllUpdateVersion,
|
||||
switchMemoryVersion,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import Sidebar from './sidebar'
|
||||
import Header from './header'
|
||||
import HeaderInMobile from './header-in-mobile'
|
||||
import ChatWrapper from './chat-wrapper'
|
||||
import MemoryPanel from './memory'
|
||||
import type { InstalledApp } from '@/models/explore'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
|
||||
@@ -34,14 +33,6 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({
|
||||
isMobile,
|
||||
themeBuilder,
|
||||
sidebarCollapseState,
|
||||
showChatMemory,
|
||||
setShowChatMemory,
|
||||
memoryList,
|
||||
clearAllMemory,
|
||||
updateMemory,
|
||||
resetDefault,
|
||||
clearAllUpdateVersion,
|
||||
switchMemoryVersion,
|
||||
} = useChatWithHistoryContext()
|
||||
const isSidebarCollapsed = sidebarCollapseState
|
||||
const customConfig = appData?.custom_config
|
||||
@@ -77,7 +68,7 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({
|
||||
{isMobile && (
|
||||
<HeaderInMobile />
|
||||
)}
|
||||
<div className={cn('relative flex grow p-2', isMobile && 'h-[calc(100%_-_56px)] p-0')}>
|
||||
<div className={cn('relative grow p-2', isMobile && 'h-[calc(100%_-_56px)] p-0')}>
|
||||
{isSidebarCollapsed && (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -90,11 +81,7 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({
|
||||
<Sidebar isPanel panelVisible={showSidePanel} />
|
||||
</div>
|
||||
)}
|
||||
<div className={cn(
|
||||
'flex h-full grow flex-col overflow-hidden border-[0,5px] border-components-panel-border-subtle bg-chatbot-bg',
|
||||
isMobile ? 'rounded-t-2xl' : 'rounded-2xl',
|
||||
showChatMemory && !isMobile && 'mr-1',
|
||||
)}>
|
||||
<div className={cn('flex h-full flex-col overflow-hidden border-[0,5px] border-components-panel-border-subtle bg-chatbot-bg', isMobile ? 'rounded-t-2xl' : 'rounded-2xl')}>
|
||||
{!isMobile && <Header />}
|
||||
{appChatListDataLoading && (
|
||||
<Loading type='app' />
|
||||
@@ -103,38 +90,6 @@ const ChatWithHistory: FC<ChatWithHistoryProps> = ({
|
||||
<ChatWrapper key={chatShouldReloadKey} />
|
||||
)}
|
||||
</div>
|
||||
{!isMobile && (
|
||||
<MemoryPanel
|
||||
isMobile={isMobile}
|
||||
showChatMemory={showChatMemory}
|
||||
setShowChatMemory={setShowChatMemory}
|
||||
memoryList={memoryList}
|
||||
clearAllMemory={clearAllMemory}
|
||||
updateMemory={updateMemory}
|
||||
resetDefault={resetDefault}
|
||||
clearAllUpdateVersion={clearAllUpdateVersion}
|
||||
switchMemoryVersion={switchMemoryVersion}
|
||||
/>
|
||||
)}
|
||||
{isMobile && showChatMemory && (
|
||||
<div className='fixed inset-0 z-50 flex flex-row-reverse bg-background-overlay p-1 backdrop-blur-sm'
|
||||
onClick={() => setShowChatMemory(false)}
|
||||
>
|
||||
<div className='flex h-full w-[360px] rounded-xl shadow-lg' onClick={e => e.stopPropagation()}>
|
||||
<MemoryPanel
|
||||
isMobile={isMobile}
|
||||
showChatMemory={showChatMemory}
|
||||
setShowChatMemory={setShowChatMemory}
|
||||
memoryList={memoryList}
|
||||
clearAllMemory={clearAllMemory}
|
||||
updateMemory={updateMemory}
|
||||
resetDefault={resetDefault}
|
||||
clearAllUpdateVersion={clearAllUpdateVersion}
|
||||
switchMemoryVersion={switchMemoryVersion}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -190,14 +145,6 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
|
||||
setCurrentConversationInputs,
|
||||
allInputsHidden,
|
||||
initUserVariables,
|
||||
showChatMemory,
|
||||
setShowChatMemory,
|
||||
memoryList,
|
||||
clearAllMemory,
|
||||
updateMemory,
|
||||
resetDefault,
|
||||
clearAllUpdateVersion,
|
||||
switchMemoryVersion,
|
||||
} = useChatWithHistory(installedAppInfo)
|
||||
|
||||
return (
|
||||
@@ -241,14 +188,6 @@ const ChatWithHistoryWrap: FC<ChatWithHistoryWrapProps> = ({
|
||||
setCurrentConversationInputs,
|
||||
allInputsHidden,
|
||||
initUserVariables,
|
||||
showChatMemory,
|
||||
setShowChatMemory,
|
||||
memoryList,
|
||||
clearAllMemory,
|
||||
updateMemory,
|
||||
resetDefault,
|
||||
clearAllUpdateVersion,
|
||||
switchMemoryVersion,
|
||||
}}>
|
||||
<ChatWithHistory className={className} />
|
||||
</ChatWithHistoryContext.Provider>
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
'use client'
|
||||
import React, { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { Memory } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import type { Memory as MemoryItem } from '@/app/components/base/chat/types'
|
||||
import { noop } from 'lodash-es'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
memory: MemoryItem
|
||||
show: boolean
|
||||
onConfirm: (info: MemoryItem, content: string) => Promise<void>
|
||||
onHide: () => void
|
||||
isMobile?: boolean
|
||||
}
|
||||
|
||||
const MemoryEditModal = ({
|
||||
memory,
|
||||
show = false,
|
||||
onConfirm,
|
||||
onHide,
|
||||
isMobile,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation()
|
||||
const [content, setContent] = React.useState(memory.value)
|
||||
|
||||
const versionTag = useMemo(() => {
|
||||
const res = `${t('share.chat.memory.updateVersion.update')} ${memory.version}`
|
||||
if (memory.edited_by_user)
|
||||
return `${res} · ${t('share.chat.memory.updateVersion.edited')}`
|
||||
return res
|
||||
}, [memory.version, t])
|
||||
|
||||
const reset = () => {
|
||||
setContent(memory.value)
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
if (!content.trim()) {
|
||||
Toast.notify({ type: 'error', message: 'content is required' })
|
||||
return
|
||||
}
|
||||
onConfirm(memory, content)
|
||||
onHide()
|
||||
}
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<div className='fixed inset-0 z-50 flex flex-col bg-background-overlay pt-3 backdrop-blur-sm'
|
||||
onClick={onHide}
|
||||
>
|
||||
<div className='relative flex w-full grow flex-col rounded-t-xl bg-components-panel-bg shadow-xl' onClick={e => e.stopPropagation()}>
|
||||
<div className='absolute right-4 top-4 cursor-pointer p-2'>
|
||||
<ActionButton onClick={onHide}>
|
||||
<RiCloseLine className='h-5 w-5' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
<div className='p-4 pb-3'>
|
||||
<div className='title-2xl-semi-bold mb-2 text-text-primary'>{t('share.chat.memory.editTitle')}</div>
|
||||
<div className='flex items-center gap-1 pb-1 pt-2'>
|
||||
<Memory className='h-4 w-4 shrink-0 text-util-colors-teal-teal-700' />
|
||||
<div className='system-sm-semibold truncate text-text-primary'>{memory.spec.name}</div>
|
||||
{memory.version > 1 && <Badge text={versionTag} className='!h-4' />}
|
||||
</div>
|
||||
</div>
|
||||
<div className='grow px-4'>
|
||||
<Textarea
|
||||
className='h-full'
|
||||
value={content}
|
||||
onChange={e => setContent(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-row-reverse items-center p-4'>
|
||||
<Button className='ml-2' variant='primary' onClick={submit}>{t('share.chat.memory.operations.save')}</Button>
|
||||
<Button className='ml-3' onClick={onHide}>{t('share.chat.memory.operations.cancel')}</Button>
|
||||
<Divider type='vertical' className='!mx-0 !h-4' />
|
||||
<Button className='mr-3' onClick={reset}>{t('share.chat.memory.operations.reset')}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={show}
|
||||
onClose={noop}
|
||||
className={cn('relative !max-w-[800px]', 'p-0')}
|
||||
>
|
||||
<div className='absolute right-5 top-5 cursor-pointer p-2'>
|
||||
<ActionButton onClick={onHide}>
|
||||
<RiCloseLine className='h-5 w-5' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
<div className='p-6 pb-3'>
|
||||
<div className='title-2xl-semi-bold mb-2 text-text-primary'>{t('share.chat.memory.editTitle')}</div>
|
||||
<div className='flex items-center gap-1 pb-1 pt-2'>
|
||||
<Memory className='h-4 w-4 shrink-0 text-util-colors-teal-teal-700' />
|
||||
<div className='system-sm-semibold truncate text-text-primary'>{memory.spec.name}</div>
|
||||
{memory.version > 1 && <Badge text={versionTag} />}
|
||||
</div>
|
||||
</div>
|
||||
<div className='px-6'>
|
||||
<Textarea
|
||||
className='h-[562px]'
|
||||
value={content}
|
||||
onChange={e => setContent(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-row-reverse items-center p-6 pt-5'>
|
||||
<Button className='ml-2' variant='primary' onClick={submit}>{t('share.chat.memory.operations.save')}</Button>
|
||||
<Button className='ml-3' onClick={onHide}>{t('share.chat.memory.operations.cancel')}</Button>
|
||||
<Divider type='vertical' className='!mx-0 !h-4' />
|
||||
<Button className='mr-3' onClick={reset}>{t('share.chat.memory.operations.reset')}</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default MemoryEditModal
|
||||
@@ -1,127 +0,0 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
RiArrowUpSLine,
|
||||
} from '@remixicon/react'
|
||||
import { Memory } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import Operation from './operation'
|
||||
import MemoryEditModal from './edit-modal'
|
||||
import type { Memory as MemoryItem } from '@/app/components/base/chat/types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
isMobile?: boolean
|
||||
memory: MemoryItem
|
||||
updateMemory: (memory: MemoryItem, content: string) => void
|
||||
resetDefault: (memory: MemoryItem) => void
|
||||
clearAllUpdateVersion: (memory: MemoryItem) => void
|
||||
switchMemoryVersion: (memory: MemoryItem, version: string) => void
|
||||
}
|
||||
|
||||
const MemoryCard: React.FC<Props> = ({
|
||||
isMobile,
|
||||
memory,
|
||||
updateMemory,
|
||||
resetDefault,
|
||||
clearAllUpdateVersion,
|
||||
switchMemoryVersion,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [isHovering, setIsHovering] = React.useState(false)
|
||||
const [showEditModal, setShowEditModal] = React.useState(false)
|
||||
|
||||
const versionTag = useMemo(() => {
|
||||
const res = `${t('share.chat.memory.updateVersion.update')} ${memory.version}`
|
||||
if (memory.edited_by_user)
|
||||
return `${res} · ${t('share.chat.memory.updateVersion.edited')}`
|
||||
return res
|
||||
}, [memory.version, t])
|
||||
|
||||
const isLatest = useMemo(() => {
|
||||
if (memory.conversation_metadata)
|
||||
return memory.conversation_metadata.visible_count === memory.spec.preserved_turns
|
||||
|
||||
return true
|
||||
}, [memory])
|
||||
|
||||
const waitMergeCount = useMemo(() => {
|
||||
if (memory.conversation_metadata)
|
||||
return memory.conversation_metadata.visible_count - memory.spec.preserved_turns
|
||||
|
||||
return 0
|
||||
}, [memory])
|
||||
|
||||
const prevVersion = () => {
|
||||
if (memory.version > 1)
|
||||
switchMemoryVersion(memory, (memory.version - 1).toString())
|
||||
}
|
||||
|
||||
const nextVersion = () => {
|
||||
switchMemoryVersion(memory, (memory.version + 1).toString())
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={cn('group mb-1 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs hover:bg-components-panel-on-panel-item-bg-hover hover:shadow-md')}
|
||||
onMouseEnter={() => setIsHovering(true)}
|
||||
onMouseLeave={() => setIsHovering(false)}
|
||||
>
|
||||
<div className='relative flex items-end justify-between pb-1 pl-4 pr-2 pt-2'>
|
||||
<div className='flex items-center gap-1 pb-1 pt-2'>
|
||||
<Memory className='h-4 w-4 shrink-0 text-util-colors-teal-teal-700' />
|
||||
<div className='system-sm-semibold truncate text-text-primary'>{memory.spec.name}</div>
|
||||
{memory.version > 1 && <Badge text={versionTag} className='!h-4' />}
|
||||
</div>
|
||||
{isHovering && (
|
||||
<div className='hover:bg-components-actionbar-bg-hover absolute bottom-0 right-2 flex items-center gap-0.5 rounded-lg border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0.5 shadow-md'>
|
||||
<ActionButton onClick={prevVersion}><RiArrowUpSLine className='h-4 w-4' /></ActionButton>
|
||||
<ActionButton onClick={nextVersion}><RiArrowDownSLine className='h-4 w-4' /></ActionButton>
|
||||
<Operation
|
||||
memory={memory}
|
||||
onEdit={() => {
|
||||
setShowEditModal(true)
|
||||
setIsHovering(false)
|
||||
}}
|
||||
resetDefault={resetDefault}
|
||||
clearAllUpdateVersion={clearAllUpdateVersion}
|
||||
switchMemoryVersion={switchMemoryVersion}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='system-xs-regular line-clamp-[12] px-4 pb-2 pt-1 text-text-tertiary'>{memory.value}</div>
|
||||
{isLatest && (
|
||||
<div className='flex items-center gap-1 rounded-b-xl border-t-[0.5px] border-divider-subtle bg-background-default-subtle px-4 py-3 group-hover:bg-components-panel-on-panel-item-bg-hover'>
|
||||
<div className='system-xs-regular text-text-tertiary'>{t('share.chat.memory.latestVersion')}</div>
|
||||
<Indicator color='green' />
|
||||
</div>
|
||||
)}
|
||||
{!isLatest && (
|
||||
<div className='flex items-center gap-1 rounded-b-xl border-t-[0.5px] border-divider-subtle bg-background-default-subtle px-4 py-3 group-hover:bg-components-panel-on-panel-item-bg-hover'>
|
||||
<div className='system-xs-regular text-text-tertiary'>{t('share.chat.memory.notLatestVersion', { num: waitMergeCount })}</div>
|
||||
<Indicator color='orange' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{showEditModal && (
|
||||
<MemoryEditModal
|
||||
isMobile={isMobile}
|
||||
show={showEditModal}
|
||||
memory={memory}
|
||||
onConfirm={async (info, content) => {
|
||||
await updateMemory(info, content)
|
||||
setShowEditModal(false)
|
||||
}}
|
||||
onHide={() => setShowEditModal(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default MemoryCard
|
||||
@@ -1,88 +0,0 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiCheckLine, RiMoreFill } from '@remixicon/react'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import type { Memory } from '@/app/components/base/chat/types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
memory: Memory
|
||||
onEdit: () => void
|
||||
resetDefault: (memory: Memory) => void
|
||||
clearAllUpdateVersion: (memory: Memory) => void
|
||||
switchMemoryVersion: (memory: Memory, version: string) => void
|
||||
}
|
||||
|
||||
const OperationDropdown: FC<Props> = ({
|
||||
memory,
|
||||
onEdit,
|
||||
resetDefault,
|
||||
clearAllUpdateVersion,
|
||||
switchMemoryVersion,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, doSetOpen] = useState(false)
|
||||
const openRef = useRef(open)
|
||||
const setOpen = useCallback((v: boolean) => {
|
||||
doSetOpen(v)
|
||||
openRef.current = v
|
||||
}, [doSetOpen])
|
||||
|
||||
const handleTrigger = useCallback(() => {
|
||||
setOpen(!openRef.current)
|
||||
}, [setOpen])
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-end'
|
||||
offset={{
|
||||
mainAxis: 4,
|
||||
crossAxis: 4,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={handleTrigger}>
|
||||
<div>
|
||||
<ActionButton className={cn(open && 'bg-state-base-hover')}>
|
||||
<RiMoreFill className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-50'>
|
||||
<div className='w-[220px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg backdrop-blur-sm'>
|
||||
<div className='p-1'>
|
||||
<div className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover' onClick={onEdit}>{t('share.chat.memory.operations.edit')}</div>
|
||||
<div className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover' onClick={() => resetDefault(memory)}>{t('share.chat.memory.operations.reset')}</div>
|
||||
<div className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-destructive-hover hover:text-text-destructive' onClick={() => clearAllUpdateVersion(memory)}>{t('share.chat.memory.operations.clear')}</div>
|
||||
</div>
|
||||
<Divider className='!my-0 !h-px bg-divider-subtle' />
|
||||
<div className='px-1 py-2'>
|
||||
<div className='system-xs-medium-uppercase px-3 pb-0.5 pt-1 text-text-tertiary'>{t('share.chat.memory.updateVersion.title')}</div>
|
||||
<div className='system-md-regular flex cursor-pointer items-center gap-1 rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>
|
||||
{t('share.chat.memory.operations.edit')}
|
||||
<RiCheckLine className='h-4 w-4 text-text-accent' />
|
||||
</div>
|
||||
<div className='system-md-regular flex cursor-pointer items-center gap-1 rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>
|
||||
{t('share.chat.memory.operations.edit')}
|
||||
<RiCheckLine className='h-4 w-4 text-text-accent' />
|
||||
</div>
|
||||
<div className='system-md-regular flex cursor-pointer items-center gap-1 rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>
|
||||
{t('share.chat.memory.operations.edit')}
|
||||
<RiCheckLine className='h-4 w-4 text-text-accent' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
export default React.memo(OperationDropdown)
|
||||
@@ -1,80 +0,0 @@
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiCloseLine,
|
||||
RiDeleteBinLine,
|
||||
} from '@remixicon/react'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Button from '@/app/components/base/button'
|
||||
import MemoryCard from './card'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
import type { Memory } from '@/app/components/base/chat/types'
|
||||
|
||||
type Props = {
|
||||
isMobile?: boolean
|
||||
showChatMemory?: boolean
|
||||
setShowChatMemory: (show: boolean) => void
|
||||
memoryList: Memory[]
|
||||
clearAllMemory: () => void
|
||||
updateMemory: (memory: Memory, content: string) => void
|
||||
resetDefault: (memory: Memory) => void
|
||||
clearAllUpdateVersion: (memory: Memory) => void
|
||||
switchMemoryVersion: (memory: Memory, version: string) => void
|
||||
}
|
||||
|
||||
const MemoryPanel: React.FC<Props> = ({
|
||||
isMobile,
|
||||
showChatMemory,
|
||||
setShowChatMemory,
|
||||
memoryList,
|
||||
clearAllMemory,
|
||||
updateMemory,
|
||||
resetDefault,
|
||||
clearAllUpdateVersion,
|
||||
switchMemoryVersion,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className={cn(
|
||||
'flex h-full w-[360px] shrink-0 flex-col rounded-2xl border-[0.5px] border-components-panel-border-subtle bg-chatbot-bg transition-all ease-in-out',
|
||||
showChatMemory ? 'w-[360px]' : 'w-0 opacity-0',
|
||||
)}>
|
||||
<div className='flex shrink-0 items-center border-b-[0.5px] border-components-panel-border-subtle pl-4 pr-3.5 pt-2'>
|
||||
<div className='system-md-semibold-uppercase grow py-3 text-text-primary'>{t('share.chat.memory.title')}</div>
|
||||
<ActionButton size='l' onClick={() => setShowChatMemory(false)}>
|
||||
<RiCloseLine className='h-[18px] w-[18px]' />
|
||||
</ActionButton>
|
||||
</div>
|
||||
<div className='h-0 grow overflow-y-auto px-3 pt-2'>
|
||||
{memoryList.map(memory => (
|
||||
<MemoryCard
|
||||
key={memory.spec.id}
|
||||
isMobile={isMobile}
|
||||
memory={memory}
|
||||
updateMemory={updateMemory}
|
||||
resetDefault={resetDefault}
|
||||
clearAllUpdateVersion={clearAllUpdateVersion}
|
||||
switchMemoryVersion={switchMemoryVersion}
|
||||
/>
|
||||
))}
|
||||
{memoryList.length > 0 && (
|
||||
<div className='flex items-center justify-center'>
|
||||
<Button variant='ghost' onClick={clearAllMemory}>
|
||||
<RiDeleteBinLine className='mr-1 h-3.5 w-3.5' />
|
||||
{t('share.chat.memory.clearAll')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{memoryList.length === 0 && (
|
||||
<div className='system-xs-regular flex items-center justify-center py-2 text-text-tertiary'>
|
||||
{t('share.chat.memory.empty')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MemoryPanel
|
||||
@@ -1,96 +0,0 @@
|
||||
import type { Memory as MemoryItem } from '@/app/components/base/chat/types'
|
||||
|
||||
export const mockMemoryList: MemoryItem[] = [
|
||||
{
|
||||
tenant_id: 'user-tenant-id',
|
||||
value: `Learning Goal: [What you\'re studying]
|
||||
Current Level: [Beginner/Intermediate/Advanced]
|
||||
Learning Style: [Visual, hands-on, theoretical, etc.]
|
||||
Progress: [Topics mastered, current focus]
|
||||
Preferred Pace: [Fast/moderate/slow explanations]
|
||||
Background: [Relevant experience or education]
|
||||
Time Constraints: [Available study time]`,
|
||||
app_id: 'user-app-id',
|
||||
conversation_id: '',
|
||||
version: 1,
|
||||
edited_by_user: false,
|
||||
conversation_metadata: {
|
||||
type: 'mutable_visible_window',
|
||||
visible_count: 5,
|
||||
},
|
||||
spec: {
|
||||
id: 'learning_companion',
|
||||
name: 'Learning Companion',
|
||||
description: 'A companion to help with learning goals',
|
||||
template: 'no zuo no die why you try', // default value
|
||||
instruction: 'enjoy yourself',
|
||||
scope: 'app', // app or node
|
||||
term: 'session', // session or persistent
|
||||
strategy: 'on_turns',
|
||||
update_turns: 3,
|
||||
preserved_turns: 5,
|
||||
schedule_mode: 'sync', // sync or async
|
||||
end_user_visible: true,
|
||||
end_user_editable: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
tenant_id: 'user-tenant-id',
|
||||
value: `Research Topic: [Your research topic]
|
||||
Current Progress: [Literature review, experiments, etc.]
|
||||
Challenges: [What you\'re struggling with]
|
||||
Goals: [Short-term and long-term research goals]`,
|
||||
app_id: 'user-app-id',
|
||||
conversation_id: '',
|
||||
version: 1,
|
||||
edited_by_user: false,
|
||||
conversation_metadata: {
|
||||
type: 'mutable_visible_window',
|
||||
visible_count: 5,
|
||||
},
|
||||
spec: {
|
||||
id: 'research_partner',
|
||||
name: 'research_partner',
|
||||
description: 'A companion to help with research goals',
|
||||
template: 'no zuo no die why you try', // default value
|
||||
instruction: 'enjoy yourself',
|
||||
scope: 'app', // app or node
|
||||
term: 'session', // session or persistent
|
||||
strategy: 'on_turns',
|
||||
update_turns: 3,
|
||||
preserved_turns: 3,
|
||||
schedule_mode: 'sync', // sync or async
|
||||
end_user_visible: true,
|
||||
end_user_editable: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
tenant_id: 'user-tenant-id',
|
||||
value: `Code Context: [Brief description of the codebase]
|
||||
Current Issues: [Bugs, technical debt, etc.]
|
||||
Goals: [Features to implement, improvements to make]`,
|
||||
app_id: 'user-app-id',
|
||||
conversation_id: '',
|
||||
version: 3,
|
||||
edited_by_user: true,
|
||||
conversation_metadata: {
|
||||
type: 'mutable_visible_window',
|
||||
visible_count: 5,
|
||||
},
|
||||
spec: {
|
||||
id: 'code_partner',
|
||||
name: 'code_partner',
|
||||
description: 'A companion to help with code-related tasks',
|
||||
template: 'no zuo no die why you try', // default value
|
||||
instruction: 'enjoy yourself',
|
||||
scope: 'app', // app or node
|
||||
term: 'session', // session or persistent
|
||||
strategy: 'on_turns',
|
||||
update_turns: 3,
|
||||
preserved_turns: 5,
|
||||
schedule_mode: 'sync', // sync or async
|
||||
end_user_visible: true,
|
||||
end_user_editable: true,
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -6,7 +6,6 @@ import type {
|
||||
ChatConfig,
|
||||
ChatItem,
|
||||
Feedback,
|
||||
Memory,
|
||||
} from '../types'
|
||||
import type { ThemeBuilder } from './theme/theme-context'
|
||||
import type {
|
||||
@@ -54,14 +53,6 @@ export type EmbeddedChatbotContextValue = {
|
||||
name?: string
|
||||
avatar_url?: string
|
||||
}
|
||||
showChatMemory?: boolean
|
||||
setShowChatMemory: (state: boolean) => void
|
||||
memoryList: Memory[]
|
||||
clearAllMemory: () => void
|
||||
updateMemory: (memory: Memory, content: string) => void
|
||||
resetDefault: (memory: Memory) => void
|
||||
clearAllUpdateVersion: (memory: Memory) => void
|
||||
switchMemoryVersion: (memory: Memory, version: string) => void
|
||||
}
|
||||
|
||||
export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue>({
|
||||
@@ -95,13 +86,5 @@ export const EmbeddedChatbotContext = createContext<EmbeddedChatbotContextValue>
|
||||
setCurrentConversationInputs: noop,
|
||||
allInputsHidden: false,
|
||||
initUserVariables: {},
|
||||
showChatMemory: false,
|
||||
setShowChatMemory: noop,
|
||||
memoryList: [],
|
||||
clearAllMemory: noop,
|
||||
updateMemory: noop,
|
||||
resetDefault: noop,
|
||||
clearAllUpdateVersion: noop,
|
||||
switchMemoryVersion: noop,
|
||||
})
|
||||
export const useEmbeddedChatbotContext = () => useContext(EmbeddedChatbotContext)
|
||||
|
||||
@@ -7,9 +7,8 @@ import { CssTransform } from '../theme/utils'
|
||||
import {
|
||||
useEmbeddedChatbotContext,
|
||||
} from '../context'
|
||||
import { Memory } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import ViewFormDropdown from '@/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown'
|
||||
import DifyLogo from '@/app/components/base/logo/dify-logo'
|
||||
@@ -37,8 +36,6 @@ const Header: FC<IHeaderProps> = ({
|
||||
appData,
|
||||
currentConversationId,
|
||||
inputsForms,
|
||||
showChatMemory,
|
||||
setShowChatMemory,
|
||||
allInputsHidden,
|
||||
} = useEmbeddedChatbotContext()
|
||||
|
||||
@@ -80,10 +77,6 @@ const Header: FC<IHeaderProps> = ({
|
||||
}, parentOrigin)
|
||||
}, [isIframe, parentOrigin, showToggleExpandButton, expanded])
|
||||
|
||||
const handleChatMemoryToggle = useCallback(() => {
|
||||
setShowChatMemory(!showChatMemory)
|
||||
}, [setShowChatMemory, showChatMemory])
|
||||
|
||||
if (!isMobile) {
|
||||
return (
|
||||
<div className='flex h-14 shrink-0 items-center justify-end p-3'>
|
||||
@@ -135,15 +128,6 @@ const Header: FC<IHeaderProps> = ({
|
||||
{currentConversationId && inputsForms.length > 0 && !allInputsHidden && (
|
||||
<ViewFormDropdown />
|
||||
)}
|
||||
{currentConversationId && (
|
||||
<Tooltip
|
||||
popupContent={t('share.chat.memory.actionButton')}
|
||||
>
|
||||
<ActionButton size='l' state={showChatMemory ? ActionButtonState.Active : ActionButtonState.Default} onClick={handleChatMemoryToggle}>
|
||||
<Memory className='h-[18px] w-[18px]' />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -191,15 +175,6 @@ const Header: FC<IHeaderProps> = ({
|
||||
{currentConversationId && inputsForms.length > 0 && !allInputsHidden && (
|
||||
<ViewFormDropdown iconColor={theme?.colorPathOnHeader} />
|
||||
)}
|
||||
{currentConversationId && (
|
||||
<Tooltip
|
||||
popupContent={t('share.chat.memory.actionButton')}
|
||||
>
|
||||
<ActionButton size='l' onClick={handleChatMemoryToggle}>
|
||||
<Memory className={cn('h-[18px] w-[18px]', theme?.colorPathOnHeader)} />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -18,11 +18,8 @@ import { CONVERSATION_ID_INFO } from '../constants'
|
||||
import { buildChatItemTree, getProcessedInputsFromUrlParams, getProcessedSystemVariablesFromUrlParams, getProcessedUserVariablesFromUrlParams } from '../utils'
|
||||
import { getProcessedFilesFromResponse } from '../../file-uploader/utils'
|
||||
import {
|
||||
deleteMemory,
|
||||
editMemory,
|
||||
fetchChatList,
|
||||
fetchConversations,
|
||||
fetchMemories,
|
||||
generationConversationName,
|
||||
updateFeedback,
|
||||
} from '@/service/share'
|
||||
@@ -36,7 +33,6 @@ import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
|
||||
import { noop } from 'lodash-es'
|
||||
import type { Memory } from '@/app/components/base/chat/types'
|
||||
import { useWebAppStore } from '@/context/web-app-context'
|
||||
|
||||
function getFormattedChatList(messages: any[]) {
|
||||
@@ -391,61 +387,6 @@ export const useEmbeddedChatbot = () => {
|
||||
notify({ type: 'success', message: t('common.api.success') })
|
||||
}, [isInstalledApp, appId, t, notify])
|
||||
|
||||
const [showChatMemory, setShowChatMemory] = useState(false)
|
||||
const [memoryList, setMemoryList] = useState<Memory[]>([])
|
||||
|
||||
const getMemoryList = useCallback(async (currentConversationId: string) => {
|
||||
const memories = await fetchMemories(currentConversationId, '', '', isInstalledApp, appId)
|
||||
setMemoryList(memories)
|
||||
}, [isInstalledApp, appId])
|
||||
|
||||
const clearAllMemory = useCallback(async () => {
|
||||
await deleteMemory('', isInstalledApp, appId)
|
||||
notify({ type: 'success', message: t('common.api.success') })
|
||||
getMemoryList(currentConversationId)
|
||||
}, [currentConversationId, getMemoryList])
|
||||
|
||||
const resetDefault = useCallback(async (memory: Memory) => {
|
||||
try {
|
||||
await editMemory(memory.spec.id, memory.spec.template, isInstalledApp, appId)
|
||||
getMemoryList(currentConversationId)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to reset memory:', error)
|
||||
}
|
||||
}, [currentConversationId, getMemoryList, isInstalledApp, appId])
|
||||
|
||||
const clearAllUpdateVersion = useCallback(async (memory: Memory) => {
|
||||
await deleteMemory(memory.spec.id, isInstalledApp, appId)
|
||||
notify({ type: 'success', message: t('common.api.success') })
|
||||
getMemoryList(currentConversationId)
|
||||
}, [currentConversationId, getMemoryList])
|
||||
|
||||
const switchMemoryVersion = useCallback(async (memory: Memory, version: string) => {
|
||||
const memories = await fetchMemories(currentConversationId, memory.spec.id, version, isInstalledApp, appId)
|
||||
const newMemory = memories[0]
|
||||
const newList = produce(memoryList, (draft) => {
|
||||
const index = draft.findIndex(item => item.spec.id === memory.spec.id)
|
||||
if (index !== -1)
|
||||
draft[index] = newMemory
|
||||
})
|
||||
setMemoryList(newList)
|
||||
}, [memoryList, currentConversationId, isInstalledApp, appId])
|
||||
|
||||
const updateMemory = useCallback(async (memory: Memory, content: string) => {
|
||||
try {
|
||||
await editMemory(memory.spec.id, content, isInstalledApp, appId)
|
||||
getMemoryList(currentConversationId)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to reset memory:', error)
|
||||
}
|
||||
}, [getMemoryList, currentConversationId, isInstalledApp, appId])
|
||||
|
||||
useEffect(() => {
|
||||
getMemoryList(currentConversationId)
|
||||
}, [currentConversationId, getMemoryList])
|
||||
|
||||
return {
|
||||
isInstalledApp,
|
||||
allowResetChat,
|
||||
@@ -485,13 +426,5 @@ export const useEmbeddedChatbot = () => {
|
||||
setCurrentConversationInputs,
|
||||
allInputsHidden,
|
||||
initUserVariables,
|
||||
showChatMemory,
|
||||
setShowChatMemory,
|
||||
memoryList,
|
||||
clearAllMemory,
|
||||
updateMemory,
|
||||
resetDefault,
|
||||
clearAllUpdateVersion,
|
||||
switchMemoryVersion,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import Loading from '@/app/components/base/loading'
|
||||
import LogoHeader from '@/app/components/base/logo/logo-embedded-chat-header'
|
||||
import Header from '@/app/components/base/chat/embedded-chatbot/header'
|
||||
import ChatWrapper from '@/app/components/base/chat/embedded-chatbot/chat-wrapper'
|
||||
import MemoryPanel from '@/app/components/base/chat/chat-with-history/memory'
|
||||
import DifyLogo from '@/app/components/base/logo/dify-logo'
|
||||
import cn from '@/utils/classnames'
|
||||
import useDocumentTitle from '@/hooks/use-document-title'
|
||||
@@ -31,14 +30,6 @@ const Chatbot = () => {
|
||||
chatShouldReloadKey,
|
||||
handleNewConversation,
|
||||
themeBuilder,
|
||||
showChatMemory,
|
||||
setShowChatMemory,
|
||||
memoryList,
|
||||
clearAllMemory,
|
||||
updateMemory,
|
||||
resetDefault,
|
||||
clearAllUpdateVersion,
|
||||
switchMemoryVersion,
|
||||
} = useEmbeddedChatbotContext()
|
||||
const { t } = useTranslation()
|
||||
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
|
||||
@@ -99,25 +90,6 @@ const Chatbot = () => {
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{showChatMemory && (
|
||||
<div className='fixed inset-0 z-50 flex flex-row-reverse bg-background-overlay p-1 backdrop-blur-sm'
|
||||
onClick={() => setShowChatMemory(false)}
|
||||
>
|
||||
<div className='flex h-full w-[360px] rounded-xl shadow-lg' onClick={e => e.stopPropagation()}>
|
||||
<MemoryPanel
|
||||
isMobile={isMobile}
|
||||
showChatMemory={showChatMemory}
|
||||
setShowChatMemory={setShowChatMemory}
|
||||
memoryList={memoryList}
|
||||
clearAllMemory={clearAllMemory}
|
||||
updateMemory={updateMemory}
|
||||
resetDefault={resetDefault}
|
||||
clearAllUpdateVersion={clearAllUpdateVersion}
|
||||
switchMemoryVersion={switchMemoryVersion}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -159,14 +131,6 @@ const EmbeddedChatbotWrapper = () => {
|
||||
setCurrentConversationInputs,
|
||||
allInputsHidden,
|
||||
initUserVariables,
|
||||
showChatMemory,
|
||||
setShowChatMemory,
|
||||
memoryList,
|
||||
clearAllMemory,
|
||||
updateMemory,
|
||||
resetDefault,
|
||||
clearAllUpdateVersion,
|
||||
switchMemoryVersion,
|
||||
} = useEmbeddedChatbot()
|
||||
|
||||
return <EmbeddedChatbotContext.Provider value={{
|
||||
@@ -203,14 +167,6 @@ const EmbeddedChatbotWrapper = () => {
|
||||
setCurrentConversationInputs,
|
||||
allInputsHidden,
|
||||
initUserVariables,
|
||||
showChatMemory,
|
||||
setShowChatMemory,
|
||||
memoryList,
|
||||
clearAllMemory,
|
||||
updateMemory,
|
||||
resetDefault,
|
||||
clearAllUpdateVersion,
|
||||
switchMemoryVersion,
|
||||
}}>
|
||||
<Chatbot />
|
||||
</EmbeddedChatbotContext.Provider>
|
||||
|
||||
@@ -95,36 +95,3 @@ export type Feedback = {
|
||||
rating: 'like' | 'dislike' | null
|
||||
content?: string | null
|
||||
}
|
||||
|
||||
export type MemorySpec = {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
template: string // default value
|
||||
instruction: string
|
||||
scope: string // app or node
|
||||
term: string // session or persistent
|
||||
strategy: string
|
||||
update_turns: number
|
||||
preserved_turns: number
|
||||
schedule_mode: string // sync or async
|
||||
end_user_visible: boolean
|
||||
end_user_editable: boolean
|
||||
}
|
||||
|
||||
export type ConversationMetaData = {
|
||||
type: string // mutable_visible_window
|
||||
visible_count: number // visible_count - preserved_turns = N messages waiting merged
|
||||
}
|
||||
|
||||
export type Memory = {
|
||||
tenant_id: string
|
||||
value: string
|
||||
app_id: string
|
||||
conversation_id?: string
|
||||
node_id?: string
|
||||
version: number
|
||||
edited_by_user: boolean
|
||||
conversation_metadata?: ConversationMetaData
|
||||
spec: MemorySpec
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
'use client'
|
||||
import useSWR from 'swr'
|
||||
import { produce } from 'immer'
|
||||
import React, { Fragment } from 'react'
|
||||
import { usePathname } from 'next/navigation'
|
||||
@@ -9,7 +8,6 @@ import { Listbox, ListboxButton, ListboxOption, ListboxOptions, Transition } fro
|
||||
import { CheckIcon, ChevronDownIcon } from '@heroicons/react/20/solid'
|
||||
import { useFeatures, useFeaturesStore } from '@/app/components/base/features/hooks'
|
||||
import type { Item } from '@/app/components/base/select'
|
||||
import { fetchAppVoices } from '@/service/apps'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import AudioBtn from '@/app/components/base/audio-btn'
|
||||
@@ -17,6 +15,7 @@ import { languages } from '@/i18n-config/language'
|
||||
import { TtsAutoPlay } from '@/types/app'
|
||||
import type { OnFeaturesChange } from '@/app/components/base/features/types'
|
||||
import classNames from '@/utils/classnames'
|
||||
import { useAppVoices } from '@/service/use-apps'
|
||||
|
||||
type VoiceParamConfigProps = {
|
||||
onClose: () => void
|
||||
@@ -39,7 +38,7 @@ const VoiceParamConfig = ({
|
||||
const localLanguagePlaceholder = languageItem?.name || t('common.placeholder.select')
|
||||
|
||||
const language = languageItem?.value
|
||||
const voiceItems = useSWR({ appId, language }, fetchAppVoices).data
|
||||
const { data: voiceItems } = useAppVoices(appId, language)
|
||||
let voiceItem = voiceItems?.find(item => item.value === text2speech?.voice)
|
||||
if (voiceItems && !voiceItem)
|
||||
voiceItem = voiceItems[0]
|
||||
|
||||
@@ -9,12 +9,7 @@ import Tooltip from '@/app/components/base/tooltip'
|
||||
import { useRenderI18nObject } from '@/hooks/use-i18n'
|
||||
import { useTriggerPluginDynamicOptions } from '@/service/use-triggers'
|
||||
import cn from '@/utils/classnames'
|
||||
import {
|
||||
RiArrowDownSFill,
|
||||
RiDraftLine,
|
||||
RiExternalLinkLine,
|
||||
RiInputField,
|
||||
} from '@remixicon/react'
|
||||
import { RiExternalLinkLine } from '@remixicon/react'
|
||||
import type { AnyFieldApi } from '@tanstack/react-form'
|
||||
import { useStore } from '@tanstack/react-form'
|
||||
import {
|
||||
@@ -24,19 +19,6 @@ import {
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Textarea from '@/app/components/base/textarea'
|
||||
import PromptEditor from '@/app/components/base/prompt-editor'
|
||||
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
|
||||
import ObjectValueList from '@/app/components/workflow/panel/chat-variable-panel/components/object-value-list'
|
||||
import ArrayValueList from '@/app/components/workflow/panel/chat-variable-panel/components/array-value-list'
|
||||
import ArrayBooleanValueList from '@/app/components/workflow/panel/chat-variable-panel/components/array-bool-list'
|
||||
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
|
||||
import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
|
||||
import Button from '@/app/components/base/button'
|
||||
import PromptGeneratorBtn from '@/app/components/workflow/nodes/llm/components/prompt-generator-btn'
|
||||
import Slider from '@/app/components/base/slider'
|
||||
import Switch from '../../../switch'
|
||||
import NodeSelector from '@/app/components/workflow/panel/chat-variable-panel/components/node-selector'
|
||||
|
||||
const getExtraProps = (type: FormTypeEnum) => {
|
||||
switch (type) {
|
||||
@@ -118,6 +100,7 @@ const BaseField = ({
|
||||
options,
|
||||
labelClassName: formLabelClassName,
|
||||
disabled: formSchemaDisabled,
|
||||
type: formItemType,
|
||||
dynamicSelectParams,
|
||||
multiple = false,
|
||||
tooltip,
|
||||
@@ -125,14 +108,7 @@ const BaseField = ({
|
||||
description,
|
||||
url,
|
||||
help,
|
||||
type: typeOrFn,
|
||||
fieldClassName: formFieldClassName,
|
||||
inputContainerClassName: formInputContainerClassName,
|
||||
inputClassName: formInputClassName,
|
||||
selfFormProps,
|
||||
onChange: formOnChange,
|
||||
} = formSchema
|
||||
const formItemType = typeof typeOrFn === 'function' ? typeOrFn(field.form) : typeOrFn
|
||||
const disabled = propsDisabled || formSchemaDisabled
|
||||
|
||||
const [translatedLabel, translatedPlaceholder, translatedTooltip, translatedDescription, translatedHelp] = useMemo(() => {
|
||||
@@ -172,8 +148,7 @@ const BaseField = ({
|
||||
return true
|
||||
|
||||
return option.show_on.every((condition) => {
|
||||
const conditionValue = watchedValues[condition.variable]
|
||||
return Array.isArray(condition.value) ? condition.value.includes(conditionValue) : conditionValue === condition.value
|
||||
return watchedValues[condition.variable] === condition.value
|
||||
})
|
||||
}).map((option) => {
|
||||
return {
|
||||
@@ -205,67 +180,21 @@ const BaseField = ({
|
||||
}))
|
||||
}, [dynamicOptionsData, renderI18nObject])
|
||||
|
||||
const booleanRadioValue = useMemo(() => {
|
||||
if (value === null || value === undefined)
|
||||
return undefined
|
||||
return value ? 1 : 0
|
||||
}, [value])
|
||||
|
||||
const handleChange = useCallback((value: any) => {
|
||||
if (disabled)
|
||||
return
|
||||
|
||||
field.handleChange(value)
|
||||
formOnChange?.(field.form, value)
|
||||
onChange?.(field.name, value)
|
||||
}, [field, formOnChange, onChange, disabled])
|
||||
|
||||
const selfProps = typeof selfFormProps === 'function' ? selfFormProps(field.form) : selfFormProps
|
||||
}, [field, onChange])
|
||||
|
||||
return (
|
||||
<>
|
||||
{
|
||||
selfProps?.withTopDivider && (
|
||||
<div className='h-px w-full bg-divider-subtle' />
|
||||
)
|
||||
}
|
||||
<div className={cn(fieldClassName, formFieldClassName)}>
|
||||
<div
|
||||
className={cn(formItemType === FormTypeEnum.collapse && 'cursor-pointer', labelClassName, formLabelClassName)}
|
||||
onClick={() => {
|
||||
if (formItemType === FormTypeEnum.collapse)
|
||||
handleChange(!value)
|
||||
}}
|
||||
>
|
||||
<div className={cn(fieldClassName)}>
|
||||
<div className={cn(labelClassName, formLabelClassName)}>
|
||||
{translatedLabel}
|
||||
{
|
||||
required && !isValidElement(label) && (
|
||||
<span className='ml-1 text-text-destructive-secondary'>*</span>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.collapse && (
|
||||
<RiArrowDownSFill
|
||||
className={cn(
|
||||
'h-4 w-4 text-text-quaternary',
|
||||
!value && '-rotate-90',
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.editMode && (
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='small'
|
||||
className='text-text-tertiary'
|
||||
onClick={() => handleChange(!value)}
|
||||
>
|
||||
{value ? <RiInputField className='mr-1 h-3.5 w-3.5' /> : <RiDraftLine className='mr-1 h-3.5 w-3.5' />}
|
||||
{selfProps?.editModeLabel}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
{tooltip && (
|
||||
<Tooltip
|
||||
popupContent={<div className='w-[200px]'>{translatedTooltip}</div>}
|
||||
@@ -273,9 +202,9 @@ const BaseField = ({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className={cn(inputContainerClassName, formInputContainerClassName)}>
|
||||
<div className={cn(inputContainerClassName)}>
|
||||
{
|
||||
!selfProps?.withSlider && [FormTypeEnum.textInput, FormTypeEnum.secretInput, FormTypeEnum.textNumber].includes(formItemType) && (
|
||||
[FormTypeEnum.textInput, FormTypeEnum.secretInput, FormTypeEnum.textNumber].includes(formItemType) && (
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
@@ -292,34 +221,6 @@ const BaseField = ({
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.textNumber && selfProps?.withSlider && (
|
||||
<div className='flex items-center space-x-2'>
|
||||
<Slider
|
||||
min={selfProps?.sliderMin}
|
||||
max={selfProps?.sliderMax}
|
||||
step={selfProps?.sliderStep}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
className={cn(selfProps.sliderClassName)}
|
||||
trackClassName={cn(selfProps.sliderTrackClassName)}
|
||||
thumbClassName={cn(selfProps.sliderThumbClassName)}
|
||||
/>
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
type='number'
|
||||
className={cn('', inputClassName, formInputClassName)}
|
||||
wrapperClassName={cn(selfProps.inputWrapperClassName)}
|
||||
value={value || ''}
|
||||
onChange={e => handleChange(e.target.value)}
|
||||
onBlur={field.handleBlur}
|
||||
disabled={disabled}
|
||||
placeholder={translatedPlaceholder}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.select && !multiple && (
|
||||
<PureSelect
|
||||
@@ -375,16 +276,15 @@ const BaseField = ({
|
||||
<div
|
||||
key={option.value}
|
||||
className={cn(
|
||||
'system-sm-regular hover:bg-components-option-card-option-hover-bg hover:border-components-option-card-option-hover-border flex h-8 flex-[1] grow cursor-pointer items-center justify-center rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg p-2 text-text-secondary',
|
||||
'system-sm-regular hover:bg-components-option-card-option-hover-bg hover:border-components-option-card-option-hover-border flex h-8 flex-[1] grow cursor-pointer items-center justify-center gap-2 rounded-lg border border-components-option-card-option-border bg-components-option-card-option-bg p-2 text-text-secondary',
|
||||
value === option.value && 'border-components-option-card-option-selected-border bg-components-option-card-option-selected-bg text-text-primary shadow-xs',
|
||||
disabled && 'cursor-not-allowed opacity-50',
|
||||
inputClassName,
|
||||
formInputClassName,
|
||||
)}
|
||||
onClick={() => handleChange(option.value)}
|
||||
onClick={() => !disabled && handleChange(option.value)}
|
||||
>
|
||||
{
|
||||
selfProps?.showRadioUI && (
|
||||
formSchema.showRadioUI && (
|
||||
<RadioE
|
||||
className='mr-2'
|
||||
isChecked={value === option.value}
|
||||
@@ -398,151 +298,18 @@ const BaseField = ({
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.textareaInput && (
|
||||
<Textarea
|
||||
className={cn(
|
||||
'min-h-[80px]',
|
||||
inputClassName,
|
||||
formInputClassName,
|
||||
)}
|
||||
value={value}
|
||||
placeholder={translatedPlaceholder}
|
||||
onChange={e => handleChange(e.target.value)}
|
||||
onBlur={field.handleBlur}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.promptInput && (
|
||||
<div className={cn(
|
||||
'relative rounded-lg bg-components-input-bg-normal p-2',
|
||||
formInputContainerClassName,
|
||||
)}>
|
||||
{
|
||||
selfProps?.enablePromptGenerator && (
|
||||
<PromptGeneratorBtn
|
||||
nodeId={selfProps?.nodeId}
|
||||
editorId={selfProps?.editorId}
|
||||
className='absolute right-0 top-[-26px]'
|
||||
onGenerated={handleChange}
|
||||
modelConfig={selfProps?.modelConfig}
|
||||
currentPrompt={value}
|
||||
isBasicMode={selfProps?.isBasicMode}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<PromptEditor
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
onBlur={field.handleBlur}
|
||||
editable={!disabled}
|
||||
placeholder={translatedPlaceholder || selfProps?.placeholder}
|
||||
className={cn(
|
||||
'min-h-[80px]',
|
||||
inputClassName,
|
||||
formInputClassName,
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.objectList && (
|
||||
<ObjectValueList
|
||||
list={value}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.arrayList && (
|
||||
<ArrayValueList
|
||||
isString={selfProps?.isString}
|
||||
list={value}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.booleanList && (
|
||||
<ArrayBooleanValueList
|
||||
list={value}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.jsonInput && (
|
||||
<div className='w-full rounded-[10px] bg-components-input-bg-normal py-2 pl-3 pr-1' style={{ height: selfProps?.editorMinHeight }}>
|
||||
<CodeEditor
|
||||
isExpand
|
||||
noWrapper
|
||||
language={CodeLanguage.json}
|
||||
value={value}
|
||||
placeholder={<div className='whitespace-pre'>{selfProps?.placeholder as string}</div>}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.modelSelector && (
|
||||
<ModelParameterModal
|
||||
popupClassName='!w-[387px]'
|
||||
modelId={value?.name}
|
||||
provider={value?.provider}
|
||||
setModel={({ modelId, mode, provider }) => {
|
||||
handleChange({
|
||||
mode,
|
||||
provider,
|
||||
name: modelId,
|
||||
completion_params: value?.completion_params,
|
||||
})
|
||||
}}
|
||||
completionParams={value?.completion_params}
|
||||
onCompletionParamsChange={(params) => {
|
||||
handleChange({
|
||||
...value,
|
||||
completion_params: params,
|
||||
})
|
||||
}}
|
||||
readonly={disabled}
|
||||
isAdvancedMode
|
||||
isInWorkflow
|
||||
hideDebugWithMultipleModel
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.nodeSelector && (
|
||||
<NodeSelector
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.boolean && (
|
||||
<Radio.Group
|
||||
className={cn('flex w-full items-center space-x-1', inputClassName, formInputClassName)}
|
||||
value={booleanRadioValue}
|
||||
onChange={handleChange}
|
||||
className='flex w-fit items-center'
|
||||
value={value}
|
||||
onChange={v => field.handleChange(v)}
|
||||
>
|
||||
<Radio value={1} className='m-0 h-7 flex-1 justify-center p-0'>True</Radio>
|
||||
<Radio value={0} className='m-0 h-7 flex-1 justify-center p-0'>False</Radio>
|
||||
<Radio value={true} className='!mr-1'>True</Radio>
|
||||
<Radio value={false}>False</Radio>
|
||||
</Radio.Group>
|
||||
)
|
||||
}
|
||||
{
|
||||
formItemType === FormTypeEnum.switch && (
|
||||
<Switch
|
||||
defaultValue={value}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{fieldState?.validateStatus && [FormItemValidateStatusEnum.Error, FormItemValidateStatusEnum.Warning].includes(fieldState?.validateStatus) && (
|
||||
<div className={cn(
|
||||
'system-xs-regular mt-1 px-0 py-[2px]',
|
||||
@@ -572,11 +339,6 @@ const BaseField = ({
|
||||
</a>
|
||||
)
|
||||
}
|
||||
{
|
||||
selfProps?.withBottomDivider && (
|
||||
<div className='h-px w-full bg-divider-subtle' />
|
||||
)
|
||||
}
|
||||
</>
|
||||
|
||||
)
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type {
|
||||
AnyFieldApi,
|
||||
AnyFormApi,
|
||||
@@ -32,7 +31,6 @@ import {
|
||||
useGetFormValues,
|
||||
useGetValidators,
|
||||
} from '@/app/components/base/form/hooks'
|
||||
import { Button } from '@/app/components/base/button'
|
||||
|
||||
export type BaseFormProps = {
|
||||
formSchemas?: FormSchema[]
|
||||
@@ -41,7 +39,6 @@ export type BaseFormProps = {
|
||||
ref?: FormRef
|
||||
disabled?: boolean
|
||||
formFromProps?: AnyFormApi
|
||||
onCancel?: () => void
|
||||
onChange?: (field: string, value: any) => void
|
||||
onSubmit?: (e: React.FormEvent<HTMLFormElement>) => void
|
||||
preventDefaultSubmit?: boolean
|
||||
@@ -58,12 +55,10 @@ const BaseForm = ({
|
||||
ref,
|
||||
disabled,
|
||||
formFromProps,
|
||||
onCancel,
|
||||
onChange,
|
||||
onSubmit,
|
||||
preventDefaultSubmit = false,
|
||||
}: BaseFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const initialDefaultValues = useMemo(() => {
|
||||
if (defaultValues)
|
||||
return defaultValues
|
||||
@@ -87,22 +82,8 @@ const BaseForm = ({
|
||||
const result: Record<string, any> = {}
|
||||
formSchemas.forEach((schema) => {
|
||||
const { show_on } = schema
|
||||
const showOn = typeof show_on === 'function' ? show_on(form) : show_on
|
||||
if (showOn?.length) {
|
||||
showOn?.forEach((condition) => {
|
||||
result[condition.variable] = s.values[condition.variable]
|
||||
})
|
||||
}
|
||||
})
|
||||
return result
|
||||
})
|
||||
const moreOnValues = useStore(form.store, (s: any) => {
|
||||
const result: Record<string, any> = {}
|
||||
formSchemas.forEach((schema) => {
|
||||
const { more_on } = schema
|
||||
const moreOn = typeof more_on === 'function' ? more_on(form) : more_on
|
||||
if (moreOn?.length) {
|
||||
moreOn?.forEach((condition) => {
|
||||
if (show_on?.length) {
|
||||
show_on.forEach((condition) => {
|
||||
result[condition.variable] = s.values[condition.variable]
|
||||
})
|
||||
}
|
||||
@@ -154,18 +135,11 @@ const BaseForm = ({
|
||||
const formSchema = formSchemas?.find(schema => schema.name === field.name)
|
||||
|
||||
if (formSchema) {
|
||||
const { more_on = [] } = formSchema
|
||||
const moreOn = typeof more_on === 'function' ? more_on(form) : more_on
|
||||
const more = (moreOn || []).every((condition) => {
|
||||
const conditionValue = moreOnValues[condition.variable]
|
||||
return Array.isArray(condition.value) ? condition.value.includes(conditionValue) : conditionValue === condition.value
|
||||
})
|
||||
|
||||
return (
|
||||
<BaseField
|
||||
field={field}
|
||||
formSchema={formSchema}
|
||||
fieldClassName={cn(fieldClassName ?? formSchema.fieldClassName, !more ? 'absolute top-[-9999px]' : '')}
|
||||
fieldClassName={fieldClassName ?? formSchema.fieldClassName}
|
||||
labelClassName={labelClassName ?? formSchema.labelClassName}
|
||||
inputContainerClassName={inputContainerClassName}
|
||||
inputClassName={inputClassName}
|
||||
@@ -177,7 +151,7 @@ const BaseForm = ({
|
||||
}
|
||||
|
||||
return null
|
||||
}, [formSchemas, fieldClassName, labelClassName, inputContainerClassName, inputClassName, disabled, onChange, moreOnValues, fieldStates])
|
||||
}, [formSchemas, fieldClassName, labelClassName, inputContainerClassName, inputClassName, disabled, onChange, fieldStates])
|
||||
|
||||
const renderFieldWrapper = useCallback((formSchema: FormSchema) => {
|
||||
const validators = getValidators(formSchema)
|
||||
@@ -185,10 +159,10 @@ const BaseForm = ({
|
||||
name,
|
||||
show_on = [],
|
||||
} = formSchema
|
||||
const showOn = typeof show_on === 'function' ? show_on(form) : show_on
|
||||
const show = (showOn || []).every((condition) => {
|
||||
|
||||
const show = show_on?.every((condition) => {
|
||||
const conditionValue = showOnValues[condition.variable]
|
||||
return Array.isArray(condition.value) ? condition.value.includes(conditionValue) : conditionValue === condition.value
|
||||
return conditionValue === condition.value
|
||||
})
|
||||
|
||||
if (!show)
|
||||
@@ -222,28 +196,6 @@ const BaseForm = ({
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{formSchemas.map(renderFieldWrapper)}
|
||||
{
|
||||
onSubmit && (
|
||||
<div className='flex justify-end space-x-2'>
|
||||
{
|
||||
onCancel && (
|
||||
<Button
|
||||
variant='secondary'
|
||||
onClick={onCancel}
|
||||
>
|
||||
{t('common.operation.cancel')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
<Button
|
||||
variant='primary'
|
||||
onClick={() => onSubmit(form.getValues())}
|
||||
>
|
||||
{t('common.operation.save')}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { memo } from 'react'
|
||||
import { BaseForm } from '../../components/base'
|
||||
import type { BaseFormProps } from '../../components/base'
|
||||
|
||||
const VariableForm = ({
|
||||
formSchemas = [],
|
||||
defaultValues,
|
||||
ref,
|
||||
formFromProps,
|
||||
...rest
|
||||
}: BaseFormProps) => {
|
||||
return (
|
||||
<BaseForm
|
||||
ref={ref}
|
||||
formSchemas={formSchemas}
|
||||
defaultValues={defaultValues}
|
||||
formClassName='space-y-3'
|
||||
labelClassName='h-6 flex items-center mb-1 system-sm-medium text-text-secondary'
|
||||
formFromProps={formFromProps}
|
||||
{...rest}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(VariableForm)
|
||||
@@ -15,14 +15,13 @@ export const useCheckValidated = (form: AnyFormApi, FormSchemas: FormSchema[]) =
|
||||
const errorArray = Object.keys(fields).reduce((acc: string[], key: string) => {
|
||||
const currentSchema = FormSchemas.find(schema => schema.name === key)
|
||||
const { show_on = [] } = currentSchema || {}
|
||||
const showOn = typeof show_on === 'function' ? show_on(form) : show_on
|
||||
const showOnValues = (showOn || []).reduce((acc, condition) => {
|
||||
const showOnValues = show_on.reduce((acc, condition) => {
|
||||
acc[condition.variable] = values[condition.variable]
|
||||
return acc
|
||||
}, {} as Record<string, any>)
|
||||
const show = (showOn || []).every((condition) => {
|
||||
const show = show_on?.every((condition) => {
|
||||
const conditionValue = showOnValues[condition.variable]
|
||||
return Array.isArray(condition.value) ? condition.value.includes(conditionValue) : conditionValue === condition.value
|
||||
return conditionValue === condition.value
|
||||
})
|
||||
const errors: any[] = show ? fields[key].errors : []
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ export type TypeWithI18N<T = string> = {
|
||||
|
||||
export type FormShowOnObject = {
|
||||
variable: string
|
||||
value: string | string[]
|
||||
value: string
|
||||
}
|
||||
|
||||
export enum FormTypeEnum {
|
||||
@@ -33,17 +33,7 @@ export enum FormTypeEnum {
|
||||
multiToolSelector = 'array[tools]',
|
||||
appSelector = 'app-selector',
|
||||
dynamicSelect = 'dynamic-select',
|
||||
textareaInput = 'textarea-input',
|
||||
promptInput = 'prompt-input',
|
||||
objectList = 'object-list',
|
||||
arrayList = 'array-list',
|
||||
jsonInput = 'json-input',
|
||||
collapse = 'collapse',
|
||||
editMode = 'edit-mode',
|
||||
boolean = 'boolean',
|
||||
booleanList = 'boolean-list',
|
||||
switch = 'switch',
|
||||
nodeSelector = 'node-selector', // used in memory variable form
|
||||
}
|
||||
|
||||
export type FormOption = {
|
||||
@@ -63,7 +53,7 @@ export enum FormItemValidateStatusEnum {
|
||||
}
|
||||
|
||||
export type FormSchema = {
|
||||
type: FormTypeEnum | ((form: AnyFormApi) => FormTypeEnum)
|
||||
type: FormTypeEnum
|
||||
name: string
|
||||
label: string | ReactNode | TypeWithI18N | Record<Locale, string>
|
||||
required: boolean
|
||||
@@ -71,20 +61,15 @@ export type FormSchema = {
|
||||
default?: any
|
||||
description?: string | TypeWithI18N | Record<Locale, string>
|
||||
tooltip?: string | TypeWithI18N | Record<Locale, string>
|
||||
show_on?: FormShowOnObject[] | ((form: AnyFormApi) => FormShowOnObject[])
|
||||
more_on?: FormShowOnObject[] | ((form: AnyFormApi) => FormShowOnObject[])
|
||||
show_on?: FormShowOnObject[]
|
||||
url?: string
|
||||
scope?: string
|
||||
help?: string | TypeWithI18N | Record<Locale, string>
|
||||
placeholder?: string | TypeWithI18N | Record<Locale, string>
|
||||
options?: FormOption[]
|
||||
fieldClassName?: string
|
||||
labelClassName?: string
|
||||
inputContainerClassName?: string
|
||||
inputClassName?: string
|
||||
fieldClassName?: string
|
||||
validators?: AnyValidators
|
||||
selfFormProps?: ((form: AnyFormApi) => Record<string, any>) | Record<string, any>
|
||||
onChange?: (form: AnyFormApi, v: any) => void
|
||||
showRadioUI?: boolean
|
||||
disabled?: boolean
|
||||
showCopy?: boolean
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.678 1.6502C11.1023 1.50885 11.5679 1.56398 11.9473 1.80108L14.2947 3.26885C14.7333 3.54295 15 4.02396 15 4.54107V5.62505L15.9001 6.30035C16.2777 6.58359 16.5 7.02807 16.5 7.50005V9.75005C16.5 10.222 16.2777 10.6665 15.9001 10.9498L15 11.6251V13.4598C14.9999 13.9767 14.7336 14.4572 14.2954 14.7313L11.9473 16.199C11.6152 16.4066 11.217 16.4748 10.8384 16.3939L10.678 16.3499L9 15.7903L7.32202 16.3499C6.89768 16.4913 6.43213 16.4362 6.05273 16.199L3.70532 14.7313C3.2672 14.4572 3.00013 13.9768 3 13.4598V11.6251L2.09985 10.9498C1.72225 10.6665 1.5 10.222 1.5 9.75005V7.50005C1.50004 7.02809 1.72231 6.5836 2.09985 6.30035L3 5.62505V4.54107C3.00005 4.02394 3.26679 3.54294 3.70532 3.26885L6.05273 1.80108C6.43204 1.56403 6.89766 1.50884 7.32202 1.6502L9 2.20977L10.678 1.6502ZM9.75 3.54058V5.68951L10.8625 6.80206C10.9863 6.76904 11.1159 6.75005 11.25 6.75005C12.0784 6.75005 12.75 7.42165 12.75 8.25005C12.75 9.07848 12.0784 9.75005 11.25 9.75005C10.4216 9.75005 9.75 9.07848 9.75 8.25005C9.75001 8.11594 9.76898 7.98631 9.802 7.8626L8.68945 6.75005C8.40829 6.46885 8.25003 6.08736 8.25 5.68951V3.54058L6.84814 3.0733L4.5 4.54107V5.62505C4.5 6.09705 4.27767 6.54146 3.90015 6.82476L3 7.50005V9.75005L3.90015 10.4253C4.27764 10.7086 4.49996 11.1531 4.5 11.6251V13.459L6.84814 14.9268L8.25 14.4588V12.3106L7.13672 11.1973C7.01316 11.2303 6.88394 11.2501 6.75 11.2501C5.92157 11.2501 5.25 10.5785 5.25 9.75005C5.25003 8.92165 5.92159 8.25005 6.75 8.25005C7.57841 8.25005 8.24997 8.92165 8.25 9.75005C8.25 9.88396 8.23019 10.0132 8.19727 10.1368L9.31055 11.2501C9.59176 11.5313 9.74996 11.9128 9.75 12.3106V14.4588L11.1519 14.9268L13.5 13.459V11.6251C13.5 11.153 13.7224 10.7086 14.0999 10.4253L15 9.75005V7.50005L14.0999 6.82476C13.7224 6.54147 13.5 6.09707 13.5 5.62505V4.54107L11.1519 3.0733L9.75 3.54058Z" fill="#354052"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "18",
|
||||
"height": "18",
|
||||
"viewBox": "0 0 18 18",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"fill-rule": "evenodd",
|
||||
"clip-rule": "evenodd",
|
||||
"d": "M10.678 1.6502C11.1023 1.50885 11.5679 1.56398 11.9473 1.80108L14.2947 3.26885C14.7333 3.54295 15 4.02396 15 4.54107V5.62505L15.9001 6.30035C16.2777 6.58359 16.5 7.02807 16.5 7.50005V9.75005C16.5 10.222 16.2777 10.6665 15.9001 10.9498L15 11.6251V13.4598C14.9999 13.9767 14.7336 14.4572 14.2954 14.7313L11.9473 16.199C11.6152 16.4066 11.217 16.4748 10.8384 16.3939L10.678 16.3499L9 15.7903L7.32202 16.3499C6.89768 16.4913 6.43213 16.4362 6.05273 16.199L3.70532 14.7313C3.2672 14.4572 3.00013 13.9768 3 13.4598V11.6251L2.09985 10.9498C1.72225 10.6665 1.5 10.222 1.5 9.75005V7.50005C1.50004 7.02809 1.72231 6.5836 2.09985 6.30035L3 5.62505V4.54107C3.00005 4.02394 3.26679 3.54294 3.70532 3.26885L6.05273 1.80108C6.43204 1.56403 6.89766 1.50884 7.32202 1.6502L9 2.20977L10.678 1.6502ZM9.75 3.54058V5.68951L10.8625 6.80206C10.9863 6.76904 11.1159 6.75005 11.25 6.75005C12.0784 6.75005 12.75 7.42165 12.75 8.25005C12.75 9.07848 12.0784 9.75005 11.25 9.75005C10.4216 9.75005 9.75 9.07848 9.75 8.25005C9.75001 8.11594 9.76898 7.98631 9.802 7.8626L8.68945 6.75005C8.40829 6.46885 8.25003 6.08736 8.25 5.68951V3.54058L6.84814 3.0733L4.5 4.54107V5.62505C4.5 6.09705 4.27767 6.54146 3.90015 6.82476L3 7.50005V9.75005L3.90015 10.4253C4.27764 10.7086 4.49996 11.1531 4.5 11.6251V13.459L6.84814 14.9268L8.25 14.4588V12.3106L7.13672 11.1973C7.01316 11.2303 6.88394 11.2501 6.75 11.2501C5.92157 11.2501 5.25 10.5785 5.25 9.75005C5.25003 8.92165 5.92159 8.25005 6.75 8.25005C7.57841 8.25005 8.24997 8.92165 8.25 9.75005C8.25 9.88396 8.23019 10.0132 8.19727 10.1368L9.31055 11.2501C9.59176 11.5313 9.74996 11.9128 9.75 12.3106V14.4588L11.1519 14.9268L13.5 13.459V11.6251C13.5 11.153 13.7224 10.7086 14.0999 10.4253L15 9.75005V7.50005L14.0999 6.82476C13.7224 6.54147 13.5 6.09707 13.5 5.62505V4.54107L11.1519 3.0733L9.75 3.54058Z",
|
||||
"fill": "currentColor"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Memory"
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import * as React from 'react'
|
||||
import data from './Memory.json'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import type { IconData } from '@/app/components/base/icons/IconBase'
|
||||
|
||||
const Icon = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>;
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
Icon.displayName = 'Memory'
|
||||
|
||||
export default Icon
|
||||
@@ -6,6 +6,5 @@ export { default as GlobalVariable } from './GlobalVariable'
|
||||
export { default as Icon3Dots } from './Icon3Dots'
|
||||
export { default as LongArrowLeft } from './LongArrowLeft'
|
||||
export { default as LongArrowRight } from './LongArrowRight'
|
||||
export { default as Memory } from './Memory'
|
||||
export { default as SearchMenu } from './SearchMenu'
|
||||
export { default as Tools } from './Tools'
|
||||
|
||||
@@ -10,7 +10,6 @@ export const LAST_RUN_PLACEHOLDER_TEXT = '{{#last_run#}}'
|
||||
export const PRE_PROMPT_PLACEHOLDER_TEXT = '{{#pre_prompt#}}'
|
||||
export const UPDATE_DATASETS_EVENT_EMITTER = 'prompt-editor-context-block-update-datasets'
|
||||
export const UPDATE_HISTORY_EVENT_EMITTER = 'prompt-editor-history-block-update-role'
|
||||
export const UPDATE_WORKFLOW_VARIABLES_EVENT_EMITTER = 'prompt-editor-workflow-variables-block-update-variables'
|
||||
|
||||
export const checkHasContextBlock = (text: string) => {
|
||||
if (!text)
|
||||
|
||||
@@ -61,8 +61,6 @@ import { VariableValueBlockNode } from './plugins/variable-value-block/node'
|
||||
import { CustomTextNode } from './plugins/custom-text/node'
|
||||
import OnBlurBlock from './plugins/on-blur-or-focus-block'
|
||||
import UpdateBlock from './plugins/update-block'
|
||||
import MemoryPopupPlugin from './plugins/memory-popup-plugin'
|
||||
|
||||
import { textToEditorState } from './utils'
|
||||
import type {
|
||||
ContextBlockType,
|
||||
@@ -78,11 +76,9 @@ import type {
|
||||
import {
|
||||
UPDATE_DATASETS_EVENT_EMITTER,
|
||||
UPDATE_HISTORY_EVENT_EMITTER,
|
||||
UPDATE_WORKFLOW_VARIABLES_EVENT_EMITTER,
|
||||
} from './constants'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import cn from '@/utils/classnames'
|
||||
import PromptEditorProvider from './store/provider'
|
||||
|
||||
export type PromptEditorProps = {
|
||||
instanceId?: string
|
||||
@@ -178,13 +174,6 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
payload: historyBlock?.history,
|
||||
} as any)
|
||||
}, [eventEmitter, historyBlock?.history])
|
||||
useEffect(() => {
|
||||
eventEmitter?.emit({
|
||||
type: UPDATE_WORKFLOW_VARIABLES_EVENT_EMITTER,
|
||||
payload: workflowVariableBlock?.variables,
|
||||
instanceId,
|
||||
} as any)
|
||||
}, [eventEmitter, workflowVariableBlock?.variables])
|
||||
|
||||
return (
|
||||
<LexicalComposer initialConfig={{ ...initialConfig, editable }}>
|
||||
@@ -209,12 +198,6 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
}
|
||||
ErrorBoundary={LexicalErrorBoundary}
|
||||
/>
|
||||
{workflowVariableBlock?.show && workflowVariableBlock?.isMemorySupported && (
|
||||
<MemoryPopupPlugin
|
||||
instanceId={instanceId}
|
||||
memoryVariables={workflowVariableBlock?.variables?.find(v => v.nodeId === 'memory_block')?.vars || []}
|
||||
/>
|
||||
)}
|
||||
<ComponentPickerBlock
|
||||
triggerString='/'
|
||||
contextBlock={contextBlock}
|
||||
@@ -320,12 +303,4 @@ const PromptEditor: FC<PromptEditorProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
const PromptEditorWithProvider = ({ instanceId, ...props }: PromptEditorProps) => {
|
||||
return (
|
||||
<PromptEditorProvider instanceId={instanceId}>
|
||||
<PromptEditor {...props} instanceId={instanceId} />
|
||||
</PromptEditorProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default PromptEditorWithProvider
|
||||
export default PromptEditor
|
||||
|
||||
@@ -283,19 +283,7 @@ export const useOptions = (
|
||||
const workflowVariableOptions = useMemo(() => {
|
||||
if (!workflowVariableBlockType?.show)
|
||||
return []
|
||||
let res = workflowVariableBlockType.variables || []
|
||||
|
||||
if (!workflowVariableBlockType.isMemorySupported) {
|
||||
res = res.map((v) => {
|
||||
if (v.nodeId === 'conversation') {
|
||||
return {
|
||||
...v,
|
||||
vars: v.vars.filter(vv => !vv.variable.startsWith('memory_block.')),
|
||||
}
|
||||
}
|
||||
return v
|
||||
})
|
||||
}
|
||||
const res = workflowVariableBlockType.variables || []
|
||||
if(errorMessageBlockType?.show && res.findIndex(v => v.nodeId === 'error_message') === -1) {
|
||||
res.unshift({
|
||||
nodeId: 'error_message',
|
||||
|
||||
@@ -1,273 +0,0 @@
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
autoUpdate,
|
||||
flip,
|
||||
offset,
|
||||
shift,
|
||||
size,
|
||||
useFloating,
|
||||
} from '@floating-ui/react'
|
||||
import {
|
||||
RiAddLine,
|
||||
} from '@remixicon/react'
|
||||
import { Memory } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import {
|
||||
$getSelection,
|
||||
$isRangeSelection,
|
||||
} from 'lexical'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { MEMORY_POPUP_SHOW_BY_EVENT_EMITTER, MEMORY_VAR_CREATED_BY_MODAL_BY_EVENT_EMITTER, MEMORY_VAR_MODAL_SHOW_BY_EVENT_EMITTER } from '@/app/components/workflow/nodes/_base/components/prompt/type'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import VariableIcon from '@/app/components/workflow/nodes/_base/components/variable/variable-label/base/variable-icon'
|
||||
import type {
|
||||
Var,
|
||||
} from '@/app/components/workflow/types'
|
||||
import { INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND } from '../workflow-variable-block'
|
||||
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
export type MemoryPopupProps = {
|
||||
className?: string
|
||||
container?: Element | null
|
||||
instanceId?: string
|
||||
memoryVariables: Var[]
|
||||
}
|
||||
|
||||
export default function MemoryPopupPlugin({
|
||||
className,
|
||||
container,
|
||||
instanceId,
|
||||
memoryVariables,
|
||||
}: MemoryPopupProps) {
|
||||
const { t } = useTranslation()
|
||||
const [editor] = useLexicalComposerContext()
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
const portalRef = useRef<HTMLDivElement | null>(null)
|
||||
const lastSelectionRef = useRef<Range | null>(null)
|
||||
|
||||
const containerEl = useMemo(() => container ?? (typeof document !== 'undefined' ? document.body : null), [container])
|
||||
|
||||
const useContainer = !!containerEl && containerEl !== document.body
|
||||
const memoryVarInNode = memoryVariables.filter(memoryVariable => memoryVariable.memoryVariableNodeId)
|
||||
const memoryVarInApp = memoryVariables.filter(memoryVariable => !memoryVariable.memoryVariableNodeId)
|
||||
|
||||
const { refs, floatingStyles, isPositioned } = useFloating({
|
||||
placement: 'bottom-start',
|
||||
middleware: [
|
||||
offset(0), // fix hide cursor
|
||||
shift({
|
||||
padding: 8,
|
||||
altBoundary: true,
|
||||
}),
|
||||
flip(),
|
||||
size({
|
||||
apply({ availableWidth, availableHeight, elements }) {
|
||||
Object.assign(elements.floating.style, {
|
||||
maxWidth: `${Math.min(400, availableWidth)}px`,
|
||||
maxHeight: `${Math.min(300, availableHeight)}px`,
|
||||
overflow: 'auto',
|
||||
})
|
||||
},
|
||||
padding: 8,
|
||||
}),
|
||||
],
|
||||
whileElementsMounted: autoUpdate,
|
||||
})
|
||||
|
||||
const openPortal = useCallback(() => {
|
||||
const domSelection = window.getSelection()
|
||||
let range: Range | null = null
|
||||
if (domSelection && domSelection.rangeCount > 0)
|
||||
range = domSelection.getRangeAt(0).cloneRange()
|
||||
else
|
||||
range = lastSelectionRef.current
|
||||
|
||||
if (range) {
|
||||
const rects = range.getClientRects()
|
||||
let rect: DOMRect | null = null
|
||||
|
||||
if (rects && rects.length)
|
||||
rect = rects[rects.length - 1]
|
||||
|
||||
else
|
||||
rect = range.getBoundingClientRect()
|
||||
|
||||
if (rect.width === 0 && rect.height === 0) {
|
||||
const root = editor.getRootElement()
|
||||
if (root) {
|
||||
const sc = range.startContainer
|
||||
const node = sc.nodeType === Node.ELEMENT_NODE
|
||||
? sc as Element
|
||||
: (sc.parentElement || root)
|
||||
|
||||
rect = node.getBoundingClientRect()
|
||||
|
||||
if (rect.width === 0 && rect.height === 0)
|
||||
rect = root.getBoundingClientRect()
|
||||
}
|
||||
}
|
||||
|
||||
if (rect && !(rect.top === 0 && rect.left === 0 && rect.width === 0 && rect.height === 0)) {
|
||||
const virtualEl = {
|
||||
getBoundingClientRect() {
|
||||
return rect!
|
||||
},
|
||||
}
|
||||
refs.setReference(virtualEl as Element)
|
||||
}
|
||||
}
|
||||
|
||||
setOpen(true)
|
||||
}, [setOpen])
|
||||
|
||||
const closePortal = useCallback(() => {
|
||||
setOpen(false)
|
||||
}, [setOpen])
|
||||
|
||||
const handleSelectVariable = useCallback((variable: string[]) => {
|
||||
editor.dispatchCommand(INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND, variable)
|
||||
closePortal()
|
||||
}, [editor, closePortal])
|
||||
|
||||
const handleCreate = useCallback(() => {
|
||||
eventEmitter?.emit({ type: MEMORY_VAR_MODAL_SHOW_BY_EVENT_EMITTER, instanceId } as any)
|
||||
closePortal()
|
||||
}, [eventEmitter, instanceId, closePortal])
|
||||
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
if (v.type === MEMORY_POPUP_SHOW_BY_EVENT_EMITTER && v.instanceId === instanceId)
|
||||
openPortal()
|
||||
})
|
||||
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
if (v.type === MEMORY_VAR_CREATED_BY_MODAL_BY_EVENT_EMITTER && v.instanceId === instanceId)
|
||||
handleSelectVariable(v.variable)
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
return editor.registerUpdateListener(({ editorState }) => {
|
||||
editorState.read(() => {
|
||||
const selection = $getSelection()
|
||||
if ($isRangeSelection(selection)) {
|
||||
const domSelection = window.getSelection()
|
||||
if (domSelection && domSelection.rangeCount > 0)
|
||||
lastSelectionRef.current = domSelection.getRangeAt(0).cloneRange()
|
||||
}
|
||||
})
|
||||
})
|
||||
}, [editor])
|
||||
|
||||
useEffect(() => {
|
||||
if (!open)
|
||||
return
|
||||
|
||||
const onMouseDown = (e: MouseEvent) => {
|
||||
if (!portalRef.current)
|
||||
return
|
||||
if (!portalRef.current.contains(e.target as Node))
|
||||
closePortal()
|
||||
}
|
||||
document.addEventListener('mousedown', onMouseDown, false)
|
||||
return () => document.removeEventListener('mousedown', onMouseDown, false)
|
||||
}, [open, closePortal])
|
||||
|
||||
if (!open || !containerEl)
|
||||
return null
|
||||
|
||||
return createPortal(
|
||||
<div className='h-0 w-0'>
|
||||
<div
|
||||
ref={(node) => {
|
||||
portalRef.current = node
|
||||
refs.setFloating(node)
|
||||
}}
|
||||
className={cn(
|
||||
useContainer ? '' : 'z-[999999]',
|
||||
'absolute rounded-xl shadow-lg backdrop-blur-sm',
|
||||
className,
|
||||
)}
|
||||
style={{
|
||||
...floatingStyles,
|
||||
visibility: isPositioned ? 'visible' : 'hidden',
|
||||
}}
|
||||
>
|
||||
<div className='w-[261px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur'>
|
||||
{memoryVarInNode.length > 0 && (
|
||||
<>
|
||||
<div className='flex items-center gap-1 pb-1 pt-2.5'>
|
||||
<Divider className='!h-px !w-3 bg-divider-subtle' />
|
||||
<div className='system-2xs-medium-uppercase shrink-0 text-text-tertiary'>{t('workflow.nodes.llm.memory.currentNodeLabel')}</div>
|
||||
<Divider className='!h-px grow bg-divider-subtle' />
|
||||
</div>
|
||||
<div className='p-1'>
|
||||
{memoryVarInNode.map(variable => (
|
||||
<div
|
||||
key={variable.variable}
|
||||
className='flex cursor-pointer items-center gap-1 rounded-md px-3 py-1 hover:bg-state-base-hover'
|
||||
onClick={() => handleSelectVariable(['memory_block', variable.variable])}
|
||||
>
|
||||
<VariableIcon
|
||||
variables={['memory_block', '']}
|
||||
className='text-util-colors-teal-teal-700'
|
||||
/>
|
||||
<div title={variable.memoryVariableName} className='system-sm-medium shrink-0 truncate text-text-secondary'>{variable.memoryVariableName}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{memoryVarInApp.length > 0 && (
|
||||
<>
|
||||
<div className='flex items-center gap-1 pb-1 pt-2.5'>
|
||||
<Divider className='!h-px !w-3 bg-divider-subtle' />
|
||||
<div className='system-2xs-medium-uppercase shrink-0 text-text-tertiary'>{t('workflow.nodes.llm.memory.conversationScopeLabel')}</div>
|
||||
<Divider className='!h-px grow bg-divider-subtle' />
|
||||
</div>
|
||||
<div className='p-1'>
|
||||
{memoryVarInApp.map(variable => (
|
||||
<div
|
||||
key={variable.variable}
|
||||
className='flex cursor-pointer items-center gap-1 rounded-md px-3 py-1 hover:bg-state-base-hover'
|
||||
onClick={() => handleSelectVariable(['memory_block', variable.variable])}
|
||||
>
|
||||
<VariableIcon
|
||||
variables={['memory_block', '']}
|
||||
className='text-util-colors-teal-teal-700'
|
||||
/>
|
||||
<div title={variable.variable} className='system-sm-medium shrink-0 truncate text-text-secondary'>{variable.memoryVariableName}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!memoryVarInNode.length && !memoryVarInApp.length && (
|
||||
<div className='p-2'>
|
||||
<div className='flex flex-col gap-2 rounded-[10px] bg-workflow-process-bg p-4'>
|
||||
<div className='flex h-10 w-10 items-center justify-center rounded-lg border-[0.5px] border-components-card-border bg-components-card-bg shadow-lg backdrop-blur-sm'>
|
||||
<Memory className='h-5 w-5 text-util-colors-teal-teal-700' />
|
||||
</div>
|
||||
<div className='system-sm-medium text-text-secondary'>{t('workflow.nodes.llm.memory.emptyState')}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className='system-xs-medium flex cursor-pointer items-center gap-1 border-t border-divider-subtle px-4 py-2 text-text-accent-light-mode-only' onClick={handleCreate}>
|
||||
<RiAddLine className='h-4 w-4' />
|
||||
<div>{t('workflow.nodes.llm.memory.createButton')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
containerEl,
|
||||
)
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
import {
|
||||
$insertNodes,
|
||||
} from 'lexical'
|
||||
import { $insertNodes } from 'lexical'
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { textToEditorState } from '../utils'
|
||||
import { CustomTextNode } from './custom-text/node'
|
||||
import { CLEAR_HIDE_MENU_TIMEOUT } from './workflow-variable-block'
|
||||
import { MEMORY_POPUP_SHOW_BY_EVENT_EMITTER } from '@/app/components/workflow/nodes/_base/components/prompt/type'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
|
||||
export const PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER = 'PROMPT_EDITOR_UPDATE_VALUE_BY_EVENT_EMITTER'
|
||||
@@ -39,18 +36,6 @@ const UpdateBlock = ({
|
||||
}
|
||||
})
|
||||
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
if (v.type === MEMORY_POPUP_SHOW_BY_EVENT_EMITTER && v.instanceId === instanceId) {
|
||||
editor.focus()
|
||||
editor.update(() => {
|
||||
const textNode = new CustomTextNode('')
|
||||
$insertNodes([textNode])
|
||||
|
||||
editor.dispatchCommand(CLEAR_HIDE_MENU_TIMEOUT, undefined)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -19,27 +19,23 @@ import {
|
||||
DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND,
|
||||
UPDATE_WORKFLOW_NODES_MAP,
|
||||
} from './index'
|
||||
import { isConversationVar, isENV, isGlobalVar, isMemoryVariable, isRagVariableVar, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import { isConversationVar, isENV, isGlobalVar, isRagVariableVar, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { isExceptionVariable } from '@/app/components/workflow/utils'
|
||||
import VarFullPathPanel from '@/app/components/workflow/nodes/_base/components/variable/var-full-path-panel'
|
||||
import { Type } from '@/app/components/workflow/nodes/llm/types'
|
||||
import type {
|
||||
NodeOutPutVar,
|
||||
ValueSelector,
|
||||
} from '@/app/components/workflow/types'
|
||||
import type { ValueSelector, Var } from '@/app/components/workflow/types'
|
||||
import {
|
||||
VariableLabelInEditor,
|
||||
} from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { UPDATE_WORKFLOW_VARIABLES_EVENT_EMITTER } from '../../constants'
|
||||
import { usePromptEditorStore } from '../../store/store'
|
||||
|
||||
type WorkflowVariableBlockComponentProps = {
|
||||
nodeKey: string
|
||||
variables: string[]
|
||||
workflowNodesMap: WorkflowNodesMap
|
||||
availableVariables: NodeOutPutVar[]
|
||||
environmentVariables?: Var[]
|
||||
conversationVariables?: Var[]
|
||||
ragVariables?: Var[]
|
||||
getVarType?: (payload: {
|
||||
nodeId: string,
|
||||
valueSelector: ValueSelector,
|
||||
@@ -51,27 +47,12 @@ const WorkflowVariableBlockComponent = ({
|
||||
variables,
|
||||
workflowNodesMap = {},
|
||||
getVarType,
|
||||
availableVariables: initialAvailableVariables,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
ragVariables,
|
||||
}: WorkflowVariableBlockComponentProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [editor] = useLexicalComposerContext()
|
||||
const instanceId = usePromptEditorStore(s => s.instanceId)
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const [availableVariables, setAvailableVariables] = useState<NodeOutPutVar[]>(initialAvailableVariables)
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
if (v?.type === UPDATE_WORKFLOW_VARIABLES_EVENT_EMITTER && instanceId && v.instanceId === instanceId)
|
||||
setAvailableVariables(v.payload)
|
||||
})
|
||||
const environmentVariables = availableVariables?.find(v => v.nodeId === 'env')?.vars || []
|
||||
const conversationVariables = availableVariables?.find(v => v.nodeId === 'conversation')?.vars || []
|
||||
const memoryVariables = conversationVariables?.filter(v => v.variable.startsWith('memory_block.'))
|
||||
const ragVariables = availableVariables?.reduce<any[]>((acc, curr) => {
|
||||
if (curr.nodeId === 'rag')
|
||||
acc.push(...curr.vars)
|
||||
else
|
||||
acc.push(...curr.vars.filter(v => v.isRagVariable))
|
||||
return acc
|
||||
}, [])
|
||||
const [ref, isSelected] = useSelectOrDelete(nodeKey, DELETE_WORKFLOW_VARIABLE_BLOCK_COMMAND)
|
||||
const variablesLength = variables.length
|
||||
const isRagVar = isRagVariableVar(variables)
|
||||
@@ -91,23 +72,21 @@ const WorkflowVariableBlockComponent = ({
|
||||
let variableValid = true
|
||||
const isEnv = isENV(variables)
|
||||
const isChatVar = isConversationVar(variables)
|
||||
const isMemoryVar = isMemoryVariable(variables)
|
||||
const isGlobal = isGlobalVar(variables)
|
||||
if (isGlobal)
|
||||
return true
|
||||
|
||||
if (isEnv) {
|
||||
variableValid
|
||||
= environmentVariables.some(v => v.variable === `${variables?.[0] ?? ''}.${variables?.[1] ?? ''}`)
|
||||
if (environmentVariables)
|
||||
variableValid = environmentVariables.some(v => v.variable === `${variables?.[0] ?? ''}.${variables?.[1] ?? ''}`)
|
||||
}
|
||||
else if (isChatVar) {
|
||||
variableValid = conversationVariables.some(v => v.variable === `${variables?.[0] ?? ''}.${variables?.[1] ?? ''}`)
|
||||
}
|
||||
else if (isMemoryVar) {
|
||||
variableValid = memoryVariables.some(v => v.variable === `${variables?.[0] ?? ''}.${variables?.[1] ?? ''}`)
|
||||
if (conversationVariables)
|
||||
variableValid = conversationVariables.some(v => v.variable === `${variables?.[0] ?? ''}.${variables?.[1] ?? ''}`)
|
||||
}
|
||||
else if (isRagVar) {
|
||||
variableValid = ragVariables.some(v => v.variable === `${variables?.[0] ?? ''}.${variables?.[1] ?? ''}.${variables?.[2] ?? ''}`)
|
||||
if (ragVariables)
|
||||
variableValid = ragVariables.some(v => v.variable === `${variables?.[0] ?? ''}.${variables?.[1] ?? ''}.${variables?.[2] ?? ''}`)
|
||||
}
|
||||
else {
|
||||
variableValid = !!node
|
||||
@@ -155,28 +134,11 @@ const WorkflowVariableBlockComponent = ({
|
||||
})
|
||||
}, [node, reactflow, store])
|
||||
|
||||
const memoriedVariables = useMemo(() => {
|
||||
if (variables[0] === 'memory_block') {
|
||||
const currentMemoryVariable = memoryVariables?.find(v => v.variable === variables.join('.'))
|
||||
|
||||
if (currentMemoryVariable && currentMemoryVariable.memoryVariableName) {
|
||||
return [
|
||||
'memory_block',
|
||||
currentMemoryVariable.memoryVariableName,
|
||||
]
|
||||
}
|
||||
|
||||
return variables
|
||||
}
|
||||
|
||||
return variables
|
||||
}, [memoryVariables, variables])
|
||||
|
||||
const Item = (
|
||||
<VariableLabelInEditor
|
||||
nodeType={node?.type}
|
||||
nodeTitle={node?.title}
|
||||
variables={memoriedVariables}
|
||||
variables={variables}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleVariableJump()
|
||||
@@ -190,7 +152,7 @@ const WorkflowVariableBlockComponent = ({
|
||||
)
|
||||
|
||||
if (!node)
|
||||
return <div>{Item}</div>
|
||||
return Item
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
@@ -198,10 +160,10 @@ const WorkflowVariableBlockComponent = ({
|
||||
popupContent={
|
||||
<VarFullPathPanel
|
||||
nodeName={node.title}
|
||||
path={memoriedVariables.slice(1)}
|
||||
path={variables.slice(1)}
|
||||
varType={getVarType ? getVarType({
|
||||
nodeId: memoriedVariables[0],
|
||||
valueSelector: memoriedVariables,
|
||||
nodeId: variables[0],
|
||||
valueSelector: variables,
|
||||
}) : Type.string}
|
||||
nodeType={node?.type}
|
||||
/>}
|
||||
|
||||
@@ -32,7 +32,6 @@ const WorkflowVariableBlock = memo(({
|
||||
onInsert,
|
||||
onDelete,
|
||||
getVarType,
|
||||
variables: originalVariables,
|
||||
}: WorkflowVariableBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
@@ -51,7 +50,7 @@ const WorkflowVariableBlock = memo(({
|
||||
INSERT_WORKFLOW_VARIABLE_BLOCK_COMMAND,
|
||||
(variables: string[]) => {
|
||||
editor.dispatchCommand(CLEAR_HIDE_MENU_TIMEOUT, undefined)
|
||||
const workflowVariableBlockNode = $createWorkflowVariableBlockNode(variables, workflowNodesMap, getVarType, originalVariables || [])
|
||||
const workflowVariableBlockNode = $createWorkflowVariableBlockNode(variables, workflowNodesMap, getVarType)
|
||||
|
||||
$insertNodes([workflowVariableBlockNode])
|
||||
if (onInsert)
|
||||
|
||||
@@ -3,7 +3,7 @@ import { DecoratorNode } from 'lexical'
|
||||
import type { WorkflowVariableBlockType } from '../../types'
|
||||
import WorkflowVariableBlockComponent from './component'
|
||||
import type { GetVarType } from '../../types'
|
||||
import type { NodeOutPutVar } from '@/app/components/workflow/types'
|
||||
import type { Var } from '@/app/components/workflow/types'
|
||||
|
||||
export type WorkflowNodesMap = WorkflowVariableBlockType['workflowNodesMap']
|
||||
|
||||
@@ -11,40 +11,40 @@ export type SerializedNode = SerializedLexicalNode & {
|
||||
variables: string[]
|
||||
workflowNodesMap: WorkflowNodesMap
|
||||
getVarType?: GetVarType
|
||||
availableVariables?: NodeOutPutVar[]
|
||||
environmentVariables?: Var[]
|
||||
conversationVariables?: Var[]
|
||||
ragVariables?: Var[]
|
||||
}
|
||||
|
||||
export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element> {
|
||||
__variables: string[]
|
||||
__workflowNodesMap: WorkflowNodesMap
|
||||
__getVarType?: GetVarType
|
||||
__availableVariables?: NodeOutPutVar[]
|
||||
__environmentVariables?: Var[]
|
||||
__conversationVariables?: Var[]
|
||||
__ragVariables?: Var[]
|
||||
|
||||
static getType(): string {
|
||||
return 'workflow-variable-block'
|
||||
}
|
||||
|
||||
static clone(node: WorkflowVariableBlockNode): WorkflowVariableBlockNode {
|
||||
return new WorkflowVariableBlockNode(node.__variables, node.__workflowNodesMap, node.__getVarType, node.__key, node.__availableVariables)
|
||||
return new WorkflowVariableBlockNode(node.__variables, node.__workflowNodesMap, node.__getVarType, node.__key, node.__environmentVariables, node.__conversationVariables, node.__ragVariables)
|
||||
}
|
||||
|
||||
isInline(): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
constructor(
|
||||
variables: string[],
|
||||
workflowNodesMap: WorkflowNodesMap,
|
||||
getVarType: any,
|
||||
key?: NodeKey,
|
||||
availableVariables?: NodeOutPutVar[],
|
||||
) {
|
||||
constructor(variables: string[], workflowNodesMap: WorkflowNodesMap, getVarType: any, key?: NodeKey, environmentVariables?: Var[], conversationVariables?: Var[], ragVariables?: Var[]) {
|
||||
super(key)
|
||||
|
||||
this.__variables = variables
|
||||
this.__workflowNodesMap = workflowNodesMap
|
||||
this.__getVarType = getVarType
|
||||
this.__availableVariables = availableVariables
|
||||
this.__environmentVariables = environmentVariables
|
||||
this.__conversationVariables = conversationVariables
|
||||
this.__ragVariables = ragVariables
|
||||
}
|
||||
|
||||
createDOM(): HTMLElement {
|
||||
@@ -64,13 +64,15 @@ export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element>
|
||||
variables={this.__variables}
|
||||
workflowNodesMap={this.__workflowNodesMap}
|
||||
getVarType={this.__getVarType!}
|
||||
availableVariables={this.__availableVariables || []}
|
||||
environmentVariables={this.__environmentVariables}
|
||||
conversationVariables={this.__conversationVariables}
|
||||
ragVariables={this.__ragVariables}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
static importJSON(serializedNode: SerializedNode): WorkflowVariableBlockNode {
|
||||
const node = $createWorkflowVariableBlockNode(serializedNode.variables, serializedNode.workflowNodesMap, serializedNode.getVarType, serializedNode.availableVariables)
|
||||
const node = $createWorkflowVariableBlockNode(serializedNode.variables, serializedNode.workflowNodesMap, serializedNode.getVarType, serializedNode.environmentVariables, serializedNode.conversationVariables, serializedNode.ragVariables)
|
||||
|
||||
return node
|
||||
}
|
||||
@@ -82,7 +84,9 @@ export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element>
|
||||
variables: this.getVariables(),
|
||||
workflowNodesMap: this.getWorkflowNodesMap(),
|
||||
getVarType: this.getVarType(),
|
||||
availableVariables: this.getAvailableVariables(),
|
||||
environmentVariables: this.getEnvironmentVariables(),
|
||||
conversationVariables: this.getConversationVariables(),
|
||||
ragVariables: this.getRagVariables(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,17 +105,27 @@ export class WorkflowVariableBlockNode extends DecoratorNode<React.JSX.Element>
|
||||
return self.__getVarType
|
||||
}
|
||||
|
||||
getAvailableVariables(): NodeOutPutVar[] {
|
||||
getEnvironmentVariables(): any {
|
||||
const self = this.getLatest()
|
||||
return self.__availableVariables || []
|
||||
return self.__environmentVariables
|
||||
}
|
||||
|
||||
getConversationVariables(): any {
|
||||
const self = this.getLatest()
|
||||
return self.__conversationVariables
|
||||
}
|
||||
|
||||
getRagVariables(): any {
|
||||
const self = this.getLatest()
|
||||
return self.__ragVariables
|
||||
}
|
||||
|
||||
getTextContent(): string {
|
||||
return `{{#${this.getVariables().join('.')}#}}`
|
||||
}
|
||||
}
|
||||
export function $createWorkflowVariableBlockNode(variables: string[], workflowNodesMap: WorkflowNodesMap, getVarType?: GetVarType, availableVariables?: NodeOutPutVar[]): WorkflowVariableBlockNode {
|
||||
return new WorkflowVariableBlockNode(variables, workflowNodesMap, getVarType, undefined, availableVariables)
|
||||
export function $createWorkflowVariableBlockNode(variables: string[], workflowNodesMap: WorkflowNodesMap, getVarType?: GetVarType, environmentVariables?: Var[], conversationVariables?: Var[], ragVariables?: Var[]): WorkflowVariableBlockNode {
|
||||
return new WorkflowVariableBlockNode(variables, workflowNodesMap, getVarType, undefined, environmentVariables, conversationVariables, ragVariables)
|
||||
}
|
||||
|
||||
export function $isWorkflowVariableBlockNode(
|
||||
|
||||
@@ -21,6 +21,13 @@ const WorkflowVariableBlockReplacementBlock = ({
|
||||
variables,
|
||||
}: WorkflowVariableBlockType) => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
const ragVariables = variables?.reduce<any[]>((acc, curr) => {
|
||||
if (curr.nodeId === 'rag')
|
||||
acc.push(...curr.vars)
|
||||
else
|
||||
acc.push(...curr.vars.filter(v => v.isRagVariable))
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!editor.hasNodes([WorkflowVariableBlockNode]))
|
||||
@@ -32,7 +39,7 @@ const WorkflowVariableBlockReplacementBlock = ({
|
||||
onInsert()
|
||||
|
||||
const nodePathString = textNode.getTextContent().slice(3, -3)
|
||||
return $applyNodeReplacement($createWorkflowVariableBlockNode(nodePathString.split('.'), workflowNodesMap, getVarType, variables))
|
||||
return $applyNodeReplacement($createWorkflowVariableBlockNode(nodePathString.split('.'), workflowNodesMap, getVarType, variables?.find(o => o.nodeId === 'env')?.vars || [], variables?.find(o => o.nodeId === 'conversation')?.vars || [], ragVariables))
|
||||
}, [onInsert, workflowNodesMap, getVarType, variables])
|
||||
|
||||
const getMatch = useCallback((text: string) => {
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { createContext, useRef } from 'react'
|
||||
import { createPromptEditorStore } from './store'
|
||||
|
||||
type PromptEditorStoreApi = ReturnType<typeof createPromptEditorStore>
|
||||
|
||||
type PromptEditorContextType = PromptEditorStoreApi | undefined
|
||||
|
||||
export const PromptEditorContext = createContext<PromptEditorContextType>(undefined)
|
||||
|
||||
type PromptEditorProviderProps = {
|
||||
instanceId?: string
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const PromptEditorProvider = ({
|
||||
instanceId,
|
||||
children,
|
||||
}: PromptEditorProviderProps) => {
|
||||
const storeRef = useRef<PromptEditorStoreApi>(undefined)
|
||||
|
||||
if (!storeRef.current)
|
||||
storeRef.current = createPromptEditorStore({ instanceId })
|
||||
|
||||
return (
|
||||
<PromptEditorContext.Provider value={storeRef.current!}>
|
||||
{children}
|
||||
</PromptEditorContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default PromptEditorProvider
|
||||
@@ -1,24 +0,0 @@
|
||||
import { useContext } from 'react'
|
||||
import { createStore, useStore } from 'zustand'
|
||||
import { PromptEditorContext } from './provider'
|
||||
|
||||
type PromptEditorStoreProps = {
|
||||
instanceId?: string
|
||||
}
|
||||
type PromptEditorStore = {
|
||||
instanceId?: string
|
||||
}
|
||||
|
||||
export const createPromptEditorStore = ({ instanceId }: PromptEditorStoreProps) => {
|
||||
return createStore<PromptEditorStore>(() => ({
|
||||
instanceId,
|
||||
}))
|
||||
}
|
||||
|
||||
export const usePromptEditorStore = <T>(selector: (state: PromptEditorStore) => T): T => {
|
||||
const store = useContext(PromptEditorContext)
|
||||
if (!store)
|
||||
throw new Error('Missing PromptEditorContext.Provider in the tree')
|
||||
|
||||
return useStore(store, selector)
|
||||
}
|
||||
@@ -71,7 +71,6 @@ export type WorkflowVariableBlockType = {
|
||||
getVarType?: GetVarType
|
||||
showManageInputField?: boolean
|
||||
onManageInputField?: () => void
|
||||
isMemorySupported?: boolean
|
||||
}
|
||||
|
||||
export type MenuTextMatch = {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiDeleteBinLine } from '@remixicon/react'
|
||||
import { PlusIcon, XMarkIcon } from '@heroicons/react/20/solid'
|
||||
import useSWR, { useSWRConfig } from 'swr'
|
||||
import useSWR from 'swr'
|
||||
import SecretKeyGenerateModal from './secret-key-generate'
|
||||
import s from './style.module.css'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
@@ -15,7 +15,6 @@ import CopyFeedback from '@/app/components/base/copy-feedback'
|
||||
import {
|
||||
createApikey as createAppApikey,
|
||||
delApikey as delAppApikey,
|
||||
fetchApiKeysList as fetchAppApiKeysList,
|
||||
} from '@/service/apps'
|
||||
import {
|
||||
createApikey as createDatasetApikey,
|
||||
@@ -27,6 +26,7 @@ import Loading from '@/app/components/base/loading'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import useTimestamp from '@/hooks/use-timestamp'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useAppApiKeys, useInvalidateAppApiKeys } from '@/service/use-apps'
|
||||
|
||||
type ISecretKeyModalProps = {
|
||||
isShow: boolean
|
||||
@@ -45,12 +45,14 @@ const SecretKeyModal = ({
|
||||
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
|
||||
const [isVisible, setVisible] = useState(false)
|
||||
const [newKey, setNewKey] = useState<CreateApiKeyResponse | undefined>(undefined)
|
||||
const { mutate } = useSWRConfig()
|
||||
const commonParams = appId
|
||||
? { url: `/apps/${appId}/api-keys`, params: {} }
|
||||
: { url: '/datasets/api-keys', params: {} }
|
||||
const fetchApiKeysList = appId ? fetchAppApiKeysList : fetchDatasetApiKeysList
|
||||
const { data: apiKeysList } = useSWR(commonParams, fetchApiKeysList)
|
||||
const invalidateAppApiKeys = useInvalidateAppApiKeys()
|
||||
const { data: appApiKeys, isLoading: isAppApiKeysLoading } = useAppApiKeys(appId, { enabled: !!appId && isShow })
|
||||
const { data: datasetApiKeys, isLoading: isDatasetApiKeysLoading, mutate: mutateDatasetApiKeys } = useSWR(
|
||||
!appId && isShow ? { url: '/datasets/api-keys', params: {} } : null,
|
||||
fetchDatasetApiKeysList,
|
||||
)
|
||||
const apiKeysList = appId ? appApiKeys : datasetApiKeys
|
||||
const isApiKeysLoading = appId ? isAppApiKeysLoading : isDatasetApiKeysLoading
|
||||
|
||||
const [delKeyID, setDelKeyId] = useState('')
|
||||
|
||||
@@ -64,7 +66,10 @@ const SecretKeyModal = ({
|
||||
? { url: `/apps/${appId}/api-keys/${delKeyID}`, params: {} }
|
||||
: { url: `/datasets/api-keys/${delKeyID}`, params: {} }
|
||||
await delApikey(params)
|
||||
mutate(commonParams)
|
||||
if (appId)
|
||||
invalidateAppApiKeys(appId)
|
||||
else
|
||||
mutateDatasetApiKeys()
|
||||
}
|
||||
|
||||
const onCreate = async () => {
|
||||
@@ -75,7 +80,10 @@ const SecretKeyModal = ({
|
||||
const res = await createApikey(params)
|
||||
setVisible(true)
|
||||
setNewKey(res)
|
||||
mutate(commonParams)
|
||||
if (appId)
|
||||
invalidateAppApiKeys(appId)
|
||||
else
|
||||
mutateDatasetApiKeys()
|
||||
}
|
||||
|
||||
const generateToken = (token: string) => {
|
||||
@@ -88,7 +96,7 @@ const SecretKeyModal = ({
|
||||
<XMarkIcon className="h-6 w-6 cursor-pointer text-text-tertiary" onClick={onClose} />
|
||||
</div>
|
||||
<p className='mt-1 shrink-0 text-[13px] font-normal leading-5 text-text-tertiary'>{t('appApi.apiKeyModal.apiSecretKeyTips')}</p>
|
||||
{!apiKeysList && <div className='mt-4'><Loading /></div>}
|
||||
{isApiKeysLoading && <div className='mt-4'><Loading /></div>}
|
||||
{
|
||||
!!apiKeysList?.data?.length && (
|
||||
<div className='mt-4 flex grow flex-col overflow-hidden'>
|
||||
|
||||
@@ -214,8 +214,12 @@ export const searchAnything = async (
|
||||
actionItem?: ActionItem,
|
||||
dynamicActions?: Record<string, ActionItem>,
|
||||
): Promise<SearchResult[]> => {
|
||||
const trimmedQuery = query.trim()
|
||||
|
||||
if (actionItem) {
|
||||
const searchTerm = query.replace(actionItem.key, '').replace(actionItem.shortcut, '').trim()
|
||||
const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
const prefixPattern = new RegExp(`^(${escapeRegExp(actionItem.key)}|${escapeRegExp(actionItem.shortcut)})\\s*`)
|
||||
const searchTerm = trimmedQuery.replace(prefixPattern, '').trim()
|
||||
try {
|
||||
return await actionItem.search(query, searchTerm, locale)
|
||||
}
|
||||
@@ -225,10 +229,12 @@ export const searchAnything = async (
|
||||
}
|
||||
}
|
||||
|
||||
if (query.startsWith('@') || query.startsWith('/'))
|
||||
if (trimmedQuery.startsWith('@') || trimmedQuery.startsWith('/'))
|
||||
return []
|
||||
|
||||
const globalSearchActions = Object.values(dynamicActions || Actions)
|
||||
// Exclude slash commands from general search results
|
||||
.filter(action => action.key !== '/')
|
||||
|
||||
// Use Promise.allSettled to handle partial failures gracefully
|
||||
const searchPromises = globalSearchActions.map(async (action) => {
|
||||
|
||||
@@ -177,31 +177,42 @@ const GotoAnything: FC<Props> = ({
|
||||
}
|
||||
}, [router])
|
||||
|
||||
const dedupedResults = useMemo(() => {
|
||||
const seen = new Set<string>()
|
||||
return searchResults.filter((result) => {
|
||||
const key = `${result.type}-${result.id}`
|
||||
if (seen.has(key))
|
||||
return false
|
||||
seen.add(key)
|
||||
return true
|
||||
})
|
||||
}, [searchResults])
|
||||
|
||||
// Group results by type
|
||||
const groupedResults = useMemo(() => searchResults.reduce((acc, result) => {
|
||||
const groupedResults = useMemo(() => dedupedResults.reduce((acc, result) => {
|
||||
if (!acc[result.type])
|
||||
acc[result.type] = []
|
||||
|
||||
acc[result.type].push(result)
|
||||
return acc
|
||||
}, {} as { [key: string]: SearchResult[] }),
|
||||
[searchResults])
|
||||
[dedupedResults])
|
||||
|
||||
useEffect(() => {
|
||||
if (isCommandsMode)
|
||||
return
|
||||
|
||||
if (!searchResults.length)
|
||||
if (!dedupedResults.length)
|
||||
return
|
||||
|
||||
const currentValueExists = searchResults.some(result => `${result.type}-${result.id}` === cmdVal)
|
||||
const currentValueExists = dedupedResults.some(result => `${result.type}-${result.id}` === cmdVal)
|
||||
|
||||
if (!currentValueExists)
|
||||
setCmdVal(`${searchResults[0].type}-${searchResults[0].id}`)
|
||||
}, [isCommandsMode, searchResults, cmdVal])
|
||||
setCmdVal(`${dedupedResults[0].type}-${dedupedResults[0].id}`)
|
||||
}, [isCommandsMode, dedupedResults, cmdVal])
|
||||
|
||||
const emptyResult = useMemo(() => {
|
||||
if (searchResults.length || !searchQuery.trim() || isLoading || isCommandsMode)
|
||||
if (dedupedResults.length || !searchQuery.trim() || isLoading || isCommandsMode)
|
||||
return null
|
||||
|
||||
const isCommandSearch = searchMode !== 'general'
|
||||
@@ -246,7 +257,7 @@ const GotoAnything: FC<Props> = ({
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [searchResults, searchQuery, Actions, searchMode, isLoading, isError, isCommandsMode])
|
||||
}, [dedupedResults, searchQuery, Actions, searchMode, isLoading, isError, isCommandsMode])
|
||||
|
||||
const defaultUI = useMemo(() => {
|
||||
if (searchQuery.trim())
|
||||
@@ -430,14 +441,14 @@ const GotoAnything: FC<Props> = ({
|
||||
{/* Always show footer to prevent height jumping */}
|
||||
<div className='border-t border-divider-subtle bg-components-panel-bg-blur px-4 py-2 text-xs text-text-tertiary'>
|
||||
<div className='flex min-h-[16px] items-center justify-between'>
|
||||
{(!!searchResults.length || isError) ? (
|
||||
{(!!dedupedResults.length || isError) ? (
|
||||
<>
|
||||
<span>
|
||||
{isError ? (
|
||||
<span className='text-red-500'>{t('app.gotoAnything.someServicesUnavailable')}</span>
|
||||
) : (
|
||||
<>
|
||||
{t('app.gotoAnything.resultCount', { count: searchResults.length })}
|
||||
{t('app.gotoAnything.resultCount', { count: dedupedResults.length })}
|
||||
{searchMode !== 'general' && (
|
||||
<span className='ml-2 opacity-60'>
|
||||
{t('app.gotoAnything.inScope', { scope: searchMode.replace('@', '') })}
|
||||
|
||||
@@ -129,8 +129,8 @@ export const useProviderCredentialsAndLoadBalancing = (
|
||||
// as ([Record<string, string | boolean | undefined> | undefined, ModelLoadBalancingConfig | undefined])
|
||||
}
|
||||
|
||||
export const useModelList = (type: ModelTypeEnum, enabled?: boolean) => {
|
||||
const { data, mutate, isLoading } = useSWR(enabled ? `/workspaces/current/models/model-types/${type}` : null, fetchModelList)
|
||||
export const useModelList = (type: ModelTypeEnum) => {
|
||||
const { data, mutate, isLoading } = useSWR(`/workspaces/current/models/model-types/${type}`, fetchModelList)
|
||||
|
||||
return {
|
||||
data: data?.data || [],
|
||||
@@ -139,8 +139,8 @@ export const useModelList = (type: ModelTypeEnum, enabled?: boolean) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const useDefaultModel = (type: ModelTypeEnum, enabled?: boolean) => {
|
||||
const { data, mutate, isLoading } = useSWR(enabled ? `/workspaces/current/default-model?model_type=${type}` : null, fetchDefaultModal)
|
||||
export const useDefaultModel = (type: ModelTypeEnum) => {
|
||||
const { data, mutate, isLoading } = useSWR(`/workspaces/current/default-model?model_type=${type}`, fetchDefaultModal)
|
||||
|
||||
return {
|
||||
data: data?.data,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useParams } from 'next/navigation'
|
||||
import useSWRInfinite from 'swr/infinite'
|
||||
import { flatten } from 'lodash-es'
|
||||
import { produce } from 'immer'
|
||||
import {
|
||||
@@ -12,33 +11,13 @@ import {
|
||||
} from '@remixicon/react'
|
||||
import Nav from '../nav'
|
||||
import type { NavItem } from '../nav/nav-selector'
|
||||
import { fetchAppList } from '@/service/apps'
|
||||
import CreateAppTemplateDialog from '@/app/components/app/create-app-dialog'
|
||||
import CreateAppModal from '@/app/components/app/create-app-modal'
|
||||
import CreateFromDSLModal from '@/app/components/app/create-from-dsl-modal'
|
||||
import type { AppListResponse } from '@/models/app'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useStore as useAppStore } from '@/app/components/app/store'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
|
||||
const getKey = (
|
||||
pageIndex: number,
|
||||
previousPageData: AppListResponse,
|
||||
activeTab: string,
|
||||
keywords: string,
|
||||
) => {
|
||||
if (!pageIndex || previousPageData.has_more) {
|
||||
const params: any = { url: 'apps', params: { page: pageIndex + 1, limit: 30, name: keywords } }
|
||||
|
||||
if (activeTab !== 'all')
|
||||
params.params.mode = activeTab
|
||||
else
|
||||
delete params.params.mode
|
||||
|
||||
return params
|
||||
}
|
||||
return null
|
||||
}
|
||||
import { useInfiniteAppList } from '@/service/use-apps'
|
||||
|
||||
const AppNav = () => {
|
||||
const { t } = useTranslation()
|
||||
@@ -50,17 +29,21 @@ const AppNav = () => {
|
||||
const [showCreateFromDSLModal, setShowCreateFromDSLModal] = useState(false)
|
||||
const [navItems, setNavItems] = useState<NavItem[]>([])
|
||||
|
||||
const { data: appsData, setSize, mutate } = useSWRInfinite(
|
||||
appId
|
||||
? (pageIndex: number, previousPageData: AppListResponse) => getKey(pageIndex, previousPageData, 'all', '')
|
||||
: () => null,
|
||||
fetchAppList,
|
||||
{ revalidateFirstPage: false },
|
||||
)
|
||||
const {
|
||||
data: appsData,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
refetch,
|
||||
} = useInfiniteAppList({
|
||||
page: 1,
|
||||
limit: 30,
|
||||
name: '',
|
||||
}, { enabled: !!appId })
|
||||
|
||||
const handleLoadMore = useCallback(() => {
|
||||
setSize(size => size + 1)
|
||||
}, [setSize])
|
||||
if (hasNextPage)
|
||||
fetchNextPage()
|
||||
}, [fetchNextPage, hasNextPage])
|
||||
|
||||
const openModal = (state: string) => {
|
||||
if (state === 'blank')
|
||||
@@ -73,7 +56,7 @@ const AppNav = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (appsData) {
|
||||
const appItems = flatten(appsData?.map(appData => appData.data))
|
||||
const appItems = flatten((appsData.pages ?? []).map(appData => appData.data))
|
||||
const navItems = appItems.map((app) => {
|
||||
const link = ((isCurrentWorkspaceEditor, app) => {
|
||||
if (!isCurrentWorkspaceEditor) {
|
||||
@@ -132,17 +115,17 @@ const AppNav = () => {
|
||||
<CreateAppModal
|
||||
show={showNewAppDialog}
|
||||
onClose={() => setShowNewAppDialog(false)}
|
||||
onSuccess={() => mutate()}
|
||||
onSuccess={() => refetch()}
|
||||
/>
|
||||
<CreateAppTemplateDialog
|
||||
show={showNewAppTemplateDialog}
|
||||
onClose={() => setShowNewAppTemplateDialog(false)}
|
||||
onSuccess={() => mutate()}
|
||||
onSuccess={() => refetch()}
|
||||
/>
|
||||
<CreateFromDSLModal
|
||||
show={showCreateFromDSLModal}
|
||||
onClose={() => setShowCreateFromDSLModal(false)}
|
||||
onSuccess={() => mutate()}
|
||||
onSuccess={() => refetch()}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -15,32 +15,10 @@ import type {
|
||||
OffsetOptions,
|
||||
Placement,
|
||||
} from '@floating-ui/react'
|
||||
import useSWRInfinite from 'swr/infinite'
|
||||
import { fetchAppList } from '@/service/apps'
|
||||
import type { AppListResponse } from '@/models/app'
|
||||
import { useInfiniteAppList } from '@/service/use-apps'
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
const getKey = (
|
||||
pageIndex: number,
|
||||
previousPageData: AppListResponse,
|
||||
searchText: string,
|
||||
) => {
|
||||
if (pageIndex === 0 || (previousPageData && previousPageData.has_more)) {
|
||||
const params: any = {
|
||||
url: 'apps',
|
||||
params: {
|
||||
page: pageIndex + 1,
|
||||
limit: PAGE_SIZE,
|
||||
name: searchText,
|
||||
},
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
type Props = {
|
||||
value?: {
|
||||
app_id: string
|
||||
@@ -72,30 +50,32 @@ const AppSelector: FC<Props> = ({
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const [isLoadingMore, setIsLoadingMore] = useState(false)
|
||||
|
||||
const { data, isLoading, setSize } = useSWRInfinite(
|
||||
(pageIndex: number, previousPageData: AppListResponse) => getKey(pageIndex, previousPageData, searchText),
|
||||
fetchAppList,
|
||||
{
|
||||
revalidateFirstPage: true,
|
||||
shouldRetryOnError: false,
|
||||
dedupingInterval: 500,
|
||||
errorRetryCount: 3,
|
||||
},
|
||||
)
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
isFetchingNextPage,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
} = useInfiniteAppList({
|
||||
page: 1,
|
||||
limit: PAGE_SIZE,
|
||||
name: searchText,
|
||||
})
|
||||
|
||||
const pages = data?.pages ?? []
|
||||
const displayedApps = useMemo(() => {
|
||||
if (!data) return []
|
||||
return data.flatMap(({ data: apps }) => apps)
|
||||
}, [data])
|
||||
if (!pages.length) return []
|
||||
return pages.flatMap(({ data: apps }) => apps)
|
||||
}, [pages])
|
||||
|
||||
const hasMore = data?.at(-1)?.has_more ?? true
|
||||
const hasMore = hasNextPage ?? true
|
||||
|
||||
const handleLoadMore = useCallback(async () => {
|
||||
if (isLoadingMore || !hasMore) return
|
||||
if (isLoadingMore || isFetchingNextPage || !hasMore) return
|
||||
|
||||
setIsLoadingMore(true)
|
||||
try {
|
||||
await setSize((size: number) => size + 1)
|
||||
await fetchNextPage()
|
||||
}
|
||||
finally {
|
||||
// Add a small delay to ensure state updates are complete
|
||||
@@ -103,7 +83,7 @@ const AppSelector: FC<Props> = ({
|
||||
setIsLoadingMore(false)
|
||||
}, 300)
|
||||
}
|
||||
}, [isLoadingMore, hasMore, setSize])
|
||||
}, [isLoadingMore, isFetchingNextPage, hasMore, fetchNextPage])
|
||||
|
||||
const handleTriggerClick = () => {
|
||||
if (disabled) return
|
||||
@@ -185,7 +165,7 @@ const AppSelector: FC<Props> = ({
|
||||
onSelect={handleSelectApp}
|
||||
scope={scope || 'all'}
|
||||
apps={displayedApps}
|
||||
isLoading={isLoading || isLoadingMore}
|
||||
isLoading={isLoading || isLoadingMore || isFetchingNextPage}
|
||||
hasMore={hasMore}
|
||||
onLoadMore={handleLoadMore}
|
||||
searchText={searchText}
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
useWorkflowStartRun,
|
||||
} from '../hooks'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { useFormatMemoryVariables } from '@/app/components/workflow/hooks'
|
||||
|
||||
type WorkflowMainProps = Pick<WorkflowProps, 'nodes' | 'edges' | 'viewport'>
|
||||
const WorkflowMain = ({
|
||||
@@ -29,14 +28,12 @@ const WorkflowMain = ({
|
||||
}: WorkflowMainProps) => {
|
||||
const featuresStore = useFeaturesStore()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { formatMemoryVariables } = useFormatMemoryVariables()
|
||||
|
||||
const handleWorkflowDataUpdate = useCallback((payload: any) => {
|
||||
const {
|
||||
features,
|
||||
conversation_variables,
|
||||
environment_variables,
|
||||
memory_blocks,
|
||||
} = payload
|
||||
if (features && featuresStore) {
|
||||
const { setFeatures } = featuresStore.getState()
|
||||
@@ -51,11 +48,7 @@ const WorkflowMain = ({
|
||||
const { setEnvironmentVariables } = workflowStore.getState()
|
||||
setEnvironmentVariables(environment_variables)
|
||||
}
|
||||
if (memory_blocks) {
|
||||
const { setMemoryVariables } = workflowStore.getState()
|
||||
setMemoryVariables(formatMemoryVariables(memory_blocks))
|
||||
}
|
||||
}, [featuresStore, workflowStore, formatMemoryVariables])
|
||||
}, [featuresStore, workflowStore])
|
||||
|
||||
const {
|
||||
doSyncWorkflowDraft,
|
||||
|
||||
@@ -27,7 +27,6 @@ export const useNodesSyncDraft = () => {
|
||||
const {
|
||||
appId,
|
||||
conversationVariables,
|
||||
memoryVariables,
|
||||
environmentVariables,
|
||||
syncWorkflowDraftHash,
|
||||
isWorkflowDataLoaded,
|
||||
@@ -75,13 +74,6 @@ export const useNodesSyncDraft = () => {
|
||||
},
|
||||
environment_variables: environmentVariables,
|
||||
conversation_variables: conversationVariables,
|
||||
memory_blocks: memoryVariables.map(({ value_type, scope, more, node_id, ...rest }) => {
|
||||
return {
|
||||
...rest,
|
||||
node_id: scope === 'node' ? node_id : undefined,
|
||||
scope,
|
||||
}
|
||||
}),
|
||||
hash: syncWorkflowDraftHash,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
import type { FetchWorkflowDraftResponse } from '@/types/workflow'
|
||||
import { useWorkflowConfig } from '@/service/use-workflow'
|
||||
import type { FileUploadConfigResponse } from '@/models/common'
|
||||
import { useFormatMemoryVariables } from '@/app/components/workflow/hooks'
|
||||
import type { Edge, Node } from '@/app/components/workflow/types'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
@@ -55,7 +54,6 @@ export const useWorkflowInit = () => {
|
||||
data: fileUploadConfigResponse,
|
||||
isLoading: isFileUploadConfigLoading,
|
||||
} = useWorkflowConfig('/files/upload', handleUpdateWorkflowFileUploadConfig)
|
||||
const { formatMemoryVariables } = useFormatMemoryVariables()
|
||||
|
||||
const handleGetInitialWorkflowData = useCallback(async () => {
|
||||
try {
|
||||
@@ -68,7 +66,6 @@ export const useWorkflowInit = () => {
|
||||
}, {} as Record<string, string>),
|
||||
environmentVariables: res.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [],
|
||||
conversationVariables: res.conversation_variables || [],
|
||||
memoryVariables: formatMemoryVariables((res.memory_blocks || [])),
|
||||
isWorkflowDataLoaded: true,
|
||||
})
|
||||
setSyncWorkflowDraftHash(res.hash)
|
||||
|
||||
@@ -3,12 +3,10 @@ import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { fetchWorkflowDraft } from '@/service/workflow'
|
||||
import type { WorkflowDataUpdater } from '@/app/components/workflow/types'
|
||||
import { useWorkflowUpdate } from '@/app/components/workflow/hooks'
|
||||
import { useFormatMemoryVariables } from '@/app/components/workflow/hooks'
|
||||
|
||||
export const useWorkflowRefreshDraft = () => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { handleUpdateWorkflowCanvas } = useWorkflowUpdate()
|
||||
const { formatMemoryVariables } = useFormatMemoryVariables()
|
||||
|
||||
const handleRefreshWorkflowDraft = useCallback(() => {
|
||||
const {
|
||||
@@ -18,7 +16,6 @@ export const useWorkflowRefreshDraft = () => {
|
||||
setEnvironmentVariables,
|
||||
setEnvSecrets,
|
||||
setConversationVariables,
|
||||
setMemoryVariables,
|
||||
setIsWorkflowDataLoaded,
|
||||
isWorkflowDataLoaded,
|
||||
debouncedSyncWorkflowDraft,
|
||||
@@ -47,7 +44,6 @@ export const useWorkflowRefreshDraft = () => {
|
||||
}, {} as Record<string, string>))
|
||||
setEnvironmentVariables(response.environment_variables?.map(env => env.value_type === 'secret' ? { ...env, value: '[__HIDDEN__]' } : env) || [])
|
||||
setConversationVariables(response.conversation_variables || [])
|
||||
setMemoryVariables(formatMemoryVariables((response.memory_blocks || [])))
|
||||
setIsWorkflowDataLoaded(true)
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -57,7 +53,7 @@ export const useWorkflowRefreshDraft = () => {
|
||||
.finally(() => {
|
||||
setIsSyncingWorkflowDraft(false)
|
||||
})
|
||||
}, [handleUpdateWorkflowCanvas, workflowStore, formatMemoryVariables])
|
||||
}, [handleUpdateWorkflowCanvas, workflowStore])
|
||||
|
||||
return {
|
||||
handleRefreshWorkflowDraft,
|
||||
|
||||
@@ -603,7 +603,6 @@ export const useWorkflowRun = () => {
|
||||
baseSseOptions.onTTSEnd,
|
||||
baseSseOptions.onTextReplace,
|
||||
baseSseOptions.onAgentLog,
|
||||
baseSseOptions.onMemoryUpdate,
|
||||
baseSseOptions.onDataSourceNodeProcessing,
|
||||
baseSseOptions.onDataSourceNodeCompleted,
|
||||
baseSseOptions.onDataSourceNodeError,
|
||||
|
||||
@@ -22,6 +22,5 @@ export * from './use-DSL'
|
||||
export * from './use-inspect-vars-crud'
|
||||
export * from './use-set-workflow-vars-with-value'
|
||||
export * from './use-workflow-search'
|
||||
export * from './use-memory-variable'
|
||||
export * from './use-auto-generate-webhook-url'
|
||||
export * from './use-serial-async-callback'
|
||||
|
||||
@@ -51,7 +51,7 @@ export const useSetWorkflowVarsWithValue = ({
|
||||
const { getNodes } = store.getState()
|
||||
|
||||
const nodeArr = getNodes()
|
||||
const allNodesOutputVars = toNodeOutputVars(nodeArr, false, () => true, [], [], [], [], passedInAllPluginInfoList || allPluginInfoList, passedInSchemaTypeDefinitions || schemaTypeDefinitions)
|
||||
const allNodesOutputVars = toNodeOutputVars(nodeArr, false, () => true, [], [], [], passedInAllPluginInfoList || allPluginInfoList, passedInSchemaTypeDefinitions || schemaTypeDefinitions)
|
||||
|
||||
const nodesKeyValue: Record<string, Node> = {}
|
||||
nodeArr.forEach((node) => {
|
||||
|
||||
@@ -132,7 +132,7 @@ export const useInspectVarsCrudCommon = ({
|
||||
mcpTools: mcpTools || [],
|
||||
dataSourceList: dataSourceList || [],
|
||||
}
|
||||
const currentNodeOutputVars = toNodeOutputVars([currentNode], false, () => true, [], [], [], [], allPluginInfoList, schemaTypeDefinitions)
|
||||
const currentNodeOutputVars = toNodeOutputVars([currentNode], false, () => true, [], [], [], allPluginInfoList, schemaTypeDefinitions)
|
||||
const vars = await fetchNodeInspectVars(flowType, flowId, nodeId)
|
||||
const varsWithSchemaType = vars.map((varItem) => {
|
||||
const schemaType = currentNodeOutputVars[0]?.vars.find(v => v.variable === varItem.name)?.schemaType || ''
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import {
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import {
|
||||
useStore,
|
||||
useWorkflowStore,
|
||||
} from '@/app/components/workflow/store'
|
||||
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
|
||||
import type { MemoryVariable } from '@/app/components/workflow/types'
|
||||
|
||||
export const useMemoryVariable = () => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const setMemoryVariables = useStore(s => s.setMemoryVariables)
|
||||
|
||||
const handleAddMemoryVariable = useCallback((memoryVariable: MemoryVariable) => {
|
||||
const { memoryVariables } = workflowStore.getState()
|
||||
setMemoryVariables([memoryVariable, ...memoryVariables])
|
||||
}, [setMemoryVariables, workflowStore])
|
||||
|
||||
const handleUpdateMemoryVariable = useCallback((memoryVariable: MemoryVariable) => {
|
||||
const { memoryVariables } = workflowStore.getState()
|
||||
setMemoryVariables(memoryVariables.map(v => v.id === memoryVariable.id ? memoryVariable : v))
|
||||
}, [setMemoryVariables, workflowStore])
|
||||
|
||||
const handleDeleteMemoryVariable = useCallback((memoryVariable: MemoryVariable) => {
|
||||
const { memoryVariables } = workflowStore.getState()
|
||||
setMemoryVariables(memoryVariables.filter(v => v.id !== memoryVariable.id))
|
||||
}, [setMemoryVariables, workflowStore])
|
||||
|
||||
return {
|
||||
handleAddMemoryVariable,
|
||||
handleUpdateMemoryVariable,
|
||||
handleDeleteMemoryVariable,
|
||||
}
|
||||
}
|
||||
|
||||
export const useFormatMemoryVariables = () => {
|
||||
const formatMemoryVariables = useCallback((memoryVariables: MemoryVariable[]) => {
|
||||
return memoryVariables.map((v) => {
|
||||
return {
|
||||
...v,
|
||||
value_type: ChatVarType.Memory,
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
|
||||
return {
|
||||
formatMemoryVariables,
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,6 @@ export const useWorkflowVariables = () => {
|
||||
filterVar,
|
||||
hideEnv,
|
||||
hideChatVar,
|
||||
conversationVariablesFirst,
|
||||
}: {
|
||||
parentNode?: Node | null
|
||||
beforeNodes: Node[]
|
||||
@@ -44,12 +43,10 @@ export const useWorkflowVariables = () => {
|
||||
filterVar: (payload: Var, selector: ValueSelector) => boolean
|
||||
hideEnv?: boolean
|
||||
hideChatVar?: boolean
|
||||
conversationVariablesFirst?: boolean
|
||||
}): NodeOutPutVar[] => {
|
||||
const {
|
||||
conversationVariables,
|
||||
environmentVariables,
|
||||
memoryVariables,
|
||||
ragPipelineVariables,
|
||||
dataSourceList,
|
||||
} = workflowStore.getState()
|
||||
@@ -60,7 +57,6 @@ export const useWorkflowVariables = () => {
|
||||
isChatMode,
|
||||
environmentVariables: hideEnv ? [] : environmentVariables,
|
||||
conversationVariables: (isChatMode && !hideChatVar) ? conversationVariables : [],
|
||||
memoryVariables: isChatMode ? memoryVariables : [],
|
||||
ragVariables: ragPipelineVariables,
|
||||
filterVar,
|
||||
allPluginInfoList: {
|
||||
@@ -71,7 +67,6 @@ export const useWorkflowVariables = () => {
|
||||
dataSourceList: dataSourceList || [],
|
||||
},
|
||||
schemaTypeDefinitions,
|
||||
conversationVariablesFirst,
|
||||
})
|
||||
}, [t, workflowStore, schemaTypeDefinitions, buildInTools, customTools, workflowTools, mcpTools])
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ type CollapseProps = {
|
||||
onCollapse?: (collapsed: boolean) => void
|
||||
operations?: ReactNode
|
||||
hideCollapseIcon?: boolean
|
||||
triggerClassName?: string
|
||||
}
|
||||
const Collapse = ({
|
||||
disabled,
|
||||
@@ -23,7 +22,6 @@ const Collapse = ({
|
||||
onCollapse,
|
||||
operations,
|
||||
hideCollapseIcon,
|
||||
triggerClassName,
|
||||
}: CollapseProps) => {
|
||||
const [collapsedLocal, setCollapsedLocal] = useState(true)
|
||||
const collapsedMerged = collapsed !== undefined ? collapsed : collapsedLocal
|
||||
@@ -44,7 +42,7 @@ const Collapse = ({
|
||||
<>
|
||||
<div className='group/collapse flex items-center'>
|
||||
<div
|
||||
className={cn('ml-4 flex grow items-center', triggerClassName)}
|
||||
className='ml-4 flex grow items-center'
|
||||
onClick={() => {
|
||||
if (!disabled) {
|
||||
setCollapsedLocal(!collapsedMerged)
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { Memory } from '@/app/components/base/icons/src/vender/line/others'
|
||||
|
||||
type Props = {
|
||||
onAddMemory: () => void
|
||||
}
|
||||
|
||||
const AddMemoryButton = ({ onAddMemory }: Props) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='ml-1.5 mt-2.5'>
|
||||
<Button
|
||||
variant='ghost'
|
||||
size='small'
|
||||
className='text-text-tertiary'
|
||||
onClick={onAddMemory}
|
||||
>
|
||||
<Memory className='h-3.5 w-3.5' />
|
||||
<span className='ml-1'>{t('workflow.nodes.llm.memory.addButton')}</span>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AddMemoryButton
|
||||
@@ -36,9 +36,6 @@ import Switch from '@/app/components/base/switch'
|
||||
import { Jinja } from '@/app/components/base/icons/src/vender/workflow'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import { useWorkflowVariableType } from '@/app/components/workflow/hooks'
|
||||
import AddMemoryButton from './add-memory-button'
|
||||
import { MEMORY_POPUP_SHOW_BY_EVENT_EMITTER } from './type'
|
||||
import MemoryCreateButton from '@/app/components/workflow/nodes/llm/components/memory-system/memory-create-button'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
@@ -78,11 +75,10 @@ type Props = {
|
||||
titleTooltip?: ReactNode
|
||||
inputClassName?: string
|
||||
editorContainerClassName?: string
|
||||
placeholder?: string | React.JSX.Element
|
||||
placeholder?: string
|
||||
placeholderClassName?: string
|
||||
titleClassName?: string
|
||||
required?: boolean
|
||||
isMemorySupported?: boolean
|
||||
}
|
||||
|
||||
const Editor: FC<Props> = ({
|
||||
@@ -122,7 +118,6 @@ const Editor: FC<Props> = ({
|
||||
titleClassName,
|
||||
editorContainerClassName,
|
||||
required,
|
||||
isMemorySupported,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
@@ -158,182 +153,170 @@ const Editor: FC<Props> = ({
|
||||
const pipelineId = useStore(s => s.pipelineId)
|
||||
const setShowInputFieldPanel = useStore(s => s.setShowInputFieldPanel)
|
||||
|
||||
const handleAddMemory = () => {
|
||||
setFocus()
|
||||
eventEmitter?.emit({ type: MEMORY_POPUP_SHOW_BY_EVENT_EMITTER, instanceId } as any)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Wrap className={cn(className, wrapClassName)} style={wrapStyle} isInNode isExpand={isExpand}>
|
||||
<div ref={ref} className={cn(isFocus ? (gradientBorder && 'bg-gradient-to-r from-components-input-border-active-prompt-1 to-components-input-border-active-prompt-2') : 'bg-transparent', isExpand && 'h-full', '!rounded-[9px] p-0.5', containerClassName)}>
|
||||
<div className={cn(isFocus ? 'bg-background-default' : 'bg-components-input-bg-normal', isExpand && 'flex h-full flex-col', 'rounded-lg', containerClassName)}>
|
||||
<div className={cn('flex items-center justify-between pl-3 pr-2 pt-1', headerClassName)}>
|
||||
<div className='flex gap-2'>
|
||||
<div className={cn('text-xs font-semibold uppercase leading-4 text-text-secondary', titleClassName)}>{title} {required && <span className='text-text-destructive'>*</span>}</div>
|
||||
{titleTooltip && <Tooltip popupContent={titleTooltip} />}
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
<div className='text-xs font-medium leading-[18px] text-text-tertiary'>{value?.length || 0}</div>
|
||||
{isSupportPromptGenerator && (
|
||||
<PromptGeneratorBtn
|
||||
nodeId={nodeId!}
|
||||
editorId={editorId}
|
||||
className='ml-[5px]'
|
||||
onGenerated={onGenerated}
|
||||
modelConfig={modelConfig}
|
||||
currentPrompt={value}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className='ml-2 mr-2 h-3 w-px bg-divider-regular'></div>
|
||||
{/* Operations */}
|
||||
<div className='flex items-center space-x-[2px]'>
|
||||
{isSupportJinja && (
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div>
|
||||
<div>{t('workflow.common.enableJinja')}</div>
|
||||
<a className='text-text-accent' target='_blank' href='https://jinja.palletsprojects.com/en/2.10.x/'>{t('workflow.common.learnMore')}</a>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className={cn(editionType === EditionType.jinja2 && 'border-components-button-ghost-bg-hover bg-components-button-ghost-bg-hover', 'flex h-[22px] items-center space-x-0.5 rounded-[5px] border border-transparent px-1.5 hover:border-components-button-ghost-bg-hover')}>
|
||||
<Jinja className='h-3 w-6 text-text-quaternary' />
|
||||
<Switch
|
||||
size='sm'
|
||||
defaultValue={editionType === EditionType.jinja2}
|
||||
onChange={(checked) => {
|
||||
onEditionTypeChange?.(checked ? EditionType.jinja2 : EditionType.basic)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
)}
|
||||
{!readOnly && (
|
||||
<Tooltip
|
||||
popupContent={`${t('workflow.common.insertVarTip')}`}
|
||||
>
|
||||
<ActionButton onClick={handleInsertVariable}>
|
||||
<Variable02 className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{showRemove && (
|
||||
<ActionButton onClick={onRemove}>
|
||||
<RiDeleteBinLine className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
)}
|
||||
{!isCopied
|
||||
? (
|
||||
<ActionButton onClick={handleCopy}>
|
||||
<Copy className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
)
|
||||
: (
|
||||
<ActionButton>
|
||||
<CopyCheck className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
)
|
||||
}
|
||||
<ToggleExpandBtn isExpand={isExpand} onExpandChange={setIsExpand} />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<Wrap className={cn(className, wrapClassName)} style={wrapStyle} isInNode isExpand={isExpand}>
|
||||
<div ref={ref} className={cn(isFocus ? (gradientBorder && 'bg-gradient-to-r from-components-input-border-active-prompt-1 to-components-input-border-active-prompt-2') : 'bg-transparent', isExpand && 'h-full', '!rounded-[9px] p-0.5', containerClassName)}>
|
||||
<div className={cn(isFocus ? 'bg-background-default' : 'bg-components-input-bg-normal', isExpand && 'flex h-full flex-col', 'rounded-lg', containerClassName)}>
|
||||
<div className={cn('flex items-center justify-between pl-3 pr-2 pt-1', headerClassName)}>
|
||||
<div className='flex gap-2'>
|
||||
<div className={cn('text-xs font-semibold uppercase leading-4 text-text-secondary', titleClassName)}>{title} {required && <span className='text-text-destructive'>*</span>}</div>
|
||||
{titleTooltip && <Tooltip popupContent={titleTooltip} />}
|
||||
</div>
|
||||
<div className='flex items-center'>
|
||||
<div className='text-xs font-medium leading-[18px] text-text-tertiary'>{value?.length || 0}</div>
|
||||
{isSupportPromptGenerator && (
|
||||
<PromptGeneratorBtn
|
||||
nodeId={nodeId!}
|
||||
editorId={editorId}
|
||||
className='ml-[5px]'
|
||||
onGenerated={onGenerated}
|
||||
modelConfig={modelConfig}
|
||||
currentPrompt={value}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Min: 80 Max: 560. Header: 24 */}
|
||||
<div className={cn('pb-2', isExpand && 'flex grow flex-col', isMemorySupported && isFocus && 'pb-1.5')}>
|
||||
{!(isSupportJinja && editionType === EditionType.jinja2)
|
||||
? (
|
||||
<>
|
||||
<div className={cn(isExpand ? 'grow' : 'max-h-[536px]', 'relative min-h-[56px] overflow-y-auto px-3', editorContainerClassName)}>
|
||||
<PromptEditor
|
||||
key={controlPromptEditorRerenderKey}
|
||||
placeholder={placeholder}
|
||||
placeholderClassName={placeholderClassName}
|
||||
instanceId={instanceId}
|
||||
compact
|
||||
className={cn('min-h-[56px]', inputClassName)}
|
||||
style={isExpand ? { height: editorExpandHeight - 5 } : {}}
|
||||
value={value}
|
||||
contextBlock={{
|
||||
show: justVar ? false : isShowContext,
|
||||
selectable: !hasSetBlockStatus?.context,
|
||||
canNotAddContext: true,
|
||||
<div className='ml-2 mr-2 h-3 w-px bg-divider-regular'></div>
|
||||
{/* Operations */}
|
||||
<div className='flex items-center space-x-[2px]'>
|
||||
{isSupportJinja && (
|
||||
<Tooltip
|
||||
popupContent={
|
||||
<div>
|
||||
<div>{t('workflow.common.enableJinja')}</div>
|
||||
<a className='text-text-accent' target='_blank' href='https://jinja.palletsprojects.com/en/2.10.x/'>{t('workflow.common.learnMore')}</a>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div className={cn(editionType === EditionType.jinja2 && 'border-components-button-ghost-bg-hover bg-components-button-ghost-bg-hover', 'flex h-[22px] items-center space-x-0.5 rounded-[5px] border border-transparent px-1.5 hover:border-components-button-ghost-bg-hover')}>
|
||||
<Jinja className='h-3 w-6 text-text-quaternary' />
|
||||
<Switch
|
||||
size='sm'
|
||||
defaultValue={editionType === EditionType.jinja2}
|
||||
onChange={(checked) => {
|
||||
onEditionTypeChange?.(checked ? EditionType.jinja2 : EditionType.basic)
|
||||
}}
|
||||
historyBlock={{
|
||||
show: justVar ? false : isShowHistory,
|
||||
selectable: !hasSetBlockStatus?.history,
|
||||
history: {
|
||||
user: 'Human',
|
||||
assistant: 'Assistant',
|
||||
},
|
||||
}}
|
||||
queryBlock={{
|
||||
show: false, // use [sys.query] instead of query block
|
||||
selectable: false,
|
||||
}}
|
||||
workflowVariableBlock={{
|
||||
show: true,
|
||||
variables: nodesOutputVars || [],
|
||||
getVarType,
|
||||
workflowNodesMap: availableNodes.reduce((acc, node) => {
|
||||
acc[node.id] = {
|
||||
title: node.data.title,
|
||||
type: node.data.type,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
position: node.position,
|
||||
}
|
||||
if (node.data.type === BlockEnum.Start) {
|
||||
acc.sys = {
|
||||
title: t('workflow.blocks.start'),
|
||||
type: BlockEnum.Start,
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, {} as any),
|
||||
showManageInputField: !!pipelineId,
|
||||
onManageInputField: () => setShowInputFieldPanel?.(true),
|
||||
isMemorySupported,
|
||||
}}
|
||||
onChange={onChange}
|
||||
onBlur={setBlur}
|
||||
onFocus={setFocus}
|
||||
editable={!readOnly}
|
||||
isSupportFileVar={isSupportFileVar}
|
||||
/>
|
||||
{/* to patch Editor not support dynamic change editable status */}
|
||||
{readOnly && <div className='absolute inset-0 z-10'></div>}
|
||||
</div>
|
||||
{isMemorySupported && <AddMemoryButton onAddMemory={handleAddMemory} />}
|
||||
</>
|
||||
)
|
||||
: (
|
||||
<div className={cn(isExpand ? 'grow' : 'max-h-[536px]', 'relative min-h-[56px] overflow-y-auto px-3', editorContainerClassName)}>
|
||||
<CodeEditor
|
||||
availableVars={nodesOutputVars || []}
|
||||
varList={varList}
|
||||
onAddVar={handleAddVariable}
|
||||
isInNode
|
||||
readOnly={readOnly}
|
||||
language={CodeLanguage.python3}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
noWrapper
|
||||
isExpand={isExpand}
|
||||
className={inputClassName}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
||||
)}
|
||||
{!readOnly && (
|
||||
<Tooltip
|
||||
popupContent={`${t('workflow.common.insertVarTip')}`}
|
||||
>
|
||||
<ActionButton onClick={handleInsertVariable}>
|
||||
<Variable02 className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{showRemove && (
|
||||
<ActionButton onClick={onRemove}>
|
||||
<RiDeleteBinLine className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
)}
|
||||
{!isCopied
|
||||
? (
|
||||
<ActionButton onClick={handleCopy}>
|
||||
<Copy className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
)
|
||||
: (
|
||||
<ActionButton>
|
||||
<CopyCheck className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
)
|
||||
}
|
||||
<ToggleExpandBtn isExpand={isExpand} onExpandChange={setIsExpand} />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Min: 80 Max: 560. Header: 24 */}
|
||||
<div className={cn('pb-2', isExpand && 'flex grow flex-col')}>
|
||||
{!(isSupportJinja && editionType === EditionType.jinja2)
|
||||
? (
|
||||
<div className={cn(isExpand ? 'grow' : 'max-h-[536px]', 'relative min-h-[56px] overflow-y-auto px-3', editorContainerClassName)}>
|
||||
<PromptEditor
|
||||
key={controlPromptEditorRerenderKey}
|
||||
placeholder={placeholder}
|
||||
placeholderClassName={placeholderClassName}
|
||||
instanceId={instanceId}
|
||||
compact
|
||||
className={cn('min-h-[56px]', inputClassName)}
|
||||
style={isExpand ? { height: editorExpandHeight - 5 } : {}}
|
||||
value={value}
|
||||
contextBlock={{
|
||||
show: justVar ? false : isShowContext,
|
||||
selectable: !hasSetBlockStatus?.context,
|
||||
canNotAddContext: true,
|
||||
}}
|
||||
historyBlock={{
|
||||
show: justVar ? false : isShowHistory,
|
||||
selectable: !hasSetBlockStatus?.history,
|
||||
history: {
|
||||
user: 'Human',
|
||||
assistant: 'Assistant',
|
||||
},
|
||||
}}
|
||||
queryBlock={{
|
||||
show: false, // use [sys.query] instead of query block
|
||||
selectable: false,
|
||||
}}
|
||||
workflowVariableBlock={{
|
||||
show: true,
|
||||
variables: nodesOutputVars || [],
|
||||
getVarType: getVarType as any,
|
||||
workflowNodesMap: availableNodes.reduce((acc, node) => {
|
||||
acc[node.id] = {
|
||||
title: node.data.title,
|
||||
type: node.data.type,
|
||||
width: node.width,
|
||||
height: node.height,
|
||||
position: node.position,
|
||||
}
|
||||
if (node.data.type === BlockEnum.Start) {
|
||||
acc.sys = {
|
||||
title: t('workflow.blocks.start'),
|
||||
type: BlockEnum.Start,
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, {} as any),
|
||||
showManageInputField: !!pipelineId,
|
||||
onManageInputField: () => setShowInputFieldPanel?.(true),
|
||||
}}
|
||||
onChange={onChange}
|
||||
onBlur={setBlur}
|
||||
onFocus={setFocus}
|
||||
editable={!readOnly}
|
||||
isSupportFileVar={isSupportFileVar}
|
||||
/>
|
||||
{/* to patch Editor not support dynamic change editable status */}
|
||||
{readOnly && <div className='absolute inset-0 z-10'></div>}
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div className={cn(isExpand ? 'grow' : 'max-h-[536px]', 'relative min-h-[56px] overflow-y-auto px-3', editorContainerClassName)}>
|
||||
<CodeEditor
|
||||
availableVars={nodesOutputVars || []}
|
||||
varList={varList}
|
||||
onAddVar={handleAddVariable}
|
||||
isInNode
|
||||
readOnly={readOnly}
|
||||
language={CodeLanguage.python3}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
noWrapper
|
||||
isExpand={isExpand}
|
||||
className={inputClassName}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Wrap>
|
||||
{isMemorySupported && <MemoryCreateButton nodeId={nodeId || ''} instanceId={instanceId} hideTrigger />}
|
||||
</>
|
||||
</div>
|
||||
</Wrap>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export const MEMORY_POPUP_SHOW_BY_EVENT_EMITTER = 'MEMORY_POPUP_SHOW_BY_EVENT_EMITTER'
|
||||
export const MEMORY_VAR_MODAL_SHOW_BY_EVENT_EMITTER = 'MEMORY_VAR_MODAL_SHOW_BY_EVENT_EMITTER'
|
||||
export const MEMORY_VAR_CREATED_BY_MODAL_BY_EVENT_EMITTER = 'MEMORY_VAR_CREATED_BY_MODAL_BY_EVENT_EMITTER'
|
||||
@@ -39,7 +39,6 @@ import type {
|
||||
import type { VariableAssignerNodeType } from '@/app/components/workflow/nodes/variable-assigner/types'
|
||||
import type { Field as StructField } from '@/app/components/workflow/nodes/llm/types'
|
||||
import type { RAGPipelineVariable } from '@/models/pipeline'
|
||||
import type { MemoryVariable } from '@/app/components/workflow/types'
|
||||
import type { WebhookTriggerNodeType } from '@/app/components/workflow/nodes/trigger-webhook/types'
|
||||
import type { PluginTriggerNodeType } from '@/app/components/workflow/nodes/trigger-plugin/types'
|
||||
import PluginTriggerNodeDefault from '@/app/components/workflow/nodes/trigger-plugin/default'
|
||||
@@ -87,17 +86,13 @@ export const isConversationVar = (valueSelector: ValueSelector) => {
|
||||
return valueSelector[0] === 'conversation'
|
||||
}
|
||||
|
||||
export const isMemoryVariable = (valueSelector: ValueSelector) => {
|
||||
return valueSelector[0] === 'memory_block'
|
||||
}
|
||||
|
||||
export const isRagVariableVar = (valueSelector: ValueSelector) => {
|
||||
if (!valueSelector) return false
|
||||
return valueSelector[0] === 'rag'
|
||||
}
|
||||
|
||||
export const isSpecialVar = (prefix: string): boolean => {
|
||||
return ['sys', 'env', 'conversation', 'memory_block', 'rag'].includes(prefix)
|
||||
return ['sys', 'env', 'conversation', 'rag'].includes(prefix)
|
||||
}
|
||||
|
||||
export const hasValidChildren = (children: any): boolean => {
|
||||
@@ -649,25 +644,13 @@ const formatItem = (
|
||||
}
|
||||
|
||||
case 'conversation': {
|
||||
res.vars = [
|
||||
...data.memoryVarList.map((memoryVar: MemoryVariable) => {
|
||||
return {
|
||||
variable: `memory_block.${memoryVar.node_id ? `${memoryVar.node_id}_` : ''}${memoryVar.id}`,
|
||||
type: 'memory_block',
|
||||
description: '',
|
||||
isMemoryVariable: true,
|
||||
memoryVariableName: memoryVar.name,
|
||||
memoryVariableNodeId: memoryVar.node_id,
|
||||
}
|
||||
}) as Var[],
|
||||
...data.chatVarList.map((chatVar: ConversationVariable) => {
|
||||
return {
|
||||
variable: `conversation.${chatVar.name}`,
|
||||
type: chatVar.value_type,
|
||||
description: chatVar.description,
|
||||
}
|
||||
}) as Var[],
|
||||
]
|
||||
res.vars = data.chatVarList.map((chatVar: ConversationVariable) => {
|
||||
return {
|
||||
variable: `conversation.${chatVar.name}`,
|
||||
type: chatVar.value_type,
|
||||
description: chatVar.description,
|
||||
}
|
||||
}) as Var[]
|
||||
break
|
||||
}
|
||||
|
||||
@@ -715,13 +698,11 @@ const formatItem = (
|
||||
(() => {
|
||||
const variableArr = v.variable.split('.')
|
||||
const [first] = variableArr
|
||||
|
||||
if (isSpecialVar(first)) return variableArr
|
||||
|
||||
return [...selector, ...variableArr]
|
||||
})(),
|
||||
)
|
||||
|
||||
if (isCurrentMatched) return true
|
||||
|
||||
const isFile = v.type === VarType.file
|
||||
@@ -796,11 +777,9 @@ export const toNodeOutputVars = (
|
||||
filterVar = (_payload: Var, _selector: ValueSelector) => true,
|
||||
environmentVariables: EnvironmentVariable[] = [],
|
||||
conversationVariables: ConversationVariable[] = [],
|
||||
memoryVariables: MemoryVariable[] = [],
|
||||
ragVariables: RAGPipelineVariable[] = [],
|
||||
allPluginInfoList: Record<string, ToolWithProvider[]>,
|
||||
schemaTypeDefinitions?: SchemaTypeDefinition[],
|
||||
conversationVariablesFirst: boolean = false,
|
||||
): NodeOutPutVar[] => {
|
||||
// ENV_NODE data format
|
||||
const ENV_NODE = {
|
||||
@@ -817,7 +796,6 @@ export const toNodeOutputVars = (
|
||||
data: {
|
||||
title: 'CONVERSATION',
|
||||
type: 'conversation',
|
||||
memoryVarList: memoryVariables,
|
||||
chatVarList: conversationVariables,
|
||||
},
|
||||
}
|
||||
@@ -1017,7 +995,6 @@ export const getVarType = ({
|
||||
isConstant,
|
||||
environmentVariables = [],
|
||||
conversationVariables = [],
|
||||
memoryVariables = [],
|
||||
ragVariables = [],
|
||||
allPluginInfoList,
|
||||
schemaTypeDefinitions,
|
||||
@@ -1032,7 +1009,6 @@ export const getVarType = ({
|
||||
isConstant?: boolean;
|
||||
environmentVariables?: EnvironmentVariable[];
|
||||
conversationVariables?: ConversationVariable[];
|
||||
memoryVariables?: MemoryVariable[];
|
||||
ragVariables?: RAGPipelineVariable[];
|
||||
allPluginInfoList: Record<string, ToolWithProvider[]>;
|
||||
schemaTypeDefinitions?: SchemaTypeDefinition[];
|
||||
@@ -1046,7 +1022,6 @@ export const getVarType = ({
|
||||
undefined,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
memoryVariables,
|
||||
ragVariables,
|
||||
allPluginInfoList,
|
||||
schemaTypeDefinitions,
|
||||
@@ -1175,12 +1150,10 @@ export const toNodeAvailableVars = ({
|
||||
isChatMode,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
memoryVariables,
|
||||
ragVariables,
|
||||
filterVar,
|
||||
allPluginInfoList,
|
||||
schemaTypeDefinitions,
|
||||
conversationVariablesFirst,
|
||||
}: {
|
||||
parentNode?: Node | null;
|
||||
t?: any;
|
||||
@@ -1191,14 +1164,11 @@ export const toNodeAvailableVars = ({
|
||||
environmentVariables?: EnvironmentVariable[];
|
||||
// chat var
|
||||
conversationVariables?: ConversationVariable[];
|
||||
// memory variables
|
||||
memoryVariables?: MemoryVariable[];
|
||||
// rag variables
|
||||
ragVariables?: RAGPipelineVariable[];
|
||||
filterVar: (payload: Var, selector: ValueSelector) => boolean;
|
||||
allPluginInfoList: Record<string, ToolWithProvider[]>;
|
||||
schemaTypeDefinitions?: SchemaTypeDefinition[];
|
||||
conversationVariablesFirst?: boolean
|
||||
}): NodeOutPutVar[] => {
|
||||
const beforeNodesOutputVars = toNodeOutputVars(
|
||||
beforeNodes,
|
||||
@@ -1206,11 +1176,9 @@ export const toNodeAvailableVars = ({
|
||||
filterVar,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
memoryVariables,
|
||||
ragVariables,
|
||||
allPluginInfoList,
|
||||
schemaTypeDefinitions,
|
||||
conversationVariablesFirst,
|
||||
)
|
||||
const isInIteration = parentNode?.data.type === BlockEnum.Iteration
|
||||
if (isInIteration) {
|
||||
@@ -1223,7 +1191,6 @@ export const toNodeAvailableVars = ({
|
||||
isChatMode,
|
||||
environmentVariables,
|
||||
conversationVariables,
|
||||
memoryVariables,
|
||||
allPluginInfoList,
|
||||
schemaTypeDefinitions,
|
||||
})
|
||||
|
||||
@@ -65,7 +65,6 @@ const Item: FC<ItemProps> = ({
|
||||
const isSys = itemData.variable.startsWith('sys.')
|
||||
const isEnv = itemData.variable.startsWith('env.')
|
||||
const isChatVar = itemData.variable.startsWith('conversation.')
|
||||
const isMemoryVar = itemData.variable.startsWith('memory_block')
|
||||
const isRagVariable = itemData.isRagVariable
|
||||
const flatVarIcon = useMemo(() => {
|
||||
if (!isFlat)
|
||||
@@ -153,7 +152,7 @@ const Item: FC<ItemProps> = ({
|
||||
if (isFlat) {
|
||||
onChange([itemData.variable], itemData)
|
||||
}
|
||||
else if (isSys || isEnv || isChatVar || isMemoryVar || isRagVariable) { // system variable | environment variable | conversation variable
|
||||
else if (isSys || isEnv || isChatVar || isRagVariable) { // system variable | environment variable | conversation variable
|
||||
onChange([...objPath, ...itemData.variable.split('.')], itemData)
|
||||
}
|
||||
else {
|
||||
@@ -163,16 +162,10 @@ const Item: FC<ItemProps> = ({
|
||||
const variableCategory = useMemo(() => {
|
||||
if (isEnv) return 'environment'
|
||||
if (isChatVar) return 'conversation'
|
||||
if (isMemoryVar) return 'memory_block'
|
||||
if (isLoopVar) return 'loop'
|
||||
if (isRagVariable) return 'rag'
|
||||
return 'system'
|
||||
}, [isEnv, isChatVar, isMemoryVar, isSys, isLoopVar, isRagVariable])
|
||||
const variableType = useMemo(() => {
|
||||
if (itemData.type === 'memory_block')
|
||||
return 'memory'
|
||||
return itemData.type
|
||||
}, [itemData.type])
|
||||
}, [isEnv, isChatVar, isSys, isLoopVar, isRagVariable])
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
@@ -200,7 +193,7 @@ const Item: FC<ItemProps> = ({
|
||||
/>}
|
||||
{isFlat && flatVarIcon}
|
||||
|
||||
{!isEnv && !isChatVar && !isMemoryVar && !isRagVariable && (
|
||||
{!isEnv && !isChatVar && !isRagVariable && (
|
||||
<div title={itemData.variable} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{varName}</div>
|
||||
)}
|
||||
{isEnv && (
|
||||
@@ -209,15 +202,11 @@ const Item: FC<ItemProps> = ({
|
||||
{isChatVar && (
|
||||
<div title={itemData.des} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.variable.replace('conversation.', '')}</div>
|
||||
)}
|
||||
{isMemoryVar && (
|
||||
<div title={itemData.memoryVariableName} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.memoryVariableName}</div>
|
||||
)
|
||||
}
|
||||
{isRagVariable && (
|
||||
<div title={itemData.des} className='system-sm-medium ml-1 w-0 grow truncate text-text-secondary'>{itemData.variable.split('.').slice(-1)[0]}</div>
|
||||
)}
|
||||
</div>
|
||||
<div className='ml-1 shrink-0 text-xs font-normal capitalize text-text-tertiary'>{(preferSchemaType && itemData.schemaType) ? itemData.schemaType : variableType}</div>
|
||||
<div className='ml-1 shrink-0 text-xs font-normal capitalize text-text-tertiary'>{(preferSchemaType && itemData.schemaType) ? itemData.schemaType : itemData.type}</div>
|
||||
{
|
||||
(isObj || isStructureOutput) && (
|
||||
<ChevronRight className={cn('ml-0.5 h-3 w-3 text-text-quaternary', isHovering && 'text-text-tertiary')} />
|
||||
|
||||
@@ -22,7 +22,6 @@ const VariableLabel = ({
|
||||
errorMsg,
|
||||
onClick,
|
||||
isExceptionVariable,
|
||||
isMemoryVariable,
|
||||
ref,
|
||||
notShowFullPath,
|
||||
rightSlot,
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { useMemo } from 'react'
|
||||
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
|
||||
import { BubbleX, Env, GlobalVariable, Memory } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { BubbleX, Env, GlobalVariable } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import { Loop } from '@/app/components/base/icons/src/vender/workflow'
|
||||
import { InputField } from '@/app/components/base/icons/src/vender/pipeline'
|
||||
import {
|
||||
isConversationVar,
|
||||
isENV,
|
||||
isGlobalVar,
|
||||
isMemoryVariable,
|
||||
isRagVariableVar,
|
||||
isSystemVar,
|
||||
} from '../utils'
|
||||
@@ -24,9 +23,6 @@ export const useVarIcon = (variables: string[], variableCategory?: VarInInspectT
|
||||
if (isENV(variables) || variableCategory === VarInInspectType.environment || variableCategory === 'environment')
|
||||
return Env
|
||||
|
||||
if (isMemoryVariable(variables) || variableCategory === VarInInspectType.memory || variableCategory === 'memory_block')
|
||||
return Memory
|
||||
|
||||
if (isConversationVar(variables) || variableCategory === VarInInspectType.conversation || variableCategory === 'conversation')
|
||||
return BubbleX
|
||||
|
||||
@@ -50,9 +46,6 @@ export const useVarColor = (variables: string[], isExceptionVariable?: boolean,
|
||||
if (isConversationVar(variables) || variableCategory === VarInInspectType.conversation || variableCategory === 'conversation')
|
||||
return 'text-util-colors-teal-teal-700'
|
||||
|
||||
if (isMemoryVariable(variables) || variableCategory === VarInInspectType.memory || variableCategory === 'memory_block')
|
||||
return 'text-util-colors-teal-teal-700'
|
||||
|
||||
if (isGlobalVar(variables) || variableCategory === VarInInspectType.system)
|
||||
return 'text-util-colors-orange-orange-600'
|
||||
|
||||
@@ -112,15 +105,6 @@ export const useVarBgColorInEditor = (variables: string[], hasError?: boolean) =
|
||||
}
|
||||
}
|
||||
|
||||
if (isMemoryVariable(variables)) {
|
||||
return {
|
||||
hoverBorderColor: 'hover:border-util-colors-teal-teal-100',
|
||||
hoverBgColor: 'hover:bg-util-colors-teal-teal-50',
|
||||
selectedBorderColor: 'border-util-colors-teal-teal-600',
|
||||
selectedBgColor: 'bg-util-colors-teal-teal-50',
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
hoverBorderColor: 'hover:border-state-accent-alt',
|
||||
hoverBgColor: 'hover:bg-state-accent-hover',
|
||||
|
||||
@@ -13,7 +13,6 @@ export type VariablePayload = {
|
||||
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void
|
||||
errorMsg?: string
|
||||
isExceptionVariable?: boolean
|
||||
isMemoryVariable?: boolean
|
||||
ref?: React.Ref<HTMLDivElement>
|
||||
notShowFullPath?: boolean
|
||||
rightSlot?: ReactNode
|
||||
|
||||
@@ -15,8 +15,6 @@ type Params = {
|
||||
hideChatVar?: boolean
|
||||
filterVar: (payload: Var, selector: ValueSelector) => boolean
|
||||
passedInAvailableNodes?: Node[]
|
||||
conversationVariablesFirst?: boolean
|
||||
isMemorySupported?: boolean
|
||||
}
|
||||
|
||||
// TODO: loop type?
|
||||
@@ -26,8 +24,6 @@ const useAvailableVarList = (nodeId: string, {
|
||||
hideEnv,
|
||||
hideChatVar,
|
||||
passedInAvailableNodes,
|
||||
conversationVariablesFirst,
|
||||
isMemorySupported,
|
||||
}: Params = {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar: () => true,
|
||||
@@ -67,35 +63,13 @@ const useAvailableVarList = (nodeId: string, {
|
||||
})
|
||||
}
|
||||
}
|
||||
const nodeAvailableVars = getNodeAvailableVars({
|
||||
const availableVars = [...getNodeAvailableVars({
|
||||
parentNode: iterationNode,
|
||||
beforeNodes: availableNodes,
|
||||
isChatMode,
|
||||
filterVar,
|
||||
hideEnv,
|
||||
hideChatVar,
|
||||
conversationVariablesFirst,
|
||||
})
|
||||
|
||||
const availableVars = [...nodeAvailableVars.map((availableVar) => {
|
||||
if (availableVar.nodeId === 'conversation') {
|
||||
return {
|
||||
...availableVar,
|
||||
vars: availableVar.vars.filter((v) => {
|
||||
if (!v.isMemoryVariable)
|
||||
return true
|
||||
|
||||
if (!isMemorySupported)
|
||||
return false
|
||||
if (!v.memoryVariableNodeId)
|
||||
return true
|
||||
|
||||
return v.memoryVariableNodeId === nodeId
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
return availableVar
|
||||
}), ...dataSourceRagVars]
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { useCallback } from 'react'
|
||||
import { useStoreApi } from 'reactflow'
|
||||
import { useNodeDataUpdate } from '@/app/components/workflow/hooks'
|
||||
import type { CommonNodeType } from '@/app/components/workflow/types'
|
||||
const useNodeCrud = <T>(id: string, data: CommonNodeType<T>) => {
|
||||
@@ -18,28 +16,4 @@ const useNodeCrud = <T>(id: string, data: CommonNodeType<T>) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const useNodeUpdate = (id: string) => {
|
||||
const store = useStoreApi()
|
||||
const { handleNodeDataUpdateWithSyncDraft } = useNodeDataUpdate()
|
||||
|
||||
const getNodeData = useCallback(() => {
|
||||
const { getNodes } = store.getState()
|
||||
const nodes = getNodes()
|
||||
|
||||
return nodes.find(node => node.id === id)
|
||||
}, [store, id])
|
||||
|
||||
const handleNodeDataUpdate = useCallback((data: any) => {
|
||||
handleNodeDataUpdateWithSyncDraft({
|
||||
id,
|
||||
data,
|
||||
})
|
||||
}, [id, handleNodeDataUpdateWithSyncDraft])
|
||||
|
||||
return {
|
||||
getNodeData,
|
||||
handleNodeDataUpdate,
|
||||
}
|
||||
}
|
||||
|
||||
export default useNodeCrud
|
||||
|
||||
@@ -145,7 +145,6 @@ const useOneStepRun = <T>({
|
||||
const { t } = useTranslation()
|
||||
const { getBeforeNodesInSameBranch, getBeforeNodesInSameBranchIncludeParent } = useWorkflow() as any
|
||||
const conversationVariables = useStore(s => s.conversationVariables)
|
||||
const memoryVariables = useStore(s => s.memoryVariables)
|
||||
const isChatMode = useIsChatMode()
|
||||
const isIteration = data.type === BlockEnum.Iteration
|
||||
const isLoop = data.type === BlockEnum.Loop
|
||||
@@ -174,7 +173,7 @@ const useOneStepRun = <T>({
|
||||
dataSourceList: dataSourceList || [],
|
||||
}
|
||||
|
||||
const allOutputVars = toNodeOutputVars(availableNodes, isChatMode, undefined, undefined, conversationVariables, memoryVariables, [], allPluginInfoList, schemaTypeDefinitions)
|
||||
const allOutputVars = toNodeOutputVars(availableNodes, isChatMode, undefined, undefined, conversationVariables, [], allPluginInfoList, schemaTypeDefinitions)
|
||||
const targetVar = allOutputVars.find(item => isSystem ? !!item.isStartNode : item.nodeId === valueSelector[0])
|
||||
if (!targetVar)
|
||||
return undefined
|
||||
|
||||
@@ -58,7 +58,7 @@ const useConfig = (id: string, payload: IterationNodeType) => {
|
||||
mcpTools: mcpTools || [],
|
||||
dataSourceList: dataSourceList || [],
|
||||
}
|
||||
const childrenNodeVars = toNodeOutputVars(iterationChildrenNodes, isChatMode, undefined, [], [], [], [], allPluginInfoList)
|
||||
const childrenNodeVars = toNodeOutputVars(iterationChildrenNodes, isChatMode, undefined, [], [], [], allPluginInfoList)
|
||||
|
||||
const handleOutputVarChange = useCallback((output: ValueSelector | string, _varKindType: VarKindType, varInfo?: Var) => {
|
||||
if (isEqual(inputs.output_selector, output as ValueSelector))
|
||||
|
||||
@@ -39,7 +39,6 @@ type Props = {
|
||||
varList: Variable[]
|
||||
handleAddVariable: (payload: any) => void
|
||||
modelConfig?: ModelConfig
|
||||
isMemorySupported?: boolean
|
||||
}
|
||||
|
||||
const roleOptions = [
|
||||
@@ -82,7 +81,6 @@ const ConfigPromptItem: FC<Props> = ({
|
||||
varList,
|
||||
handleAddVariable,
|
||||
modelConfig,
|
||||
isMemorySupported,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const workflowStore = useWorkflowStore()
|
||||
@@ -148,17 +146,6 @@ const ConfigPromptItem: FC<Props> = ({
|
||||
varList={varList}
|
||||
handleAddVariable={handleAddVariable}
|
||||
isSupportFileVar
|
||||
placeholder={
|
||||
<>
|
||||
<div>
|
||||
{t(`${i18nPrefix}.promptEditorPlaceholder1`)}
|
||||
</div>
|
||||
<div>
|
||||
{t(`${i18nPrefix}.promptEditorPlaceholder2`)}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
isMemorySupported={isMemorySupported}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ type Props = {
|
||||
varList?: Variable[]
|
||||
handleAddVariable: (payload: any) => void
|
||||
modelConfig: ModelConfig
|
||||
isMemorySupported?: boolean
|
||||
}
|
||||
|
||||
const ConfigPrompt: FC<Props> = ({
|
||||
@@ -50,7 +49,6 @@ const ConfigPrompt: FC<Props> = ({
|
||||
varList = [],
|
||||
handleAddVariable,
|
||||
modelConfig,
|
||||
isMemorySupported,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const workflowStore = useWorkflowStore()
|
||||
@@ -75,8 +73,6 @@ const ConfigPrompt: FC<Props> = ({
|
||||
} = useAvailableVarList(nodeId, {
|
||||
onlyLeafNodeVar: false,
|
||||
filterVar,
|
||||
conversationVariablesFirst: true,
|
||||
isMemorySupported,
|
||||
})
|
||||
|
||||
const handleChatModePromptChange = useCallback((index: number) => {
|
||||
@@ -208,7 +204,6 @@ const ConfigPrompt: FC<Props> = ({
|
||||
varList={varList}
|
||||
handleAddVariable={handleAddVariable}
|
||||
modelConfig={modelConfig}
|
||||
isMemorySupported={isMemorySupported}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
@@ -245,7 +240,6 @@ const ConfigPrompt: FC<Props> = ({
|
||||
handleAddVariable={handleAddVariable}
|
||||
onGenerated={handleGenerated}
|
||||
modelConfig={modelConfig}
|
||||
isMemorySupported={isMemorySupported}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
import {
|
||||
memo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiDeleteBinLine,
|
||||
RiEditLine,
|
||||
} from '@remixicon/react'
|
||||
import type { Memory } from '@/app/components/workflow/types'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import ActionButton from '@/app/components/base/action-button'
|
||||
import { useMemoryVariables } from './hooks/use-memory-variables'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import { Memory as MemoryIcon } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import VariableModal from '@/app/components/workflow/panel/chat-variable-panel/components/variable-modal'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type BlockMemoryProps = {
|
||||
id: string
|
||||
payload: Memory
|
||||
}
|
||||
const BlockMemory = ({ id }: BlockMemoryProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [destructiveItemId, setDestructiveItemId] = useState<string | undefined>(undefined)
|
||||
const {
|
||||
memoryVariablesInUsed,
|
||||
editMemoryVariable,
|
||||
handleSetEditMemoryVariable,
|
||||
handleEdit,
|
||||
handleDelete,
|
||||
handleDeleteConfirm,
|
||||
cacheForDeleteMemoryVariable,
|
||||
setCacheForDeleteMemoryVariable,
|
||||
} = useMemoryVariables(id)
|
||||
|
||||
if (!memoryVariablesInUsed?.length) {
|
||||
return (
|
||||
<div className='system-xs-regular mt-2 flex items-center justify-center rounded-[10px] bg-background-section p-3 text-text-tertiary'>
|
||||
{t('workflow.nodes.common.memory.block.empty')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className='mt-2 space-y-1'>
|
||||
{
|
||||
memoryVariablesInUsed.map(memoryVariable => (
|
||||
<div
|
||||
key={memoryVariable.id}
|
||||
className={cn(
|
||||
'group flex h-8 items-center space-x-1 rounded-lg border-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg pl-2 pr-1 shadow-xs',
|
||||
destructiveItemId === memoryVariable.id && 'border border-state-destructive-solid bg-state-destructive-hover',
|
||||
)}>
|
||||
<MemoryIcon className='h-4 w-4 text-util-colors-teal-teal-700' />
|
||||
<div
|
||||
title={memoryVariable.name}
|
||||
className='system-sm-medium grow truncate text-text-secondary'
|
||||
>
|
||||
{memoryVariable.name}
|
||||
</div>
|
||||
<Badge className={cn('shrink-0 group-hover:hidden', editMemoryVariable?.id === memoryVariable.id && 'hidden')}>
|
||||
{memoryVariable.term}
|
||||
</Badge>
|
||||
<ActionButton
|
||||
className={cn(
|
||||
'hidden shrink-0 group-hover:inline-flex',
|
||||
editMemoryVariable?.id === memoryVariable.id && 'inline-flex bg-state-base-hover text-text-secondary',
|
||||
)}
|
||||
size='m'
|
||||
onClick={() => handleSetEditMemoryVariable(memoryVariable.id)}
|
||||
>
|
||||
<RiEditLine className='h-4 w-4 text-text-tertiary' />
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
className={cn(
|
||||
'hidden shrink-0 bg-transparent hover:bg-transparent hover:text-text-destructive group-hover:inline-flex',
|
||||
editMemoryVariable?.id === memoryVariable.id && 'inline-flex',
|
||||
)}
|
||||
size='m'
|
||||
onClick={() => handleDelete(memoryVariable)}
|
||||
onMouseOver={() => setDestructiveItemId(memoryVariable.id)}
|
||||
onMouseOut={() => setDestructiveItemId(undefined)}
|
||||
>
|
||||
<RiDeleteBinLine className={cn('h-4 w-4', destructiveItemId === memoryVariable.id && 'text-text-destructive')} />
|
||||
</ActionButton>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
{
|
||||
!!cacheForDeleteMemoryVariable && (
|
||||
<Confirm
|
||||
isShow
|
||||
onCancel={() => setCacheForDeleteMemoryVariable(undefined)}
|
||||
onConfirm={() => handleDeleteConfirm(cacheForDeleteMemoryVariable.id)}
|
||||
title={t('workflow.nodes.common.memory.deleteNodeMemoryVariableConfirmTitle', { name: cacheForDeleteMemoryVariable.name })}
|
||||
content={t('workflow.nodes.common.memory.deleteNodeMemoryVariableConfirmDesc')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
!!editMemoryVariable && (
|
||||
<VariableModal
|
||||
chatVar={editMemoryVariable}
|
||||
onClose={() => handleSetEditMemoryVariable(undefined)}
|
||||
onSave={handleEdit}
|
||||
nodeScopeMemoryVariable={{ nodeId: id }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(BlockMemory)
|
||||
@@ -1,20 +0,0 @@
|
||||
import { useCallback } from 'react'
|
||||
import type { MemoryVariable } from '@/app/components/workflow/types'
|
||||
import { useNodeUpdate } from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||
import { findUsedVarNodes } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
|
||||
export const useMemoryUsedDetector = (nodeId: string) => {
|
||||
const { getNodeData } = useNodeUpdate(nodeId)
|
||||
const getMemoryUsedDetector = useCallback((chatVar: MemoryVariable) => {
|
||||
const nodeData = getNodeData()!
|
||||
const valueSelector = ['memory_block', chatVar.node_id ? `${chatVar.node_id}_${chatVar.id}` : chatVar.id]
|
||||
return findUsedVarNodes(
|
||||
valueSelector,
|
||||
[nodeData],
|
||||
)
|
||||
}, [getNodeData])
|
||||
|
||||
return {
|
||||
getMemoryUsedDetector,
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import {
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import {
|
||||
useStore,
|
||||
useWorkflowStore,
|
||||
} from '@/app/components/workflow/store'
|
||||
import type { MemoryVariable } from '@/app/components/workflow/types'
|
||||
import { useNodesSyncDraft } from '@/app/components/workflow/hooks/use-nodes-sync-draft'
|
||||
import { useMemoryUsedDetector } from './use-memory-used-detector'
|
||||
|
||||
export const useMemoryVariables = (nodeId: string) => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const memoryVariables = useStore(s => s.memoryVariables)
|
||||
const [editMemoryVariable, setEditMemoryVariable] = useState<MemoryVariable | undefined>(undefined)
|
||||
const { handleSyncWorkflowDraft } = useNodesSyncDraft()
|
||||
const { getMemoryUsedDetector } = useMemoryUsedDetector(nodeId)
|
||||
const [cacheForDeleteMemoryVariable, setCacheForDeleteMemoryVariable] = useState<MemoryVariable | undefined>(undefined)
|
||||
|
||||
const memoryVariablesInUsed = useMemo(() => {
|
||||
return memoryVariables.filter(variable => variable.node_id === nodeId)
|
||||
}, [memoryVariables, nodeId])
|
||||
|
||||
const handleSave = useCallback((newMemoryVar: MemoryVariable) => {
|
||||
const { memoryVariables, setMemoryVariables } = workflowStore.getState()
|
||||
const newList = [newMemoryVar, ...memoryVariables]
|
||||
setMemoryVariables(newList)
|
||||
handleSyncWorkflowDraft()
|
||||
}, [handleSyncWorkflowDraft, workflowStore])
|
||||
|
||||
const handleSetEditMemoryVariable = (memoryVariableId?: string) => {
|
||||
if (!memoryVariableId) {
|
||||
setEditMemoryVariable(undefined)
|
||||
return
|
||||
}
|
||||
const memoryVariable = memoryVariables.find(variable => variable.id === memoryVariableId)
|
||||
setEditMemoryVariable(memoryVariable)
|
||||
}
|
||||
|
||||
const handleEdit = (memoryVariable: MemoryVariable) => {
|
||||
const { memoryVariables, setMemoryVariables } = workflowStore.getState()
|
||||
const newList = memoryVariables.map(variable => variable.id === memoryVariable.id ? memoryVariable : variable)
|
||||
setMemoryVariables(newList)
|
||||
handleSyncWorkflowDraft()
|
||||
}
|
||||
|
||||
const handleDeleteConfirm = (memoryVariableId?: string) => {
|
||||
const { memoryVariables, setMemoryVariables } = workflowStore.getState()
|
||||
const newList = memoryVariables.filter(variable => variable.id !== memoryVariableId)
|
||||
setMemoryVariables(newList)
|
||||
handleSyncWorkflowDraft()
|
||||
setCacheForDeleteMemoryVariable(undefined)
|
||||
}
|
||||
|
||||
const handleDelete = (memoryVariable: MemoryVariable) => {
|
||||
const effectedNodes = getMemoryUsedDetector(memoryVariable)
|
||||
if (effectedNodes.length > 0) {
|
||||
setCacheForDeleteMemoryVariable(memoryVariable)
|
||||
return
|
||||
}
|
||||
handleDeleteConfirm(memoryVariable.id)
|
||||
}
|
||||
|
||||
return {
|
||||
memoryVariablesInUsed,
|
||||
handleDelete,
|
||||
handleSave,
|
||||
handleSetEditMemoryVariable,
|
||||
handleEdit,
|
||||
editMemoryVariable,
|
||||
handleDeleteConfirm,
|
||||
cacheForDeleteMemoryVariable,
|
||||
setCacheForDeleteMemoryVariable,
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
import {
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import type { LLMNodeType } from '../../../types'
|
||||
import { useNodeUpdate } from '@/app/components/workflow/nodes/_base/hooks/use-node-crud'
|
||||
import {
|
||||
MEMORY_DEFAULT,
|
||||
} from '../linear-memory'
|
||||
import type { Memory } from '@/app/components/workflow/types'
|
||||
import { MemoryMode } from '@/app/components/workflow/types'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { useMemoryUsedDetector } from './use-memory-used-detector'
|
||||
|
||||
export const useMemory = (
|
||||
id: string,
|
||||
data: LLMNodeType,
|
||||
) => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { memory } = data
|
||||
const initCollapsed = useMemo(() => {
|
||||
if (!memory?.enabled)
|
||||
return true
|
||||
|
||||
return false
|
||||
}, [memory])
|
||||
const [collapsed, setCollapsed] = useState(initCollapsed)
|
||||
const {
|
||||
getNodeData,
|
||||
handleNodeDataUpdate,
|
||||
} = useNodeUpdate(id)
|
||||
const [showTipsWhenMemoryModeBlockToLinear, setShowTipsWhenMemoryModeBlockToLinear] = useState(false)
|
||||
const { getMemoryUsedDetector } = useMemoryUsedDetector(id)
|
||||
|
||||
const handleMemoryTypeChange = useCallback((value: string) => {
|
||||
const nodeData = getNodeData()
|
||||
const { memory: memoryData = {} as Memory } = nodeData?.data as LLMNodeType
|
||||
|
||||
if (value === MemoryMode.disabled) {
|
||||
setCollapsed(true)
|
||||
handleNodeDataUpdate({
|
||||
memory: {
|
||||
...memoryData,
|
||||
enabled: false,
|
||||
mode: '',
|
||||
},
|
||||
})
|
||||
}
|
||||
if (value === MemoryMode.linear) {
|
||||
setCollapsed(false)
|
||||
handleNodeDataUpdate({
|
||||
memory: {
|
||||
...memoryData,
|
||||
enabled: true,
|
||||
mode: MemoryMode.linear,
|
||||
window: memoryData?.window || MEMORY_DEFAULT.window,
|
||||
query_prompt_template: memoryData?.query_prompt_template || MEMORY_DEFAULT.query_prompt_template,
|
||||
},
|
||||
})
|
||||
}
|
||||
if (value === MemoryMode.block) {
|
||||
setCollapsed(false)
|
||||
handleNodeDataUpdate({
|
||||
memory: {
|
||||
...memoryData,
|
||||
enabled: true,
|
||||
mode: MemoryMode.block,
|
||||
block_id: memoryData?.block_id || [],
|
||||
query_prompt_template: memoryData?.query_prompt_template || MEMORY_DEFAULT.query_prompt_template,
|
||||
},
|
||||
})
|
||||
}
|
||||
setShowTipsWhenMemoryModeBlockToLinear(false)
|
||||
}, [getNodeData, handleNodeDataUpdate])
|
||||
|
||||
const handleMemoryTypeChangeBefore = useCallback((value: string) => {
|
||||
const nodeData = getNodeData()
|
||||
const { memory: memoryData = {} as Memory } = nodeData?.data as LLMNodeType
|
||||
const { memoryVariables } = workflowStore.getState()
|
||||
|
||||
if (memoryData.mode === MemoryMode.block && value === MemoryMode.linear && nodeData) {
|
||||
const globalMemoryVariables = memoryVariables.filter(variable => variable.scope === 'app')
|
||||
const currentNodeMemoryVariables = memoryVariables.filter(variable => variable.node_id === id)
|
||||
const allMemoryVariables = [...globalMemoryVariables, ...currentNodeMemoryVariables]
|
||||
|
||||
for (const variable of allMemoryVariables) {
|
||||
const effectedNodes = getMemoryUsedDetector(variable)
|
||||
|
||||
if (effectedNodes.length > 0) {
|
||||
setShowTipsWhenMemoryModeBlockToLinear(true)
|
||||
return
|
||||
}
|
||||
}
|
||||
handleMemoryTypeChange(value)
|
||||
}
|
||||
else {
|
||||
handleMemoryTypeChange(value)
|
||||
}
|
||||
}, [getNodeData, workflowStore, handleMemoryTypeChange])
|
||||
|
||||
const handleUpdateMemory = useCallback((memory: Memory) => {
|
||||
handleNodeDataUpdate({
|
||||
memory,
|
||||
})
|
||||
}, [handleNodeDataUpdate])
|
||||
|
||||
const memoryType = useMemo(() => {
|
||||
if (!memory)
|
||||
return MemoryMode.disabled
|
||||
|
||||
if (!('enabled' in memory))
|
||||
return MemoryMode.linear
|
||||
|
||||
if (memory.enabled) {
|
||||
if (memory.mode === MemoryMode.linear)
|
||||
return MemoryMode.linear
|
||||
if (memory.mode === MemoryMode.block)
|
||||
return MemoryMode.block
|
||||
}
|
||||
else {
|
||||
return MemoryMode.disabled
|
||||
}
|
||||
}, [memory])
|
||||
|
||||
return {
|
||||
collapsed,
|
||||
setCollapsed,
|
||||
handleMemoryTypeChange,
|
||||
handleMemoryTypeChangeBefore,
|
||||
memoryType,
|
||||
handleUpdateMemory,
|
||||
showTipsWhenMemoryModeBlockToLinear,
|
||||
setShowTipsWhenMemoryModeBlockToLinear,
|
||||
}
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiAddLine } from '@remixicon/react'
|
||||
import Collapse from '@/app/components/workflow/nodes/_base/components/collapse'
|
||||
import type {
|
||||
Node,
|
||||
} from '@/app/components/workflow/types'
|
||||
import Divider from '@/app/components/base/divider'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import MemoryCreateButton from './memory-create-button'
|
||||
import MemorySelector from './memory-selector'
|
||||
import LinearMemory from './linear-memory'
|
||||
import type { Memory } from '@/app/components/workflow/types'
|
||||
import type { LLMNodeType } from '../../types'
|
||||
import { useMemory } from './hooks/use-memory'
|
||||
import Split from '@/app/components/workflow/nodes/_base/components/split'
|
||||
import { MemoryMode } from '@/app/components/workflow/types'
|
||||
import BlockMemory from './block-memory'
|
||||
import { ActionButton } from '@/app/components/base/action-button'
|
||||
import cn from '@/utils/classnames'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
|
||||
type MemoryProps = Pick<Node, 'id' | 'data'> & {
|
||||
readonly?: boolean
|
||||
canSetRoleName?: boolean
|
||||
}
|
||||
const MemorySystem = ({
|
||||
id,
|
||||
data,
|
||||
readonly,
|
||||
canSetRoleName,
|
||||
}: MemoryProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { memory } = data as LLMNodeType
|
||||
const {
|
||||
collapsed,
|
||||
setCollapsed,
|
||||
handleMemoryTypeChange,
|
||||
handleMemoryTypeChangeBefore,
|
||||
memoryType,
|
||||
handleUpdateMemory,
|
||||
showTipsWhenMemoryModeBlockToLinear,
|
||||
setShowTipsWhenMemoryModeBlockToLinear,
|
||||
} = useMemory(id, data as LLMNodeType)
|
||||
|
||||
const renderTrigger = useCallback((open?: boolean) => {
|
||||
return (
|
||||
<ActionButton className={cn('shrink-0', open && 'bg-state-base-hover')}>
|
||||
<RiAddLine className='h-4 w-4' />
|
||||
</ActionButton>
|
||||
)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className=''>
|
||||
<Collapse
|
||||
disabled={!memory?.enabled}
|
||||
collapsed={collapsed}
|
||||
onCollapse={setCollapsed}
|
||||
hideCollapseIcon
|
||||
triggerClassName='ml-0 system-sm-semibold-uppercase'
|
||||
trigger={
|
||||
collapseIcon => (
|
||||
<div className='flex grow items-center justify-between'>
|
||||
<div className='flex items-center'>
|
||||
<div className='system-sm-semibold-uppercase mr-0.5 text-text-secondary'>
|
||||
{t('workflow.nodes.common.memory.memory')}
|
||||
</div>
|
||||
<Tooltip
|
||||
popupContent={t('workflow.nodes.common.memory.memoryTip')}
|
||||
triggerClassName='w-4 h-4'
|
||||
/>
|
||||
{collapseIcon}
|
||||
{
|
||||
memoryType === MemoryMode.block && (
|
||||
<>
|
||||
<Divider type='vertical' className='!ml-1.5 !mr-1 h-3 !w-px bg-divider-regular' />
|
||||
<div onClick={e => e.stopPropagation()}>
|
||||
<MemoryCreateButton
|
||||
nodeId={id}
|
||||
renderTrigger={renderTrigger}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<MemorySelector
|
||||
value={memoryType}
|
||||
onSelected={handleMemoryTypeChangeBefore}
|
||||
readonly={readonly}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<>
|
||||
{
|
||||
memoryType === MemoryMode.linear && !collapsed && (
|
||||
<LinearMemory
|
||||
className='mt-2'
|
||||
payload={memory as Memory}
|
||||
onChange={handleUpdateMemory}
|
||||
readonly={readonly}
|
||||
canSetRoleName={canSetRoleName}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
memoryType === MemoryMode.block && !collapsed && (
|
||||
<BlockMemory id={id} payload={memory as Memory} />
|
||||
)
|
||||
}
|
||||
</>
|
||||
</Collapse>
|
||||
<Split className='mt-4' />
|
||||
</div>
|
||||
{
|
||||
showTipsWhenMemoryModeBlockToLinear && (
|
||||
<Confirm
|
||||
isShow
|
||||
type='info'
|
||||
showCancel={false}
|
||||
onCancel={() => setShowTipsWhenMemoryModeBlockToLinear(false)}
|
||||
onConfirm={() => handleMemoryTypeChange(MemoryMode.linear)}
|
||||
confirmText={t('workflow.nodes.common.memory.toLinearConfirmButton')}
|
||||
title={t('workflow.nodes.common.memory.toLinearConfirmTitle')}
|
||||
content={t('workflow.nodes.common.memory.toLinearConfirmDesc')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(MemorySystem)
|
||||
@@ -1,182 +0,0 @@
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import { produce } from 'immer'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import Slider from '@/app/components/base/slider'
|
||||
import Input from '@/app/components/base/input'
|
||||
import type { Memory } from '@/app/components/workflow/types'
|
||||
import { MemoryRole } from '@/app/components/workflow/types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
const WINDOW_SIZE_MIN = 1
|
||||
const WINDOW_SIZE_MAX = 100
|
||||
export const WINDOW_SIZE_DEFAULT = 50
|
||||
export const MEMORY_DEFAULT: Memory = {
|
||||
window: { enabled: false, size: WINDOW_SIZE_DEFAULT },
|
||||
query_prompt_template: '{{#sys.query#}}\n\n{{#sys.files#}}',
|
||||
}
|
||||
type RoleItemProps = {
|
||||
readonly?: boolean
|
||||
title: string
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
}
|
||||
const RoleItem = ({
|
||||
readonly,
|
||||
title,
|
||||
value,
|
||||
onChange,
|
||||
}: RoleItemProps) => {
|
||||
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onChange(e.target.value)
|
||||
}, [onChange])
|
||||
return (
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='text-[13px] font-normal text-text-secondary'>{title}</div>
|
||||
<Input
|
||||
readOnly={readonly}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
className='h-8 w-[200px]'
|
||||
type='text' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type LinearMemoryProps = {
|
||||
payload: Memory
|
||||
readonly?: boolean
|
||||
onChange: (payload: Memory) => void
|
||||
canSetRoleName?: boolean
|
||||
className?: string
|
||||
}
|
||||
const LinearMemory = ({
|
||||
payload,
|
||||
readonly,
|
||||
onChange,
|
||||
canSetRoleName,
|
||||
className,
|
||||
}: LinearMemoryProps) => {
|
||||
const i18nPrefix = 'workflow.nodes.common.memory'
|
||||
const { t } = useTranslation()
|
||||
const handleWindowEnabledChange = useCallback((enabled: boolean) => {
|
||||
const newPayload = produce(payload || MEMORY_DEFAULT, (draft) => {
|
||||
if (!draft.window)
|
||||
draft.window = { enabled: false, size: WINDOW_SIZE_DEFAULT }
|
||||
|
||||
draft.window.enabled = enabled
|
||||
})
|
||||
|
||||
onChange(newPayload)
|
||||
}, [payload, onChange])
|
||||
|
||||
const handleWindowSizeChange = useCallback((size: number | string) => {
|
||||
const newPayload = produce(payload || MEMORY_DEFAULT, (draft) => {
|
||||
if (!draft.window)
|
||||
draft.window = { enabled: true, size: WINDOW_SIZE_DEFAULT }
|
||||
let limitedSize: null | string | number = size
|
||||
if (limitedSize === '') {
|
||||
limitedSize = null
|
||||
}
|
||||
else {
|
||||
limitedSize = Number.parseInt(limitedSize as string, 10)
|
||||
if (Number.isNaN(limitedSize))
|
||||
limitedSize = WINDOW_SIZE_DEFAULT
|
||||
|
||||
if (limitedSize < WINDOW_SIZE_MIN)
|
||||
limitedSize = WINDOW_SIZE_MIN
|
||||
|
||||
if (limitedSize > WINDOW_SIZE_MAX)
|
||||
limitedSize = WINDOW_SIZE_MAX
|
||||
}
|
||||
|
||||
draft.window.size = limitedSize as number
|
||||
})
|
||||
onChange(newPayload)
|
||||
}, [payload, onChange])
|
||||
|
||||
const handleBlur = useCallback(() => {
|
||||
if (!payload)
|
||||
return
|
||||
|
||||
if (payload.window.size === '' || payload.window.size === null)
|
||||
handleWindowSizeChange(WINDOW_SIZE_DEFAULT)
|
||||
}, [handleWindowSizeChange, payload])
|
||||
const handleRolePrefixChange = useCallback((role: MemoryRole) => {
|
||||
return (value: string) => {
|
||||
const newPayload = produce(payload || MEMORY_DEFAULT, (draft) => {
|
||||
if (!draft.role_prefix) {
|
||||
draft.role_prefix = {
|
||||
user: '',
|
||||
assistant: '',
|
||||
}
|
||||
}
|
||||
draft.role_prefix[role] = value
|
||||
})
|
||||
onChange(newPayload)
|
||||
}
|
||||
}, [payload, onChange])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cn('flex justify-between', className)}>
|
||||
<div className='flex h-8 items-center space-x-2'>
|
||||
<Switch
|
||||
defaultValue={payload?.window?.enabled}
|
||||
onChange={handleWindowEnabledChange}
|
||||
size='md'
|
||||
disabled={readonly}
|
||||
/>
|
||||
<div className='system-xs-medium-uppercase text-text-tertiary'>{t(`${i18nPrefix}.windowSize`)}</div>
|
||||
</div>
|
||||
<div className='flex h-8 items-center space-x-2'>
|
||||
<Slider
|
||||
className='w-[144px]'
|
||||
value={(payload.window?.size || WINDOW_SIZE_DEFAULT) as number}
|
||||
min={WINDOW_SIZE_MIN}
|
||||
max={WINDOW_SIZE_MAX}
|
||||
step={1}
|
||||
onChange={handleWindowSizeChange}
|
||||
disabled={readonly || !payload.window?.enabled}
|
||||
/>
|
||||
<Input
|
||||
value={(payload.window?.size || WINDOW_SIZE_DEFAULT) as number}
|
||||
wrapperClassName='w-12'
|
||||
className='appearance-none pr-0'
|
||||
type='number'
|
||||
min={WINDOW_SIZE_MIN}
|
||||
max={WINDOW_SIZE_MAX}
|
||||
step={1}
|
||||
onChange={e => handleWindowSizeChange(e.target.value)}
|
||||
onBlur={handleBlur}
|
||||
disabled={readonly || !payload.window?.enabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{canSetRoleName && (
|
||||
<div className='mt-4'>
|
||||
<div className='text-xs font-medium uppercase leading-6 text-text-tertiary'>{t(`${i18nPrefix}.conversationRoleName`)}</div>
|
||||
<div className='mt-1 space-y-2'>
|
||||
<RoleItem
|
||||
readonly={!!readonly}
|
||||
title={t(`${i18nPrefix}.user`)}
|
||||
value={payload.role_prefix?.user || ''}
|
||||
onChange={handleRolePrefixChange(MemoryRole.user)}
|
||||
/>
|
||||
<RoleItem
|
||||
readonly={!!readonly}
|
||||
title={t(`${i18nPrefix}.assistant`)}
|
||||
value={payload.role_prefix?.assistant || ''}
|
||||
onChange={handleRolePrefixChange(MemoryRole.assistant)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(LinearMemory)
|
||||
@@ -1,70 +0,0 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
import VariableModal from '@/app/components/workflow/panel/chat-variable-panel/components/variable-modal'
|
||||
import type { OffsetOptions, Placement } from '@floating-ui/react'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import type { MemoryVariable } from '@/app/components/workflow/types'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { MEMORY_VAR_CREATED_BY_MODAL_BY_EVENT_EMITTER, MEMORY_VAR_MODAL_SHOW_BY_EVENT_EMITTER } from '@/app/components/workflow/nodes/_base/components/prompt/type'
|
||||
import { useMemoryVariables } from './hooks/use-memory-variables'
|
||||
|
||||
type Props = {
|
||||
placement?: Placement
|
||||
offset?: number | OffsetOptions
|
||||
hideTrigger?: boolean
|
||||
instanceId?: string
|
||||
nodeId: string
|
||||
renderTrigger?: (open?: boolean) => React.ReactNode
|
||||
}
|
||||
|
||||
const MemoryCreateButton = ({
|
||||
placement,
|
||||
offset,
|
||||
instanceId,
|
||||
nodeId,
|
||||
renderTrigger = () => <div></div>,
|
||||
}: Props) => {
|
||||
const { eventEmitter } = useEventEmitterContextContext()
|
||||
const [open, setOpen] = useState(false)
|
||||
const { handleSave: handleSaveMemoryVariables } = useMemoryVariables(nodeId)
|
||||
|
||||
const handleSave = useCallback((newMemoryVar: MemoryVariable) => {
|
||||
handleSaveMemoryVariables(newMemoryVar)
|
||||
if (instanceId)
|
||||
eventEmitter?.emit({ type: MEMORY_VAR_CREATED_BY_MODAL_BY_EVENT_EMITTER, instanceId, variable: ['memory', newMemoryVar.name] } as any)
|
||||
}, [handleSaveMemoryVariables, eventEmitter, instanceId])
|
||||
|
||||
eventEmitter?.useSubscription((v: any) => {
|
||||
if (v.type === MEMORY_VAR_MODAL_SHOW_BY_EVENT_EMITTER && v.instanceId === instanceId)
|
||||
setOpen(true)
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement={placement || 'left'}
|
||||
offset={offset}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||
{renderTrigger?.(open)}
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[11]'>
|
||||
<VariableModal
|
||||
onSave={handleSave}
|
||||
onClose={() => {
|
||||
setOpen(false)
|
||||
}}
|
||||
nodeScopeMemoryVariable={nodeId ? { nodeId } : undefined}
|
||||
/>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default MemoryCreateButton
|
||||
@@ -1,107 +0,0 @@
|
||||
import {
|
||||
memo,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
RiCheckLine,
|
||||
} from '@remixicon/react'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { MemoryMode } from '@/app/components/workflow/types'
|
||||
|
||||
type MemorySelectorProps = {
|
||||
value?: string
|
||||
onSelected: (value: string) => void
|
||||
readonly?: boolean
|
||||
}
|
||||
const MemorySelector = ({
|
||||
value,
|
||||
onSelected,
|
||||
readonly,
|
||||
}: MemorySelectorProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
const options = [
|
||||
{
|
||||
value: MemoryMode.disabled,
|
||||
label: t('workflow.nodes.common.memory.disabled.title'),
|
||||
description: t('workflow.nodes.common.memory.disabled.desc'),
|
||||
},
|
||||
{
|
||||
value: MemoryMode.linear,
|
||||
label: t('workflow.nodes.common.memory.linear.title'),
|
||||
description: t('workflow.nodes.common.memory.linear.desc'),
|
||||
},
|
||||
{
|
||||
value: MemoryMode.block,
|
||||
label: t('workflow.nodes.common.memory.block.title'),
|
||||
description: t('workflow.nodes.common.memory.block.desc'),
|
||||
},
|
||||
]
|
||||
const selectedOption = options.find(option => option.value === value)
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-end'
|
||||
offset={4}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={(e) => {
|
||||
if (readonly)
|
||||
return
|
||||
e.stopPropagation()
|
||||
e.nativeEvent.stopImmediatePropagation()
|
||||
setOpen(v => !v)
|
||||
}}>
|
||||
<Button
|
||||
size='small'
|
||||
disabled={readonly}
|
||||
>
|
||||
{selectedOption?.label || t('workflow.nodes.common.memory.disabled.title')}
|
||||
<RiArrowDownSLine className='h-3.5 w-3.5' />
|
||||
</Button>
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[11]'>
|
||||
<div className='w-[280px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'>
|
||||
{
|
||||
options.map(option => (
|
||||
<div
|
||||
key={option.value}
|
||||
className='flex cursor-pointer rounded-lg p-2 pr-3 hover:bg-state-base-hover'
|
||||
onClick={(e) => {
|
||||
if (readonly)
|
||||
return
|
||||
e.stopPropagation()
|
||||
e.nativeEvent.stopImmediatePropagation()
|
||||
onSelected(option.value)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
<div className='mr-1 w-4 shrink-0'>
|
||||
{
|
||||
value === option.value && (
|
||||
<RiCheckLine className='h-4 w-4 text-text-accent' />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className='grow'>
|
||||
<div className='system-sm-semibold mb-0.5 text-text-secondary'>{option.label}</div>
|
||||
<div className='system-xs-regular text-text-tertiary'>{option.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(MemorySelector)
|
||||
@@ -18,7 +18,6 @@ type Props = {
|
||||
nodeId: string
|
||||
editorId?: string
|
||||
currentPrompt?: string
|
||||
isBasicMode?: boolean
|
||||
}
|
||||
|
||||
const PromptGeneratorBtn: FC<Props> = ({
|
||||
@@ -27,7 +26,6 @@ const PromptGeneratorBtn: FC<Props> = ({
|
||||
nodeId,
|
||||
editorId,
|
||||
currentPrompt,
|
||||
isBasicMode,
|
||||
}) => {
|
||||
const [showAutomatic, { setTrue: showAutomaticTrue, setFalse: showAutomaticFalse }] = useBoolean(false)
|
||||
const handleAutomaticRes = useCallback((res: GenRes) => {
|
||||
@@ -52,8 +50,6 @@ const PromptGeneratorBtn: FC<Props> = ({
|
||||
nodeId={nodeId}
|
||||
editorId={editorId}
|
||||
currentPrompt={currentPrompt}
|
||||
isBasicMode={isBasicMode}
|
||||
hideTryIt
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import MemoryConfig from '../_base/components/memory-config'
|
||||
import VarReferencePicker from '../_base/components/variable/var-reference-picker'
|
||||
import ConfigVision from '../_base/components/config-vision'
|
||||
import useConfig from './use-config'
|
||||
@@ -21,9 +22,6 @@ import Switch from '@/app/components/base/switch'
|
||||
import { RiAlertFill, RiQuestionLine } from '@remixicon/react'
|
||||
import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import MemorySystem from './components/memory-system'
|
||||
import { useMemory } from './components/memory-system/hooks/use-memory'
|
||||
import { MemoryMode } from '@/app/components/workflow/types'
|
||||
|
||||
const i18nPrefix = 'workflow.nodes.llm'
|
||||
|
||||
@@ -55,6 +53,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
handleVarListChange,
|
||||
handleVarNameChange,
|
||||
handleSyeQueryChange,
|
||||
handleMemoryChange,
|
||||
handleVisionResolutionEnabledChange,
|
||||
handleVisionResolutionChange,
|
||||
isModelSupportStructuredOutput,
|
||||
@@ -65,7 +64,6 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
filterJinja2InputVar,
|
||||
handleReasoningFormatChange,
|
||||
} = useConfig(id, data)
|
||||
const { memoryType } = useMemory(id, data)
|
||||
|
||||
const model = inputs.model
|
||||
|
||||
@@ -153,7 +151,6 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
varList={inputs.prompt_config?.jinja2_variables || []}
|
||||
handleAddVariable={handleAddVariable}
|
||||
modelConfig={model}
|
||||
isMemorySupported={memoryType === MemoryMode.block}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -177,7 +174,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
)}
|
||||
|
||||
{/* Memory put place examples. */}
|
||||
{isChatMode && isChatModel && (memoryType === MemoryMode.linear || memoryType === MemoryMode.block) && (
|
||||
{isChatMode && isChatModel && !!inputs.memory && (
|
||||
<div className='mt-4'>
|
||||
<div className='flex h-8 items-center justify-between rounded-lg bg-components-input-bg-normal pl-3 pr-2'>
|
||||
<div className='flex items-center space-x-1'>
|
||||
@@ -201,7 +198,7 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
triggerClassName='w-4 h-4'
|
||||
/>
|
||||
</div>}
|
||||
value={inputs.memory?.query_prompt_template || '{{#sys.query#}}'}
|
||||
value={inputs.memory.query_prompt_template || '{{#sys.query#}}'}
|
||||
onChange={handleSyeQueryChange}
|
||||
readOnly={readOnly}
|
||||
isShowContext={false}
|
||||
@@ -211,10 +208,9 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
nodesOutputVars={availableVars}
|
||||
availableNodes={availableNodesWithParent}
|
||||
isSupportFileVar
|
||||
instanceId={`${id}-chat-workflow-llm-prompt-editor-user`}
|
||||
/>
|
||||
|
||||
{inputs.memory?.query_prompt_template && !inputs.memory.query_prompt_template.includes('{{#sys.query#}}') && (
|
||||
{inputs.memory.query_prompt_template && !inputs.memory.query_prompt_template.includes('{{#sys.query#}}') && (
|
||||
<div className='text-xs font-normal leading-[18px] text-[#DC6803]'>{t(`${i18nPrefix}.sysQueryInUser`)}</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -224,10 +220,11 @@ const Panel: FC<NodePanelProps<LLMNodeType>> = ({
|
||||
{/* Memory */}
|
||||
{isChatMode && (
|
||||
<>
|
||||
<MemorySystem
|
||||
id={id}
|
||||
data={data}
|
||||
<Split />
|
||||
<MemoryConfig
|
||||
readonly={readOnly}
|
||||
config={{ data: inputs.memory }}
|
||||
onChange={handleMemoryChange}
|
||||
canSetRoleName={isCompletionModel}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -308,11 +308,11 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
||||
}, [setInputs, deleteNodeInspectorVars, id])
|
||||
|
||||
const filterInputVar = useCallback((varPayload: Var) => {
|
||||
return [VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.file, VarType.arrayFile, VarType.memory].includes(varPayload.type)
|
||||
return [VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.file, VarType.arrayFile].includes(varPayload.type)
|
||||
}, [])
|
||||
|
||||
const filterJinja2InputVar = useCallback((varPayload: Var) => {
|
||||
return [VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.arrayBoolean, VarType.arrayObject, VarType.object, VarType.array, VarType.boolean, VarType.memory].includes(varPayload.type)
|
||||
return [VarType.number, VarType.string, VarType.secret, VarType.arrayString, VarType.arrayNumber, VarType.arrayBoolean, VarType.arrayObject, VarType.object, VarType.array, VarType.boolean].includes(varPayload.type)
|
||||
}, [])
|
||||
|
||||
const filterMemoryPromptVar = useCallback((varPayload: Var) => {
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
arrayObjectPlaceholder,
|
||||
arrayStringPlaceholder,
|
||||
objectPlaceholder,
|
||||
} from '@/app/components/workflow/panel/chat-variable-panel/constants'
|
||||
} from '@/app/components/workflow/panel/chat-variable-panel/utils'
|
||||
import ArrayBoolList from '@/app/components/workflow/panel/chat-variable-panel/components/array-bool-list'
|
||||
|
||||
type FormItemProps = {
|
||||
|
||||
@@ -38,7 +38,7 @@ const useConfig = (id: string, payload: LoopNodeType) => {
|
||||
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
||||
const isChatMode = useIsChatMode()
|
||||
const conversationVariables = useStore(s => s.conversationVariables)
|
||||
const memoryVariables = useStore(s => s.memoryVariables)
|
||||
|
||||
const { inputs, setInputs } = useNodeCrud<LoopNodeType>(id, payload)
|
||||
const inputsRef = useRef(inputs)
|
||||
const handleInputsChange = useCallback((newInputs: LoopNodeType) => {
|
||||
@@ -65,7 +65,7 @@ const useConfig = (id: string, payload: LoopNodeType) => {
|
||||
mcpTools: mcpTools || [],
|
||||
dataSourceList: dataSourceList || [],
|
||||
}
|
||||
const childrenNodeVars = toNodeOutputVars(loopChildrenNodes, isChatMode, undefined, [], conversationVariables, memoryVariables, [], allPluginInfoList)
|
||||
const childrenNodeVars = toNodeOutputVars(loopChildrenNodes, isChatMode, undefined, [], conversationVariables, [], allPluginInfoList)
|
||||
|
||||
const {
|
||||
getIsVarFileAttribute,
|
||||
|
||||
@@ -11,7 +11,7 @@ const VALID_PARAMETER_TYPES: readonly VarType[] = [
|
||||
] as const
|
||||
|
||||
// Type display name mappings
|
||||
const TYPE_DISPLAY_NAMES: Record<Exclude<VarType, VarType.memory>, string> = {
|
||||
const TYPE_DISPLAY_NAMES: Record<VarType, string> = {
|
||||
[VarType.string]: 'String',
|
||||
[VarType.number]: 'Number',
|
||||
[VarType.boolean]: 'Boolean',
|
||||
@@ -91,7 +91,7 @@ export const normalizeParameterType = (input: string | undefined | null): VarTyp
|
||||
/**
|
||||
* Gets display name for parameter types in UI components
|
||||
*/
|
||||
export const getParameterTypeDisplayName = (type: Exclude<VarType, VarType.memory>): string => {
|
||||
export const getParameterTypeDisplayName = (type: VarType): string => {
|
||||
return TYPE_DISPLAY_NAMES[type]
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ export const createParameterTypeOptions = (contentType?: string) => {
|
||||
const availableTypes = getAvailableParameterTypes(contentType)
|
||||
|
||||
return availableTypes.map(type => ({
|
||||
name: getParameterTypeDisplayName(type as Exclude<VarType, VarType.memory>),
|
||||
name: getParameterTypeDisplayName(type),
|
||||
value: type,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ const ArrayValueList: FC<Props> = ({
|
||||
<div className='flex items-center space-x-1' key={index}>
|
||||
<Input
|
||||
placeholder={t('workflow.chatVariable.modal.arrayValue') || ''}
|
||||
value={list[index] || ''}
|
||||
value={list[index]}
|
||||
onChange={handleNameChange(index)}
|
||||
type={isString ? 'text' : 'number'}
|
||||
/>
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
RiCheckLine,
|
||||
} from '@remixicon/react'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import {
|
||||
useStore,
|
||||
} from 'reactflow'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
value: string
|
||||
onChange: (value: string) => void
|
||||
nodeType?: BlockEnum
|
||||
}
|
||||
|
||||
const NodeSelector: FC<Props> = ({
|
||||
value,
|
||||
onChange,
|
||||
nodeType = BlockEnum.LLM,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const filteredNodes = useStore(useShallow((s) => {
|
||||
const nodes = [...s.nodeInternals.values()]
|
||||
return nodes.filter(node => node.data?.type === nodeType)
|
||||
}))
|
||||
|
||||
const currentNode = useMemo(() => filteredNodes.find(node => node.id === value), [filteredNodes, value])
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='bottom-end'
|
||||
offset={4}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||
{currentNode && (
|
||||
<div className={cn('flex h-8 w-[208px] cursor-pointer items-center rounded-lg bg-components-input-bg-normal px-2 py-1 pl-3 hover:bg-state-base-hover-alt', open && 'bg-state-base-hover-alt')}>
|
||||
<BlockIcon className={cn('mr-1.5 h-4 w-4 shrink-0')} type={currentNode.data?.type} />
|
||||
<div className='system-sm-regular grow truncate text-components-input-text-filled'>{currentNode.data?.title}</div>
|
||||
</div>
|
||||
)}
|
||||
{!currentNode && (
|
||||
<div className={cn('flex h-8 w-[208px] cursor-pointer items-center gap-1 rounded-lg bg-components-input-bg-normal px-2 py-1 pl-3 hover:bg-state-base-hover-alt', open && 'bg-state-base-hover-alt')}>
|
||||
<div className='system-sm-regular grow truncate text-components-input-text-placeholder'>{t('workflow.chatVariable.modal.selectNode')}</div>
|
||||
<RiArrowDownSLine className='h-4 w-4 text-text-quaternary' />
|
||||
</div>
|
||||
)}
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-[25]'>
|
||||
<div className='w-[209px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-1 shadow-lg'>
|
||||
{filteredNodes.map(node => (
|
||||
<div key={node.id} className='flex cursor-pointer items-center rounded-lg px-2 py-1 hover:bg-state-base-hover' onClick={() => onChange(node.id)}>
|
||||
<BlockIcon className={cn('mr-2 shrink-0')} type={node.data?.type} />
|
||||
<div className='system-sm-medium grow text-text-secondary'>{node.data?.title}</div>
|
||||
{currentNode?.id === node.id && (
|
||||
<RiCheckLine className='h-4 w-4 shrink-0 text-text-accent' />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
)
|
||||
}
|
||||
export default React.memo(NodeSelector)
|
||||
@@ -1,54 +0,0 @@
|
||||
import { memo } from 'react'
|
||||
import { useStore } from 'reactflow'
|
||||
import VariableItem from './variable-item'
|
||||
import BlockIcon from '@/app/components/workflow/block-icon'
|
||||
import { BlockEnum } from '@/app/components/workflow/types'
|
||||
import type { MemoryVariable } from '@/app/components/workflow/types'
|
||||
|
||||
type VariableItemWithNodeProps = {
|
||||
nodeId: string
|
||||
memoryVariables: MemoryVariable[]
|
||||
onEdit: (memoryVariable: MemoryVariable) => void
|
||||
onDelete: (memoryVariable: MemoryVariable) => void
|
||||
currentVarId?: string
|
||||
}
|
||||
const VariableItemWithNode = ({
|
||||
nodeId,
|
||||
memoryVariables,
|
||||
onEdit,
|
||||
onDelete,
|
||||
currentVarId,
|
||||
}: VariableItemWithNodeProps) => {
|
||||
const currentNode = useStore(s => s.nodeInternals.get(nodeId))
|
||||
|
||||
if (!currentNode) return null
|
||||
|
||||
return (
|
||||
<div className='space-y-1 py-1'>
|
||||
<div className='mb-1 flex items-center'>
|
||||
<BlockIcon className='mr-1.5 shrink-0' type={BlockEnum.LLM} />
|
||||
<div
|
||||
className='system-sm-medium grow truncate text-text-secondary'
|
||||
title={currentNode?.data.title}
|
||||
>
|
||||
{currentNode?.data.title}
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
memoryVariables.map(memoryVariable => (
|
||||
<VariableItem
|
||||
key={memoryVariable.id}
|
||||
item={memoryVariable}
|
||||
onEdit={onEdit}
|
||||
onDelete={onDelete}
|
||||
scope='node'
|
||||
term={memoryVariable.term}
|
||||
currentVarId={currentVarId}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(VariableItemWithNode)
|
||||
@@ -1,87 +1,47 @@
|
||||
import { memo, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { memo, useState } from 'react'
|
||||
import { capitalize } from 'lodash-es'
|
||||
import { RiDeleteBinLine, RiEditLine } from '@remixicon/react'
|
||||
import {
|
||||
BubbleX,
|
||||
Memory,
|
||||
} from '@/app/components/base/icons/src/vender/line/others'
|
||||
import type { ConversationVariable, MemoryVariable } from '@/app/components/workflow/types'
|
||||
import { BubbleX } from '@/app/components/base/icons/src/vender/line/others'
|
||||
import type { ConversationVariable } from '@/app/components/workflow/types'
|
||||
import cn from '@/utils/classnames'
|
||||
import { ChatVarType } from '../type'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
|
||||
type VariableItemProps = {
|
||||
item: ConversationVariable | MemoryVariable
|
||||
onEdit: (item: ConversationVariable | MemoryVariable) => void
|
||||
onDelete: (item: ConversationVariable | MemoryVariable) => void
|
||||
scope?: string
|
||||
term?: string
|
||||
currentVarId?: string
|
||||
item: ConversationVariable
|
||||
onEdit: (item: ConversationVariable) => void
|
||||
onDelete: (item: ConversationVariable) => void
|
||||
}
|
||||
|
||||
const VariableItem = ({
|
||||
item,
|
||||
onEdit,
|
||||
onDelete,
|
||||
scope,
|
||||
term,
|
||||
currentVarId,
|
||||
}: VariableItemProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [destructive, setDestructive] = useState(false)
|
||||
const valueType = useMemo(() => {
|
||||
if (item.value_type === ChatVarType.Memory)
|
||||
return 'memory'
|
||||
return item.value_type
|
||||
}, [item.value_type])
|
||||
return (
|
||||
<div className={cn(
|
||||
'radius-md mb-1 border border-components-panel-border-subtle bg-components-panel-on-panel-item-bg px-2.5 py-2 shadow-xs hover:bg-components-panel-on-panel-item-bg-hover',
|
||||
destructive && 'border-state-destructive-border hover:bg-state-destructive-hover',
|
||||
)}>
|
||||
<div className='group flex items-center justify-between'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<div className='flex grow items-center gap-1'>
|
||||
{
|
||||
item.value_type === ChatVarType.Memory && (
|
||||
<Memory className='h-4 w-4 text-util-colors-teal-teal-700' />
|
||||
)
|
||||
}
|
||||
{
|
||||
item.value_type !== ChatVarType.Memory && (
|
||||
<BubbleX className='h-4 w-4 text-util-colors-teal-teal-700' />
|
||||
)
|
||||
}
|
||||
<BubbleX className='h-4 w-4 text-util-colors-teal-teal-700' />
|
||||
<div className='system-sm-medium text-text-primary'>{item.name}</div>
|
||||
<div className='system-xs-medium text-text-tertiary'>{capitalize(valueType)}</div>
|
||||
<div className='system-xs-medium text-text-tertiary'>{capitalize(item.value_type)}</div>
|
||||
</div>
|
||||
<div className='flex shrink-0 items-center gap-1 text-text-tertiary'>
|
||||
<div className={cn('radius-md hidden cursor-pointer p-1 hover:bg-state-base-hover hover:text-text-secondary group-hover:block', currentVarId === item.id && 'block bg-state-base-hover text-text-secondary')}>
|
||||
<div className='radius-md cursor-pointer p-1 hover:bg-state-base-hover hover:text-text-secondary'>
|
||||
<RiEditLine className='h-4 w-4' onClick={() => onEdit(item)}/>
|
||||
</div>
|
||||
<div
|
||||
className={cn('radius-md hidden cursor-pointer p-1 hover:bg-state-destructive-hover hover:text-text-destructive group-hover:block', currentVarId === item.id && 'block')}
|
||||
className='radius-md cursor-pointer p-1 hover:bg-state-destructive-hover hover:text-text-destructive'
|
||||
onMouseOver={() => setDestructive(true)}
|
||||
onMouseOut={() => setDestructive(false)}
|
||||
>
|
||||
<RiDeleteBinLine className='h-4 w-4' onClick={() => onDelete(item)}/>
|
||||
</div>
|
||||
<div className={cn('flex h-6 items-center gap-0.5 group-hover:hidden', currentVarId === item.id && 'hidden')}>
|
||||
{scope === 'app' && <Badge text={'conv'} />}
|
||||
{term && <Badge text={term} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
'description' in item && item.description && (
|
||||
<div className='system-xs-regular truncate text-text-tertiary'>{item.description}</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
scope === 'app' && (
|
||||
<div className='system-xs-regular truncate text-text-tertiary'>{t('workflow.chatVariable.appScopeText')}</div>
|
||||
)
|
||||
}
|
||||
<div className='system-xs-regular truncate text-text-tertiary'>{item.description}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,15 +9,15 @@ import {
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import type { ConversationVariable, MemoryVariable } from '@/app/components/workflow/types'
|
||||
import type { ConversationVariable } from '@/app/components/workflow/types'
|
||||
|
||||
type Props = {
|
||||
open: boolean
|
||||
setOpen: (value: React.SetStateAction<boolean>) => void
|
||||
showTip: boolean
|
||||
chatVar?: ConversationVariable | MemoryVariable
|
||||
chatVar?: ConversationVariable
|
||||
onClose: () => void
|
||||
onSave: (env: ConversationVariable | MemoryVariable) => void
|
||||
onSave: (env: ConversationVariable) => void
|
||||
}
|
||||
|
||||
const VariableModalTrigger = ({
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user