feat: Add base project structure with API and web components

This commit is contained in:
Ke Sun
2025-12-02 20:28:01 +08:00
parent f3de6d6cc9
commit c1adc62ec6
817 changed files with 111226 additions and 106 deletions

View File

@@ -0,0 +1,195 @@
import { type FC, useRef, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ReactEcharts from 'echarts-for-react';
import type { ConfigForm } from '../types'
interface LineCardProps {
config: ConfigForm
}
const SeriesConfig = {
type: 'line',
smooth: true,
lineStyle: {
width: 3
},
showSymbol: false,
label: {
show: true,
position: 'top'
},
emphasis: {
focus: 'series'
},
}
const Colors = ['#155EEF', '#4DA8FF', '#FFB048']
// 快速遗忘lambda_mem=0.3lambda_time=1offset=0.05 慢速遗忘lambda_mem=1lambda_time=0.3offset=0.2
const LineChart: FC<LineCardProps> = ({ config }) => {
const { t } = useTranslation()
const chartRef = useRef<ReactEcharts>(null);
const debounceRef = useRef()
const xAxisData = [1, 3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51, 54, 57, 60]
const [initialData, setInitialData] = useState([])
const [currentData, setCurrentData] = useState({
...SeriesConfig,
name: `${t('forgettingEngine.currentConfig')}(λ_time=${config?.lambda_mem})`,
data: [],
config: {}
})
const seriesData = [
{
...SeriesConfig,
name: `${t('forgettingEngine.quicklyForget')}(λ_time=0.3)`,
data: [],
config: {lambda_mem: 0.3, lambda_time: 1, offset: 0.05}
},
{
...SeriesConfig,
name: `${t('forgettingEngine.slowForgetting')}(λ_time=1)`,
data: [],
config: {lambda_mem: 1, lambda_time: 0.3, offset: 0.2}
}
]
useEffect(() => {
getInitData()
}, [])
useEffect(() => {
if (config) {
clearTimeout(debounceRef.current)
debounceRef.current = setTimeout(() => {
getCaculateData(config)
}, 500)
}
return () => {
console.log('clearTimeout')
clearTimeout(debounceRef.current)
}
}, [config])
// 快速遗忘lambda_mem=0.3lambda_time=1offset=0.05
// 慢速遗忘lambda_mem=1lambda_time=0.3offset=0.2
const getInitData = () => {
const list = seriesData.map(item => ({
...item,
data: formatData(item.config)
}))
setInitialData(list)
}
const calculateSeriesData = (days: number, data: ConfigForm) => {
const { offset, lambda_time, lambda_mem } = data;
const S = 1
// R = offset + (1 - offset) × e^(-λtime × t / (λmem × S))
return (Number(offset) + (1 - Number(offset)) * Math.exp(-Number(lambda_time) * days / (Number(lambda_mem) * S))).toFixed(4)
}
const formatData = (data: ConfigForm) => {
return xAxisData.map(days => Number(calculateSeriesData(days, data)))
}
const getCaculateData = (data: ConfigForm) => {
if (!data) {
return
}
console.log('getCaculateData', data)
setCurrentData({
...currentData,
config: data,
name: `${t('forgettingEngine.currentConfig')}(λ_time=${data.lambda_time})`,
data: xAxisData.map(days => Number(calculateSeriesData(days, data)))
})
}
return (
<>
{xAxisData.length > 0 && initialData.length > 0 && (
<ReactEcharts
ref={chartRef}
option={{
color: Colors,
tooltip: {
trigger: 'axis',
},
legend: {
data: [currentData.name, ...seriesData.map(item => item.name)],
textStyle: {
color: '#5B6167',
fontFamily: 'PingFangSC, PingFang SC',
lineHeight: 16,
// width: 127,
// overflow: 'break'
},
itemGap: 24,
padding: 0,
itemWidth: 26,
itemHeight: 10,
left: 'center',
bottom: 0,
},
grid: {
left: 4,
right: '2%',
bottom: 60,
top: 32,
containLabel: true
},
xAxis: {
type: 'category',
data: xAxisData,
boundaryGap: false,
axisLine: {
lineStyle: {
color: '#EBEBEB',
},
show: true,
},
axisTick: {
show: true
},
axisLabel: {
color: '#5B6167'
},
},
yAxis: {
type: 'value',
axisLabel: {
color: '#5B6167',
fontFamily: 'PingFangSC, PingFang SC',
align: 'right',
lineHeight: 17,
},
axisLine: {
lineStyle: {
color: '#EBEBEB',
},
},
},
series: [
currentData,
...initialData || []
]
}}
style={{ height: '450px', width: '100%' }}
opts={{ renderer: 'canvas' }}
notMerge={true}
lazyUpdate={true}
onEvents={{
// 图表渲染完成后再次调整大小,确保宽度正确
// 使用 setTimeout 避免在主渲染过程中调用 resize
rendered: () => {
if (chartRef.current) {
setTimeout(() => {
chartRef.current?.getEchartsInstance().resize();
}, 0);
}
}
}}
/>
)}
</>
)
}
export default LineChart

View File

@@ -0,0 +1,150 @@
import React, { useState, useEffect } from 'react';
import { Row, Col, Form, Slider, Button, Space, message } from 'antd';
import { useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import RbCard from '@/components/RbCard/Card';
import strategyImpactSimulator from '@/assets/images/memory/strategyImpactSimulator.svg'
import LineChart from './components/LineChart'
import { getMemoryForgetConfig, updateMemoryForgetConfig } from '@/api/memory'
import type { ConfigForm } from './types'
const configList = [
{
key: 'minimumRetention',
name: 'lambda_time',
range: [0, 1],
type: 'decimal',
},
{
key: 'forgettingRate',
name: 'lambda_mem',
range: [0, 1],
type: 'decimal',
},
{
key: 'offset',
name: 'offset',
type: 'decimal',
}
]
const ForgettingEngine: React.FC = () => {
const { t } = useTranslation();
const params = useParams();
const [configData, setConfigData] = useState<ConfigForm>();
const [form] = Form.useForm<ConfigForm>();
const [messageApi, contextHolder] = message.useMessage();
const [loading, setLoading] = useState(false)
const values = Form.useWatch([], form);
useEffect(() => {
getConfigData()
}, [])
const getConfigData = () => {
getMemoryForgetConfig(params.id)
.then((res) => {
const response = res as ConfigForm
const initialValues = {
...response,
lambda_time: Number(response.lambda_time || 0),
lambda_mem: Number(response.lambda_mem || 0),
offset: Number(response.offset || 0),
}
setConfigData(initialValues);
form.setFieldsValue(initialValues);
})
.catch(() => {
console.error('Failed to load data');
})
}
const handleReset = () => {
form.setFieldsValue(configData);
}
const handleSave = () => {
setLoading(true)
updateMemoryForgetConfig({
config_id: params.id,
...values
})
.then(() => {
messageApi.success(t('common.saveSuccess'))
setConfigData({...(values || {})})
})
.finally(() => {
setLoading(false)
})
}
return (
<Row gutter={[16, 16]}>
<Col span={9}>
<RbCard
title={
<div className="rb:flex rb:items-center">
<img src={strategyImpactSimulator} className="rb:w-[20px] rb:h-[20px] rb:mr-[8px]" />
{t('forgettingEngine.forgettingEngineConfigParams')}
</div>
}
className='rb:h-full!'
>
<Form
form={form}
layout="vertical"
initialValues={{
offset: 0,
lambda_time: 0.03,
lambda_mem: 0.03,
}}
>
<Space size={24} direction="vertical" style={{ width: '100%' }}>
{configList.map(config => (
<div key={config.key}>
<div className="rb:text-[14px] rb:font-medium rb:leading-[20px] rb:mb-[8px]">
{t(`forgettingEngine.${config.key}`)}
</div>
<div className="rb:mt-[4px] rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-[16px] ">
{t(`forgettingEngine.${config.key}Desc`)}
</div>
<Form.Item
name={config.name}
>
<Slider tooltip={{open: false}} max={config.range?.[1] || 1} min={config.range?.[0] || 0} step={0.01} style={{ margin: '0' }} />
</Form.Item>
<div className="rb:flex rb:text-[12px] rb:items-center rb:justify-between rb:text-[#5B6167] rb:leading-[20px] rb:mt-[-26px]">
<Space size={4}>
{config.range && <span>{t(`forgettingEngine.range`)}: {config.range?.join('-')}</span>}
{config.type && <span>{t(`forgettingEngine.type`)}: {config.type}</span>}
</Space>
<>{t('forgettingEngine.CurrentValue')}: {values?.[config.name] || 0}</>
</div>
</div>
))}
<Row gutter={16}>
<Col span={12}>
<Button block onClick={handleReset}>{t('common.reset')}</Button>
</Col>
<Col span={12}>
<Button type="primary" loading={loading} block onClick={handleSave}>{t('common.save')}</Button>
</Col>
</Row>
</Space>
</Form>
</RbCard>
</Col>
<Col span={15}>
<RbCard
className='rb:h-full!'
>
<LineChart
config={values}
/>
</RbCard>
</Col>
{contextHolder}
</Row>
);
};
export default ForgettingEngine;

View File

@@ -0,0 +1,48 @@
// 标签表单数据类型
export interface TagFormData {
tagName: string;
type: string;
color: string;
description?: string;
applicableScope?: string[];
semanticExpansion?: string;
isActive?: boolean;
// 扩展字段用于区分编辑和新增操作
isEditing?: boolean;
tagId?: string;
}
// 记忆总览数据类型
export interface MemoryOverviewRecord {
id: number;
memoryID: string,
contentSummary: string;
type: string;
createTime: string;
lastCallTime: string;
retentionDegree: string;
status: string;
}
// 定义组件暴露的方法接口
export interface MemoryOverviewFormRef {
handleOpen: (memoryOverview?: MemoryOverviewRecord | null) => void;
}
// 遗忘曲线数据类型
export interface CurveRecord {
memoryID: string;
type: string;
currentRetentionRate: string;
finallyActivated: string;
expectedForgettingTime: string;
reinforcementCount: string;
}
export interface ConfigForm {
statement_granularity?: string;
include_dialogue_context?: boolean;
max_context?: string;
lambda_time: string | number;
lambda_mem: string | number;
offset: string | number;
}