Merge branch 'main' into feat/rag-2

This commit is contained in:
twwu
2025-08-14 15:03:48 +08:00
54 changed files with 1474 additions and 306 deletions

View File

@ -35,7 +35,7 @@ import type { AppDetailResponse } from '@/models/app'
import { useAppContext } from '@/context/app-context'
import type { AppSSO } from '@/types/app'
import Indicator from '@/app/components/header/indicator'
import { fetchAppDetail } from '@/service/apps'
import { fetchAppDetailDirect } from '@/service/apps'
import { AccessMode } from '@/models/access-control'
import AccessControl from '../app-access-control'
import { useAppWhiteListSubjects } from '@/service/access-control'
@ -161,11 +161,15 @@ function AppCard({
return
setShowAccessControl(true)
}, [appDetail])
const handleAccessControlUpdate = useCallback(() => {
fetchAppDetail({ url: '/apps', id: appDetail!.id }).then((res) => {
const handleAccessControlUpdate = useCallback(async () => {
try {
const res = await fetchAppDetailDirect({ url: '/apps', id: appDetail!.id })
setAppDetail(res)
setShowAccessControl(false)
})
}
catch (error) {
console.error('Failed to fetch app detail:', error)
}
}, [appDetail, setAppDetail])
return (

View File

@ -123,7 +123,7 @@ const Chart: React.FC<IChartProps> = ({
dimensions: ['date', yField],
source: statistics,
},
grid: { top: 8, right: 36, bottom: 0, left: 0, containLabel: true },
grid: { top: 8, right: 36, bottom: 10, left: 25, containLabel: true },
tooltip: {
trigger: 'item',
position: 'top',
@ -165,7 +165,7 @@ const Chart: React.FC<IChartProps> = ({
lineStyle: {
color: COMMON_COLOR_MAP.splitLineDark,
},
interval(index, value) {
interval(_index, value) {
return !!value
},
},
@ -242,7 +242,7 @@ const Chart: React.FC<IChartProps> = ({
? ''
: <span>{t('appOverview.analysis.tokenUsage.consumed')} Tokens<span className='text-sm'>
<span className='ml-1 text-text-tertiary'>(</span>
<span className='text-orange-400'>~{sum(statistics.map(item => Number.parseFloat(get(item, 'total_price', '0')))).toLocaleString('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 4 })}</span>
<span className='text-orange-400'>~{sum(statistics.map(item => Number.parseFloat(String(get(item, 'total_price', '0'))))).toLocaleString('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 4 })}</span>
<span className='text-text-tertiary'>)</span>
</span></span>}
textStyle={{ main: `!text-3xl !font-normal ${sumData === 0 ? '!text-text-quaternary' : ''}` }} />
@ -268,7 +268,7 @@ export const MessagesChart: FC<IBizChartProps> = ({ id, period }) => {
const noDataFlag = !response.data || response.data.length === 0
return <Chart
basicInfo={{ title: t('appOverview.analysis.totalMessages.title'), explanation: t('appOverview.analysis.totalMessages.explanation'), timePeriod: period.name }}
chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query ?? defaultPeriod) }}
chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query ?? defaultPeriod) } as any}
chartType='messages'
{...(noDataFlag && { yMax: 500 })}
/>
@ -282,7 +282,7 @@ export const ConversationsChart: FC<IBizChartProps> = ({ id, period }) => {
const noDataFlag = !response.data || response.data.length === 0
return <Chart
basicInfo={{ title: t('appOverview.analysis.totalConversations.title'), explanation: t('appOverview.analysis.totalConversations.explanation'), timePeriod: period.name }}
chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query ?? defaultPeriod) }}
chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query ?? defaultPeriod) } as any}
chartType='conversations'
{...(noDataFlag && { yMax: 500 })}
/>
@ -297,7 +297,7 @@ export const EndUsersChart: FC<IBizChartProps> = ({ id, period }) => {
const noDataFlag = !response.data || response.data.length === 0
return <Chart
basicInfo={{ title: t('appOverview.analysis.activeUsers.title'), explanation: t('appOverview.analysis.activeUsers.explanation'), timePeriod: period.name }}
chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query ?? defaultPeriod) }}
chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query ?? defaultPeriod) } as any}
chartType='endUsers'
{...(noDataFlag && { yMax: 500 })}
/>
@ -380,7 +380,7 @@ export const CostChart: FC<IBizChartProps> = ({ id, period }) => {
const noDataFlag = !response.data || response.data.length === 0
return <Chart
basicInfo={{ title: t('appOverview.analysis.tokenUsage.title'), explanation: t('appOverview.analysis.tokenUsage.explanation'), timePeriod: period.name }}
chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query ?? defaultPeriod) }}
chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query ?? defaultPeriod) } as any}
chartType='costs'
{...(noDataFlag && { yMax: 100 })}
/>
@ -394,7 +394,7 @@ export const WorkflowMessagesChart: FC<IBizChartProps> = ({ id, period }) => {
const noDataFlag = !response.data || response.data.length === 0
return <Chart
basicInfo={{ title: t('appOverview.analysis.totalMessages.title'), explanation: t('appOverview.analysis.totalMessages.explanation'), timePeriod: period.name }}
chartData={!noDataFlag ? response : { data: getDefaultChartData({ ...(period.query ?? defaultPeriod), key: 'runs' }) }}
chartData={!noDataFlag ? response : { data: getDefaultChartData({ ...(period.query ?? defaultPeriod), key: 'runs' }) } as any}
chartType='conversations'
valueKey='runs'
{...(noDataFlag && { yMax: 500 })}
@ -410,7 +410,7 @@ export const WorkflowDailyTerminalsChart: FC<IBizChartProps> = ({ id, period })
const noDataFlag = !response.data || response.data.length === 0
return <Chart
basicInfo={{ title: t('appOverview.analysis.activeUsers.title'), explanation: t('appOverview.analysis.activeUsers.explanation'), timePeriod: period.name }}
chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query ?? defaultPeriod) }}
chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query ?? defaultPeriod) } as any}
chartType='endUsers'
{...(noDataFlag && { yMax: 500 })}
/>
@ -425,7 +425,7 @@ export const WorkflowCostChart: FC<IBizChartProps> = ({ id, period }) => {
const noDataFlag = !response.data || response.data.length === 0
return <Chart
basicInfo={{ title: t('appOverview.analysis.tokenUsage.title'), explanation: t('appOverview.analysis.tokenUsage.explanation'), timePeriod: period.name }}
chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query ?? defaultPeriod) }}
chartData={!noDataFlag ? response : { data: getDefaultChartData(period.query ?? defaultPeriod) } as any}
chartType='workflowCosts'
{...(noDataFlag && { yMax: 100 })}
/>

View File

@ -18,3 +18,13 @@
.pluginInstallIcon {
background-image: url(../assets/chromeplugin-install.svg);
}
:global(html[data-theme="dark"]) .iframeIcon,
:global(html[data-theme="dark"]) .scriptsIcon,
:global(html[data-theme="dark"]) .chromePluginIcon {
filter: invert(0.86) hue-rotate(180deg) saturate(0.5) brightness(0.95);
}
:global(html[data-theme="dark"]) .pluginInstallIcon {
filter: invert(0.9);
}

View File

@ -117,7 +117,7 @@ const Flowchart = React.forwardRef((props: {
const [isInitialized, setIsInitialized] = useState(false)
const [currentTheme, setCurrentTheme] = useState<'light' | 'dark'>(props.theme || 'light')
const containerRef = useRef<HTMLDivElement>(null)
const chartId = useRef(`mermaid-chart-${Math.random().toString(36).substr(2, 9)}`).current
const chartId = useRef(`mermaid-chart-${Math.random().toString(36).slice(2, 11)}`).current
const [isLoading, setIsLoading] = useState(true)
const renderTimeoutRef = useRef<NodeJS.Timeout>()
const [errMsg, setErrMsg] = useState('')

View File

@ -259,7 +259,7 @@ function getFullMatchOffset(
): number {
let triggerOffset = offset
for (let i = triggerOffset; i <= entryText.length; i++) {
if (documentText.substr(-i) === entryText.substr(0, i))
if (documentText.slice(-i) === entryText.slice(0, i))
triggerOffset = i
}
return triggerOffset

View File

@ -32,7 +32,7 @@ const GotoAnything: FC<Props> = ({
const { t } = useTranslation()
const [show, setShow] = useState<boolean>(false)
const [searchQuery, setSearchQuery] = useState<string>('')
const [cmdVal, setCmdVal] = useState<string>('')
const [cmdVal, setCmdVal] = useState<string>('_')
const inputRef = useRef<HTMLInputElement>(null)
const handleNavSearch = useCallback((q: string) => {
setShow(true)
@ -120,9 +120,14 @@ const GotoAnything: FC<Props> = ({
},
)
// Prevent automatic selection of the first option when cmdVal is not set
const clearSelection = () => {
setCmdVal('_')
}
const handleCommandSelect = useCallback((commandKey: string) => {
setSearchQuery(`${commandKey} `)
setCmdVal('')
clearSelection()
setTimeout(() => {
inputRef.current?.focus()
}, 0)
@ -233,9 +238,6 @@ const GotoAnything: FC<Props> = ({
inputRef.current?.focus()
})
}
return () => {
setCmdVal('')
}
}, [show])
return (
@ -245,6 +247,7 @@ const GotoAnything: FC<Props> = ({
onClose={() => {
setShow(false)
setSearchQuery('')
clearSelection()
onHide?.()
}}
closable={false}
@ -268,7 +271,7 @@ const GotoAnything: FC<Props> = ({
onChange={(e) => {
setSearchQuery(e.target.value)
if (!e.target.value.startsWith('@'))
setCmdVal('')
clearSelection()
}}
className='flex-1 !border-0 !bg-transparent !shadow-none'
wrapperClassName='flex-1 !border-0 !bg-transparent'
@ -321,40 +324,40 @@ const GotoAnything: FC<Props> = ({
/>
) : (
Object.entries(groupedResults).map(([type, results], groupIndex) => (
<Command.Group key={groupIndex} heading={(() => {
const typeMap: Record<string, string> = {
'app': 'app.gotoAnything.groups.apps',
'plugin': 'app.gotoAnything.groups.plugins',
'knowledge': 'app.gotoAnything.groups.knowledgeBases',
'workflow-node': 'app.gotoAnything.groups.workflowNodes',
}
return t(typeMap[type] || `${type}s`)
})()} className='p-2 capitalize text-text-secondary'>
{results.map(result => (
<Command.Item
key={`${result.type}-${result.id}`}
value={result.title}
className='flex cursor-pointer items-center gap-3 rounded-md p-3 will-change-[background-color] hover:bg-state-base-hover aria-[selected=true]:bg-state-base-hover-alt data-[selected=true]:bg-state-base-hover-alt'
onSelect={() => handleNavigate(result)}
>
{result.icon}
<div className='min-w-0 flex-1'>
<div className='truncate font-medium text-text-secondary'>
{result.title}
</div>
{result.description && (
<div className='mt-0.5 truncate text-xs text-text-quaternary'>
{result.description}
<Command.Group key={groupIndex} heading={(() => {
const typeMap: Record<string, string> = {
'app': 'app.gotoAnything.groups.apps',
'plugin': 'app.gotoAnything.groups.plugins',
'knowledge': 'app.gotoAnything.groups.knowledgeBases',
'workflow-node': 'app.gotoAnything.groups.workflowNodes',
}
return t(typeMap[type] || `${type}s`)
})()} className='p-2 capitalize text-text-secondary'>
{results.map(result => (
<Command.Item
key={`${result.type}-${result.id}`}
value={result.title}
className='flex cursor-pointer items-center gap-3 rounded-md p-3 will-change-[background-color] aria-[selected=true]:bg-state-base-hover data-[selected=true]:bg-state-base-hover'
onSelect={() => handleNavigate(result)}
>
{result.icon}
<div className='min-w-0 flex-1'>
<div className='truncate font-medium text-text-secondary'>
{result.title}
</div>
)}
</div>
<div className='text-xs capitalize text-text-quaternary'>
{result.type}
</div>
</Command.Item>
))}
</Command.Group>
))
{result.description && (
<div className='mt-0.5 truncate text-xs text-text-quaternary'>
{result.description}
</div>
)}
</div>
<div className='text-xs capitalize text-text-quaternary'>
{result.type}
</div>
</Command.Item>
))}
</Command.Group>
))
)}
{!isCommandsMode && emptyResult}
{!isCommandsMode && defaultUI}
@ -373,7 +376,7 @@ const GotoAnything: FC<Props> = ({
{t('app.gotoAnything.resultCount', { count: searchResults.length })}
{searchMode !== 'general' && (
<span className='ml-2 opacity-60'>
{t('app.gotoAnything.inScope', { scope: searchMode.replace('@', '') })}
{t('app.gotoAnything.inScope', { scope: searchMode.replace('@', '') })}
</span>
)}
</>