Apple\Apple 9 месяцев назад
Родитель
Сommit
e911eb7988
3 измененных файлов с 246 добавлено и 249 удалено
  1. 241 249
      web/src/components/LogsTable.js
  2. 1 0
      web/src/i18n/locales/en.json
  3. 4 0
      web/src/index.css

+ 241 - 249
web/src/components/LogsTable.js

@@ -14,8 +14,6 @@ import {
   Avatar,
   Button,
   Descriptions,
-  Form,
-  Layout,
   Modal,
   Popover,
   Select,
@@ -25,6 +23,11 @@ import {
   Tag,
   Tooltip,
   Checkbox,
+  Card,
+  Typography,
+  Divider,
+  Input,
+  DatePicker,
 } from '@douyinfe/semi-ui';
 import { ITEMS_PER_PAGE } from '../constants';
 import {
@@ -43,9 +46,14 @@ import {
 import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph';
 import { getLogOther } from '../helpers/other.js';
 import { StyleContext } from '../context/Style/index.js';
-import { IconInherit, IconRefresh, IconSetting } from '@douyinfe/semi-icons';
+import {
+  IconRefresh,
+  IconSetting,
+  IconEyeOpened,
+  IconSearch,
+} from '@douyinfe/semi-icons';
 
-const { Header } = Layout;
+const { Text } = Typography;
 
 function renderTimestamp(timestamp) {
   return <>{timestamp2string(timestamp)}</>;
@@ -81,37 +89,37 @@ const LogsTable = () => {
     switch (type) {
       case 1:
         return (
-          <Tag color='cyan' size='large'>
+          <Tag color='cyan' size='large' shape='circle'>
             {t('充值')}
           </Tag>
         );
       case 2:
         return (
-          <Tag color='lime' size='large'>
+          <Tag color='lime' size='large' shape='circle'>
             {t('消费')}
           </Tag>
         );
       case 3:
         return (
-          <Tag color='orange' size='large'>
+          <Tag color='orange' size='large' shape='circle'>
             {t('管理')}
           </Tag>
         );
       case 4:
         return (
-          <Tag color='purple' size='large'>
+          <Tag color='purple' size='large' shape='circle'>
             {t('系统')}
           </Tag>
         );
       case 5:
         return (
-          <Tag color='red' size='large'>
+          <Tag color='red' size='large' shape='circle'>
             {t('错误')}
           </Tag>
         );
       default:
         return (
-          <Tag color='grey' size='large'>
+          <Tag color='grey' size='large' shape='circle'>
             {t('未知')}
           </Tag>
         );
@@ -121,13 +129,13 @@ const LogsTable = () => {
   function renderIsStream(bool) {
     if (bool) {
       return (
-        <Tag color='blue' size='large'>
+        <Tag color='blue' size='large' shape='circle'>
           {t('流')}
         </Tag>
       );
     } else {
       return (
-        <Tag color='purple' size='large'>
+        <Tag color='purple' size='large' shape='circle'>
           {t('非流')}
         </Tag>
       );
@@ -138,21 +146,21 @@ const LogsTable = () => {
     const time = parseInt(type);
     if (time < 101) {
       return (
-        <Tag color='green' size='large'>
+        <Tag color='green' size='large' shape='circle'>
           {' '}
           {time} s{' '}
         </Tag>
       );
     } else if (time < 300) {
       return (
-        <Tag color='orange' size='large'>
+        <Tag color='orange' size='large' shape='circle'>
           {' '}
           {time} s{' '}
         </Tag>
       );
     } else {
       return (
-        <Tag color='red' size='large'>
+        <Tag color='red' size='large' shape='circle'>
           {' '}
           {time} s{' '}
         </Tag>
@@ -165,21 +173,21 @@ const LogsTable = () => {
     time = time.toFixed(1);
     if (time < 3) {
       return (
-        <Tag color='green' size='large'>
+        <Tag color='green' size='large' shape='circle'>
           {' '}
           {time} s{' '}
         </Tag>
       );
     } else if (time < 10) {
       return (
-        <Tag color='orange' size='large'>
+        <Tag color='orange' size='large' shape='circle'>
           {' '}
           {time} s{' '}
         </Tag>
       );
     } else {
       return (
-        <Tag color='red' size='large'>
+        <Tag color='red' size='large' shape='circle'>
           {' '}
           {time} s{' '}
         </Tag>
@@ -198,8 +206,9 @@ const LogsTable = () => {
         <Tag
           color={stringToColor(record.model_name)}
           size='large'
+          shape='circle'
           onClick={(event) => {
-            copyText(event, record.model_name).then((r) => {});
+            copyText(event, record.model_name).then((r) => { });
           }}
         >
           {' '}
@@ -217,8 +226,9 @@ const LogsTable = () => {
                     <Tag
                       color={stringToColor(record.model_name)}
                       size='large'
+                      shape='circle'
                       onClick={(event) => {
-                        copyText(event, record.model_name).then((r) => {});
+                        copyText(event, record.model_name).then((r) => { });
                       }}
                     >
                       {t('请求并计费模型')} {record.model_name}{' '}
@@ -226,9 +236,10 @@ const LogsTable = () => {
                     <Tag
                       color={stringToColor(other.upstream_model_name)}
                       size='large'
+                      shape='circle'
                       onClick={(event) => {
                         copyText(event, other.upstream_model_name).then(
-                          (r) => {},
+                          (r) => { },
                         );
                       }}
                     >
@@ -241,8 +252,9 @@ const LogsTable = () => {
               <Tag
                 color={stringToColor(record.model_name)}
                 size='large'
+                shape='circle'
                 onClick={(event) => {
-                  copyText(event, record.model_name).then((r) => {});
+                  copyText(event, record.model_name).then((r) => { });
                 }}
                 suffixIcon={
                   <IconRefresh
@@ -254,17 +266,6 @@ const LogsTable = () => {
                 {record.model_name}{' '}
               </Tag>
             </Popover>
-            {/*<Tooltip content={t('实际模型')}>*/}
-            {/*  <Tag*/}
-            {/*    color={stringToColor(other.upstream_model_name)}*/}
-            {/*    size='large'*/}
-            {/*    onClick={(event) => {*/}
-            {/*      copyText(event, other.upstream_model_name).then(r => {});*/}
-            {/*    }}*/}
-            {/*  >*/}
-            {/*    {' '}{other.upstream_model_name}{' '}*/}
-            {/*  </Tag>*/}
-            {/*</Tooltip>*/}
           </Space>
         </>
       );
@@ -386,6 +387,7 @@ const LogsTable = () => {
                   <Tag
                     color={colors[parseInt(text) % colors.length]}
                     size='large'
+                    shape='circle'
                   >
                     {' '}
                     {text}{' '}
@@ -437,6 +439,7 @@ const LogsTable = () => {
             <Tag
               color='grey'
               size='large'
+              shape='circle'
               onClick={(event) => {
                 //cancel the row click event
                 copyText(event, text);
@@ -620,21 +623,21 @@ const LogsTable = () => {
         }
         let content = other?.claude
           ? renderClaudeModelPriceSimple(
-              other.model_ratio,
-              other.model_price,
-              other.group_ratio,
-              other.cache_tokens || 0,
-              other.cache_ratio || 1.0,
-              other.cache_creation_tokens || 0,
-              other.cache_creation_ratio || 1.0,
-            )
+            other.model_ratio,
+            other.model_price,
+            other.group_ratio,
+            other.cache_tokens || 0,
+            other.cache_ratio || 1.0,
+            other.cache_creation_tokens || 0,
+            other.cache_creation_ratio || 1.0,
+          )
           : renderModelPriceSimple(
-              other.model_ratio,
-              other.model_price,
-              other.group_ratio,
-              other.cache_tokens || 0,
-              other.cache_ratio || 1.0,
-            );
+            other.model_ratio,
+            other.model_price,
+            other.group_ratio,
+            other.cache_tokens || 0,
+            other.cache_ratio || 1.0,
+          );
         return (
           <Paragraph
             ellipsis={{
@@ -673,15 +676,29 @@ const LogsTable = () => {
         visible={showColumnSelector}
         onCancel={() => setShowColumnSelector(false)}
         footer={
-          <>
-            <Button onClick={() => initDefaultColumns()}>{t('重置')}</Button>
-            <Button onClick={() => setShowColumnSelector(false)}>
+          <div className="flex justify-end">
+            <Button
+              theme="light"
+              onClick={() => initDefaultColumns()}
+              className="!rounded-full"
+            >
+              {t('重置')}
+            </Button>
+            <Button
+              theme="light"
+              onClick={() => setShowColumnSelector(false)}
+              className="!rounded-full"
+            >
               {t('取消')}
             </Button>
-            <Button type='primary' onClick={() => setShowColumnSelector(false)}>
+            <Button
+              type='primary'
+              onClick={() => setShowColumnSelector(false)}
+              className="!rounded-full"
+            >
               {t('确定')}
             </Button>
-          </>
+          </div>
         }
       >
         <div style={{ marginBottom: 20 }}>
@@ -697,15 +714,7 @@ const LogsTable = () => {
           </Checkbox>
         </div>
         <div
-          style={{
-            display: 'flex',
-            flexWrap: 'wrap',
-            maxHeight: '400px',
-            overflowY: 'auto',
-            border: '1px solid var(--semi-color-border)',
-            borderRadius: '6px',
-            padding: '16px',
-          }}
+          className="flex flex-wrap max-h-96 overflow-y-auto rounded-lg p-4"
         >
           {allColumns.map((column) => {
             // Skip admin-only columns for non-admin users
@@ -721,7 +730,7 @@ const LogsTable = () => {
             return (
               <div
                 key={column.key}
-                style={{ width: '50%', marginBottom: 16, paddingRight: 8 }}
+                className="w-1/2 mb-4 pr-2"
               >
                 <Checkbox
                   checked={!!visibleColumns[column.key]}
@@ -921,27 +930,27 @@ const LogsTable = () => {
           key: t('日志详情'),
           value: other?.claude
             ? renderClaudeLogContent(
-                other?.model_ratio,
-                other.completion_ratio,
-                other.model_price,
-                other.group_ratio,
-                other.cache_ratio || 1.0,
-                other.cache_creation_ratio || 1.0,
-              )
+              other?.model_ratio,
+              other.completion_ratio,
+              other.model_price,
+              other.group_ratio,
+              other.cache_ratio || 1.0,
+              other.cache_creation_ratio || 1.0,
+            )
             : renderLogContent(
-                other?.model_ratio,
-                other.completion_ratio,
-                other.model_price,
-                other.group_ratio,
-                other?.user_group_ratio,
-                false,
-                1.0,
-                undefined,
-                other.web_search || false,
-                other.web_search_call_count || 0,
-                other.file_search || false,
-                other.file_search_call_count || 0,
-              ),
+              other?.model_ratio,
+              other.completion_ratio,
+              other.model_price,
+              other.group_ratio,
+              other?.user_group_ratio,
+              false,
+              1.0,
+              undefined,
+              other.web_search || false,
+              other.web_search_call_count || 0,
+              other.file_search || false,
+              other.file_search_call_count || 0,
+            ),
         });
       }
       if (logs[i].type === 2) {
@@ -1056,7 +1065,7 @@ const LogsTable = () => {
 
   const handlePageChange = (page) => {
     setActivePage(page);
-    loadLogs(page, pageSize, logType).then((r) => {});
+    loadLogs(page, pageSize, logType).then((r) => { });
   };
 
   const handlePageSizeChange = async (size) => {
@@ -1104,86 +1113,56 @@ const LogsTable = () => {
   return (
     <>
       {renderColumnSelector()}
-      <Layout>
-        <Header>
-          <Spin spinning={loadingStat}>
-            <Space>
-              <Tag
-                color='blue'
-                size='large'
-                style={{
-                  padding: 15,
-                  borderRadius: '8px',
-                  fontWeight: 500,
-                  boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
-                }}
-              >
-                {t('消耗额度')}: {renderQuota(stat.quota)}
-              </Tag>
-              <Tag
-                color='pink'
-                size='large'
-                style={{
-                  padding: 15,
-                  borderRadius: '8px',
-                  fontWeight: 500,
-                  boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
-                }}
-              >
-                RPM: {stat.rpm}
-              </Tag>
-              <Tag
-                color='white'
-                size='large'
-                style={{
-                  padding: 15,
-                  border: 'none',
-                  boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
-                  borderRadius: '8px',
-                  fontWeight: 500,
-                }}
-              >
-                TPM: {stat.tpm}
-              </Tag>
-            </Space>
-          </Spin>
-        </Header>
-        <Form layout='horizontal' style={{ marginTop: 10 }}>
-          <>
-            <Form.Section>
-              <div style={{ marginBottom: 10 }}>
-                {styleState.isMobile ? (
-                  <div>
-                    <Form.DatePicker
-                      field='start_timestamp'
-                      label={t('起始时间')}
-                      style={{ width: 272 }}
-                      initValue={start_timestamp}
-                      type='dateTime'
-                      onChange={(value) => {
-                        console.log(value);
-                        handleInputChange(value, 'start_timestamp');
-                      }}
-                    />
-                    <Form.DatePicker
-                      field='end_timestamp'
-                      fluid
-                      label={t('结束时间')}
-                      style={{ width: 272 }}
-                      initValue={end_timestamp}
-                      type='dateTime'
-                      onChange={(value) =>
-                        handleInputChange(value, 'end_timestamp')
-                      }
-                    />
+      <Card
+        className="!rounded-2xl overflow-hidden mb-4"
+        title={
+          <div className="flex flex-col w-full">
+            <div className="flex flex-col md:flex-row justify-between items-center">
+              <div className="flex items-center text-orange-500 mb-2 md:mb-0">
+                <IconEyeOpened className="mr-2" />
+                <Text>{t('日志记录帮助您了解使用情况和排查问题')}</Text>
+              </div>
+
+              <Spin spinning={loadingStat}>
+                <div className="flex flex-wrap gap-4">
+                  <div className="flex items-center">
+                    <span className="text-xl mr-2">💰</span>
+                    <div>
+                      <Text type="tertiary" size="small">{t('消耗额度')}</Text>
+                      <div className="font-medium">{renderQuota(stat.quota)}</div>
+                    </div>
+                  </div>
+
+                  <div className="flex items-center">
+                    <span className="text-xl mr-2">📊</span>
+                    <div>
+                      <Text type="tertiary" size="small">RPM</Text>
+                      <div className="font-medium">{stat.rpm || 0}</div>
+                    </div>
                   </div>
-                ) : (
-                  <Form.DatePicker
-                    field='range_timestamp'
-                    label={t('时间范围')}
-                    initValue={[start_timestamp, end_timestamp]}
+
+                  <div className="flex items-center">
+                    <span className="text-xl mr-2">⚡</span>
+                    <div>
+                      <Text type="tertiary" size="small">TPM</Text>
+                      <div className="font-medium">{stat.tpm || 0}</div>
+                    </div>
+                  </div>
+                </div>
+              </Spin>
+            </div>
+
+            <Divider margin="12px" />
+
+            {/* 搜索表单区域 */}
+            <div className="flex flex-col gap-4">
+              <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
+                {/* 时间选择器 */}
+                <div className="col-span-1 lg:col-span-2">
+                  <DatePicker
+                    className="w-full"
+                    value={[start_timestamp, end_timestamp]}
                     type='dateTimeRange'
-                    name='range_timestamp'
                     onChange={(value) => {
                       if (Array.isArray(value) && value.length === 2) {
                         handleInputChange(value[0], 'start_timestamp');
@@ -1191,100 +1170,113 @@ const LogsTable = () => {
                       }
                     }}
                   />
-                )}
-              </div>
-            </Form.Section>
-            <Form.Input
-              field='token_name'
-              label={t('令牌名称')}
-              value={token_name}
-              placeholder={t('可选值')}
-              name='token_name'
-              onChange={(value) => handleInputChange(value, 'token_name')}
-            />
-            <Form.Input
-              field='model_name'
-              label={t('模型名称')}
-              value={model_name}
-              placeholder={t('可选值')}
-              name='model_name'
-              onChange={(value) => handleInputChange(value, 'model_name')}
-            />
-            <Form.Input
-              field='group'
-              label={t('分组')}
-              value={group}
-              placeholder={t('可选值')}
-              name='group'
-              onChange={(value) => handleInputChange(value, 'group')}
-            />
-            {isAdminUser && (
-              <>
-                <Form.Input
-                  field='channel'
-                  label={t('渠道 ID')}
-                  value={channel}
-                  placeholder={t('可选值')}
-                  name='channel'
-                  onChange={(value) => handleInputChange(value, 'channel')}
+                </div>
+
+                {/* 日志类型选择器 */}
+                <Select
+                  value={logType.toString()}
+                  placeholder={t('日志类型')}
+                  className="!rounded-full"
+                  onChange={(value) => {
+                    setLogType(parseInt(value));
+                    loadLogs(0, pageSize, parseInt(value));
+                  }}
+                >
+                  <Select.Option value='0'>{t('全部')}</Select.Option>
+                  <Select.Option value='1'>{t('充值')}</Select.Option>
+                  <Select.Option value='2'>{t('消费')}</Select.Option>
+                  <Select.Option value='3'>{t('管理')}</Select.Option>
+                  <Select.Option value='4'>{t('系统')}</Select.Option>
+                  <Select.Option value='5'>{t('错误')}</Select.Option>
+                </Select>
+
+                {/* 其他搜索字段 */}
+                <Input
+                  prefix={<IconSearch />}
+                  placeholder={t('令牌名称')}
+                  value={token_name}
+                  onChange={(value) => handleInputChange(value, 'token_name')}
+                  className="!rounded-full"
+                  showClear
                 />
-                <Form.Input
-                  field='username'
-                  label={t('用户名称')}
-                  value={username}
-                  placeholder={t('可选值')}
-                  name='username'
-                  onChange={(value) => handleInputChange(value, 'username')}
+
+                <Input
+                  prefix={<IconSearch />}
+                  placeholder={t('模型名称')}
+                  value={model_name}
+                  onChange={(value) => handleInputChange(value, 'model_name')}
+                  className="!rounded-full"
+                  showClear
                 />
-              </>
-            )}
-            <Button
-              label={t('查询')}
-              type='primary'
-              htmlType='submit'
-              className='btn-margin-right'
-              onClick={refresh}
-              loading={loading}
-              style={{ marginTop: 24 }}
-            >
-              {t('查询')}
-            </Button>
-            <Form.Section></Form.Section>
-          </>
-        </Form>
-        <div style={{ marginTop: 10 }}>
-          <Select
-            defaultValue='0'
-            style={{ width: 120 }}
-            onChange={(value) => {
-              setLogType(parseInt(value));
-              loadLogs(0, pageSize, parseInt(value));
-            }}
-          >
-            <Select.Option value='0'>{t('全部')}</Select.Option>
-            <Select.Option value='1'>{t('充值')}</Select.Option>
-            <Select.Option value='2'>{t('消费')}</Select.Option>
-            <Select.Option value='3'>{t('管理')}</Select.Option>
-            <Select.Option value='4'>{t('系统')}</Select.Option>
-            <Select.Option value='5'>{t('错误')}</Select.Option>
-          </Select>
-          <Button
-            theme='light'
-            type='tertiary'
-            icon={<IconSetting />}
-            onClick={() => setShowColumnSelector(true)}
-            style={{ marginLeft: 8 }}
-          >
-            {t('列设置')}
-          </Button>
-        </div>
+
+                <Input
+                  prefix={<IconSearch />}
+                  placeholder={t('分组')}
+                  value={group}
+                  onChange={(value) => handleInputChange(value, 'group')}
+                  className="!rounded-full"
+                  showClear
+                />
+
+                {isAdminUser && (
+                  <>
+                    <Input
+                      prefix={<IconSearch />}
+                      placeholder={t('渠道 ID')}
+                      value={channel}
+                      onChange={(value) => handleInputChange(value, 'channel')}
+                      className="!rounded-full"
+                      showClear
+                    />
+                    <Input
+                      prefix={<IconSearch />}
+                      placeholder={t('用户名称')}
+                      value={username}
+                      onChange={(value) => handleInputChange(value, 'username')}
+                      className="!rounded-full"
+                      showClear
+                    />
+                  </>
+                )}
+              </div>
+
+              {/* 操作按钮区域 */}
+              <div className="flex justify-between items-center pt-2">
+                <div></div>
+                <div className="flex gap-2">
+                  <Button
+                    type='primary'
+                    onClick={refresh}
+                    loading={loading}
+                    className="!rounded-full"
+                  >
+                    {t('查询')}
+                  </Button>
+                  <Button
+                    theme='light'
+                    type='tertiary'
+                    icon={<IconSetting />}
+                    onClick={() => setShowColumnSelector(true)}
+                    className="!rounded-full"
+                  >
+                    {t('列设置')}
+                  </Button>
+                </div>
+              </div>
+            </div>
+          </div>
+        }
+        shadows='hover'
+      >
         <Table
-          style={{ marginTop: 5 }}
           columns={getVisibleColumns()}
           expandedRowRender={expandRowRender}
           expandRowByClick={true}
           dataSource={logs}
           rowKey='key'
+          loading={loading}
+          className="rounded-xl overflow-hidden"
+          size="middle"
           pagination={{
             formatPageText: (page) =>
               t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
@@ -1295,7 +1287,7 @@ const LogsTable = () => {
             currentPage: activePage,
             pageSize: pageSize,
             total: logCount,
-            pageSizeOpts: [10, 20, 50, 100],
+            pageSizeOptions: [10, 20, 50, 100],
             showSizeChanger: true,
             onPageSizeChange: (size) => {
               handlePageSizeChange(size);
@@ -1303,7 +1295,7 @@ const LogsTable = () => {
             onPageChange: handlePageChange,
           }}
         />
-      </Layout>
+      </Card>
     </>
   );
 };

+ 1 - 0
web/src/i18n/locales/en.json

@@ -170,6 +170,7 @@
   "充值": "Recharge",
   "用户": "Users",
   "日志": "Logs",
+  "日志记录帮助您了解使用情况和排查问题": "Log records help you understand usage and troubleshoot issues",
   "设置": "Settings",
   "关于": "About",
   "价格": "Pricing",

+ 4 - 0
web/src/index.css

@@ -329,3 +329,7 @@ code {
 /*  -moz-box-shadow: 0 1px 6px rgba(0, 0, 0, 0.08) !important;*/
 /*  min-height: 100%;*/
 /*}*/
+
+.semi-datepicker-range-input {
+  border-radius: 9999px;
+}