fix: stabilize build and improve mobile chat layout
parent
423fc9531f
commit
faef8b6aea
|
|
@ -1,5 +1,5 @@
|
||||||
import { useEffect } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Button, Card, List, Modal, App as AntApp, Space, Alert, Tag, Popconfirm, Tooltip } from 'antd';
|
import { Button, Card, List, Modal, App as AntApp, Space, Alert, Tag, Popconfirm, Tooltip, Form, Input, Select } from 'antd';
|
||||||
import type { McpServer } from '../../../api';
|
import type { McpServer } from '../../../api';
|
||||||
import type { McpPanelLogicOutput } from '../McpPanelLogic';
|
import type { McpPanelLogicOutput } from '../McpPanelLogic';
|
||||||
|
|
||||||
|
|
@ -230,8 +230,7 @@ function McpFormH5({
|
||||||
onSubmit: (values: any) => void;
|
onSubmit: (values: any) => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { useForm } = AntApp;
|
const [form] = Form.useForm();
|
||||||
const [form] = useForm();
|
|
||||||
const [transport, setTransport] = useState<'stdio' | 'sse' | 'http'>(initial?.transport ?? 'stdio');
|
const [transport, setTransport] = useState<'stdio' | 'sse' | 'http'>(initial?.transport ?? 'stdio');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -247,92 +246,50 @@ function McpFormH5({
|
||||||
}, [initial, form]);
|
}, [initial, form]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={(e) => {
|
<Form form={form} layout="vertical" onFinish={onSubmit}>
|
||||||
e.preventDefault();
|
<Form.Item name="name" label="名称" rules={[{ required: true, message: '请输入名称' }]}>
|
||||||
onSubmit(form.getFieldsValue());
|
<Input placeholder="例如 filesystem" />
|
||||||
}}>
|
</Form.Item>
|
||||||
<div style={{ marginBottom: 12 }}>
|
|
||||||
<label style={{ fontSize: 14, fontWeight: 500, marginBottom: 4, display: 'block' }}>名称</label>
|
<Form.Item name="transport" label="Transport" rules={[{ required: true, message: '请选择 Transport' }]}>
|
||||||
<input
|
<Select
|
||||||
{...form.getFieldProps('name')}
|
|
||||||
placeholder="例如 filesystem"
|
|
||||||
style={{ width: '100%', padding: '8px', borderRadius: 6, border: '1px solid #d9d9d9' }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div style={{ marginBottom: 12 }}>
|
|
||||||
<label style={{ fontSize: 14, fontWeight: 500, marginBottom: 4, display: 'block' }}>Transport</label>
|
|
||||||
<select
|
|
||||||
value={transport}
|
value={transport}
|
||||||
onChange={(e) => setTransport(e.target.value as any)}
|
onChange={(v) => {
|
||||||
style={{ width: '100%', padding: '8px', borderRadius: 6, border: '1px solid #d9d9d9' }}
|
setTransport(v as any);
|
||||||
>
|
form.setFieldValue('transport', v);
|
||||||
<option value="stdio">stdio (本机进程)</option>
|
}}
|
||||||
<option value="sse">SSE (远程)</option>
|
options={[
|
||||||
<option value="http">HTTP (Streamable)</option>
|
{ value: 'stdio', label: 'stdio (本机进程)' },
|
||||||
</select>
|
{ value: 'sse', label: 'SSE (远程)' },
|
||||||
</div>
|
{ value: 'http', label: 'HTTP (Streamable)' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
{transport === 'stdio' ? (
|
{transport === 'stdio' ? (
|
||||||
<>
|
<>
|
||||||
<div style={{ marginBottom: 12 }}>
|
<Form.Item name="command" label="Command">
|
||||||
<label style={{ fontSize: 14, fontWeight: 500, marginBottom: 4, display: 'block' }}>Command</label>
|
<Input placeholder="npx" />
|
||||||
<input
|
</Form.Item>
|
||||||
{...form.getFieldProps('command')}
|
<Form.Item name="argsText" label="Args (每行一个)">
|
||||||
placeholder="npx"
|
<Input.TextArea placeholder={'-y\n@modelcontextprotocol/server-filesystem\n/path/to/dir'} autoSize={{ minRows: 3, maxRows: 8 }} style={{ fontFamily: 'Consolas, monospace', fontSize: 12 }} />
|
||||||
style={{ width: '100%', padding: '8px', borderRadius: 6, border: '1px solid #d9d9d9' }}
|
</Form.Item>
|
||||||
/>
|
<Form.Item name="envText" label="Env (JSON 对象)">
|
||||||
<div style={{ fontSize: 12, color: '#999', marginTop: 2 }}>如 npx / node / python</div>
|
<Input.TextArea placeholder={'{ "API_KEY": "xxx" }'} autoSize={{ minRows: 2, maxRows: 8 }} style={{ fontFamily: 'Consolas, monospace', fontSize: 12 }} />
|
||||||
</div>
|
</Form.Item>
|
||||||
<div style={{ marginBottom: 12 }}>
|
|
||||||
<label style={{ fontSize: 14, fontWeight: 500, marginBottom: 4, display: 'block' }}>Args (每行一个)</label>
|
|
||||||
<textarea
|
|
||||||
{...form.getFieldProps('argsText')}
|
|
||||||
placeholder={'-y\n@modelcontextprotocol/server-filesystem\n/path/to/dir'}
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
minHeight: 60,
|
|
||||||
fontFamily: 'Consolas, monospace',
|
|
||||||
fontSize: 12,
|
|
||||||
padding: 8,
|
|
||||||
borderRadius: 6,
|
|
||||||
border: '1px solid #d9d9d9',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div style={{ marginBottom: 12 }}>
|
|
||||||
<label style={{ fontSize: 14, fontWeight: 500, marginBottom: 4, display: 'block' }}>Env (JSON 对象)</label>
|
|
||||||
<textarea
|
|
||||||
{...form.getFieldProps('envText')}
|
|
||||||
placeholder={'{ "API_KEY": "xxx" }'}
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
minHeight: 40,
|
|
||||||
fontFamily: 'Consolas, monospace',
|
|
||||||
fontSize: 12,
|
|
||||||
padding: 8,
|
|
||||||
borderRadius: 6,
|
|
||||||
border: '1px solid #d9d9d9',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ marginBottom: 12 }}>
|
<Form.Item name="url" label="URL" rules={[{ required: true, message: '请输入 URL' }]}>
|
||||||
<label style={{ fontSize: 14, fontWeight: 500, marginBottom: 4, display: 'block' }}>URL</label>
|
<Input placeholder="https://example.com/mcp" />
|
||||||
<input
|
</Form.Item>
|
||||||
{...form.getFieldProps('url')}
|
|
||||||
placeholder="https://example.com/mcp"
|
|
||||||
style={{ width: '100%', padding: '8px', borderRadius: 6, border: '1px solid #d9d9d9' }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Space style={{ justifyContent: 'flex-end', width: '100%', marginTop: 16 }}>
|
<Space style={{ justifyContent: 'flex-end', width: '100%' }}>
|
||||||
<Button onClick={onCancel}>取消</Button>
|
<Button onClick={onCancel}>取消</Button>
|
||||||
<Button type="primary" htmlType="submit">
|
<Button type="primary" htmlType="submit">
|
||||||
保存
|
保存
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useEffect } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Button, Card, List, Modal, App as AntApp, Space, Alert, Tag, Popconfirm, Tooltip } from 'antd';
|
import { Button, Card, List, Modal, App as AntApp, Space, Alert, Tag, Popconfirm, Tooltip, Form, Input, Select } from 'antd';
|
||||||
import type { McpServer } from '../../../api';
|
import type { McpServer } from '../../../api';
|
||||||
import type { McpPanelLogicOutput } from '../McpPanelLogic';
|
import type { McpPanelLogicOutput } from '../McpPanelLogic';
|
||||||
|
|
||||||
|
|
@ -219,7 +219,7 @@ function McpForm({
|
||||||
onSubmit: (values: any) => void;
|
onSubmit: (values: any) => void;
|
||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}) {
|
}) {
|
||||||
const [form] = AntApp.useApp().form;
|
const [form] = Form.useForm();
|
||||||
const [transport, setTransport] = useState<'stdio' | 'sse' | 'http'>(initial?.transport ?? 'stdio');
|
const [transport, setTransport] = useState<'stdio' | 'sse' | 'http'>(initial?.transport ?? 'stdio');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -235,76 +235,50 @@ function McpForm({
|
||||||
}, [initial, form]);
|
}, [initial, form]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={(e) => {
|
<Form form={form} layout="vertical" onFinish={onSubmit}>
|
||||||
e.preventDefault();
|
<Form.Item name="name" label="名称" rules={[{ required: true, message: '请输入名称' }]}>
|
||||||
onSubmit(form.getFieldsValue());
|
<Input placeholder="例如 filesystem / playwright" />
|
||||||
}}>
|
</Form.Item>
|
||||||
<div className="form-item">
|
|
||||||
<label>名称</label>
|
<Form.Item name="transport" label="Transport" rules={[{ required: true, message: '请选择 Transport' }]}>
|
||||||
<input
|
<Select
|
||||||
{...form.getFieldProps('name')}
|
|
||||||
placeholder="例如 filesystem / playwright"
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="form-item">
|
|
||||||
<label>Transport</label>
|
|
||||||
<select
|
|
||||||
value={transport}
|
value={transport}
|
||||||
onChange={(e) => setTransport(e.target.value as any)}
|
onChange={(v) => {
|
||||||
style={{ width: '100%' }}
|
setTransport(v as any);
|
||||||
>
|
form.setFieldValue('transport', v);
|
||||||
<option value="stdio">stdio (本机进程)</option>
|
}}
|
||||||
<option value="sse">SSE (远程)</option>
|
options={[
|
||||||
<option value="http">HTTP (Streamable)</option>
|
{ value: 'stdio', label: 'stdio (本机进程)' },
|
||||||
</select>
|
{ value: 'sse', label: 'SSE (远程)' },
|
||||||
</div>
|
{ value: 'http', label: 'HTTP (Streamable)' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
{transport === 'stdio' ? (
|
{transport === 'stdio' ? (
|
||||||
<>
|
<>
|
||||||
<div className="form-item">
|
<Form.Item name="command" label="Command">
|
||||||
<label>Command</label>
|
<Input placeholder="npx" />
|
||||||
<input
|
</Form.Item>
|
||||||
{...form.getFieldProps('command')}
|
<Form.Item name="argsText" label="Args (每行一个)">
|
||||||
placeholder="npx"
|
<Input.TextArea placeholder={'-y\n@modelcontextprotocol/server-filesystem\n/path/to/dir'} autoSize={{ minRows: 3, maxRows: 8 }} style={{ fontFamily: 'Consolas, monospace', fontSize: 13 }} />
|
||||||
style={{ width: '100%' }}
|
</Form.Item>
|
||||||
/>
|
<Form.Item name="envText" label="Env (JSON 对象)">
|
||||||
<div className="form-extra">如 npx / node / python</div>
|
<Input.TextArea placeholder={'{ "API_KEY": "xxx" }'} autoSize={{ minRows: 2, maxRows: 8 }} style={{ fontFamily: 'Consolas, monospace', fontSize: 13 }} />
|
||||||
</div>
|
</Form.Item>
|
||||||
<div className="form-item">
|
|
||||||
<label>Args (每行一个)</label>
|
|
||||||
<textarea
|
|
||||||
{...form.getFieldProps('argsText')}
|
|
||||||
placeholder={'-y\n@modelcontextprotocol/server-filesystem\n/path/to/dir'}
|
|
||||||
style={{ width: '100%', minHeight: 80, fontFamily: 'Consolas, monospace', fontSize: 13 }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="form-item">
|
|
||||||
<label>Env (JSON 对象)</label>
|
|
||||||
<textarea
|
|
||||||
{...form.getFieldProps('envText')}
|
|
||||||
placeholder={'{ "API_KEY": "xxx" }'}
|
|
||||||
style={{ width: '100%', minHeight: 60, fontFamily: 'Consolas, monospace', fontSize: 13 }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="form-item">
|
<Form.Item name="url" label="URL" rules={[{ required: true, message: '请输入 URL' }]}>
|
||||||
<label>URL</label>
|
<Input placeholder="https://example.com/mcp" />
|
||||||
<input
|
</Form.Item>
|
||||||
{...form.getFieldProps('url')}
|
|
||||||
placeholder="https://example.com/mcp"
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Space style={{ justifyContent: 'flex-end', width: '100%', marginTop: 16 }}>
|
<Space style={{ justifyContent: 'flex-end', width: '100%' }}>
|
||||||
<Button onClick={onCancel}>取消</Button>
|
<Button onClick={onCancel}>取消</Button>
|
||||||
<Button type="primary" htmlType="submit">
|
<Button type="primary" htmlType="submit">
|
||||||
保存
|
保存
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -229,7 +229,7 @@ export default function MarketplacePageH5({ logic }: Props) {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Col>
|
||||||
))}
|
))}
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react';
|
||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
import { PointsMallAPI, PointsMallCategory, PointsMallOverview, PointsMallProduct, PointsMallProductsResponse } from '../../api';
|
import { PointsMallAPI, PointsMallCategory, PointsMallOverview, PointsMallProduct, PointsMallProductsResponse } from '../../api';
|
||||||
import { MOCK_OVERVIEW, MOCK_PRODUCTS } from './mocks';
|
import { MOCK_OVERVIEW, MOCK_PRODUCTS } from './mocks';
|
||||||
import type { SortKey } from './types';
|
import type { ExchangeFormValues, SortKey } from './types';
|
||||||
|
|
||||||
export function usePointsMallPageLogic() {
|
export function usePointsMallPageLogic() {
|
||||||
const [overviewLoading, setOverviewLoading] = useState(false);
|
const [overviewLoading, setOverviewLoading] = useState(false);
|
||||||
|
|
@ -37,7 +37,11 @@ export function usePointsMallPageLogic() {
|
||||||
PointsMallAPI.promoEntries(),
|
PointsMallAPI.promoEntries(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const me = meRes.status === 'fulfilled' ? meRes.value : { points: 0, level: 'Lv.0' };
|
const rawMe = meRes.status === 'fulfilled' ? meRes.value : { points: 0, level: 'Lv.0' };
|
||||||
|
const me = {
|
||||||
|
...rawMe,
|
||||||
|
points: Number((rawMe as any)?.points ?? 0),
|
||||||
|
};
|
||||||
const cats = categoriesRes.status === 'fulfilled' ? categoriesRes.value : MOCK_OVERVIEW.categories;
|
const cats = categoriesRes.status === 'fulfilled' ? categoriesRes.value : MOCK_OVERVIEW.categories;
|
||||||
const announcements = announcementsRes.status === 'fulfilled' ? announcementsRes.value : MOCK_OVERVIEW.announcements;
|
const announcements = announcementsRes.status === 'fulfilled' ? announcementsRes.value : MOCK_OVERVIEW.announcements;
|
||||||
const banners = bannersRes.status === 'fulfilled' ? bannersRes.value : MOCK_OVERVIEW.banners;
|
const banners = bannersRes.status === 'fulfilled' ? bannersRes.value : MOCK_OVERVIEW.banners;
|
||||||
|
|
@ -124,10 +128,11 @@ export function usePointsMallPageLogic() {
|
||||||
setPendingExpiresAt(res.expiresAt || new Date(Date.now() + 30 * 60 * 1000).toISOString());
|
setPendingExpiresAt(res.expiresAt || new Date(Date.now() + 30 * 60 * 1000).toISOString());
|
||||||
setConfirmModalVisible(false);
|
setConfirmModalVisible(false);
|
||||||
setExchangeModalVisible(true);
|
setExchangeModalVisible(true);
|
||||||
if (typeof res.remainingPoints === 'number') {
|
const remainingPoints = res.remainingPoints;
|
||||||
|
if (typeof remainingPoints === 'number') {
|
||||||
setOverview((prev) => {
|
setOverview((prev) => {
|
||||||
if (!prev) return prev;
|
if (!prev) return prev;
|
||||||
return { ...prev, me: { ...prev.me, points: res.remainingPoints } };
|
return { ...prev, me: { ...prev.me, points: remainingPoints } };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
message.success('已冻结积分并预扣库存,请继续填写收件信息');
|
message.success('已冻结积分并预扣库存,请继续填写收件信息');
|
||||||
|
|
@ -140,8 +145,34 @@ export function usePointsMallPageLogic() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleExchangeSubmit = async () => {
|
const handleExchangeSubmit = async (values: ExchangeFormValues) => {
|
||||||
if (!selectedProduct || !pendingOrderId) return;
|
if (!selectedProduct || !pendingOrderId) return;
|
||||||
|
setExchangeLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await PointsMallAPI.exchangeSubmitShipping(pendingOrderId, values);
|
||||||
|
setExchangeModalVisible(false);
|
||||||
|
setConfirmModalVisible(false);
|
||||||
|
setPendingOrderId(null);
|
||||||
|
setPendingExpiresAt(null);
|
||||||
|
setSelectedProduct(null);
|
||||||
|
setExchangeQuantity(1);
|
||||||
|
const remainingPoints = res.remainingPoints;
|
||||||
|
if (typeof remainingPoints === 'number') {
|
||||||
|
setOverview((prev) => {
|
||||||
|
if (!prev) return prev;
|
||||||
|
return { ...prev, me: { ...prev.me, points: remainingPoints } };
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
loadOverview();
|
||||||
|
}
|
||||||
|
loadProducts();
|
||||||
|
message.success('兑换成功');
|
||||||
|
} catch (e: any) {
|
||||||
|
const msg = e?.response?.data?.message || e?.response?.data?.error || e?.message || '提交收件信息失败,请稍后重试';
|
||||||
|
message.error(msg);
|
||||||
|
} finally {
|
||||||
|
setExchangeLoading(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
import { Button, Card, Empty, Input, Select, Space, Spin, Tag } from 'antd';
|
import { Button, Card, Empty, Form, Input, Select, Space, Spin, Tag } from 'antd';
|
||||||
import { SearchOutlined } from '@ant-design/icons';
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
import type { PointsMallProduct } from '../../../api';
|
import type { PointsMallProduct } from '../../../api';
|
||||||
import type { PointsMallPageLogicOutput } from '../PointsMallPageLogic';
|
import type { PointsMallPageLogicOutput } from '../PointsMallPageLogic';
|
||||||
import ExchangeModal from '../components/ExchangeModal';
|
import ExchangeModal from '../components/ExchangeModal';
|
||||||
import ConfirmExchangeModal from '../components/ConfirmExchangeModal';
|
import ConfirmExchangeModal from '../components/ConfirmExchangeModal';
|
||||||
|
import type { ExchangeFormValues } from '../types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
logic: PointsMallPageLogicOutput;
|
logic: PointsMallPageLogicOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PointsMallPageH5({ logic }: Props) {
|
export default function PointsMallPageH5({ logic }: Props) {
|
||||||
|
const [exchangeForm] = Form.useForm<ExchangeFormValues>();
|
||||||
const {
|
const {
|
||||||
overview,
|
overview,
|
||||||
overviewLoading,
|
overviewLoading,
|
||||||
|
|
@ -246,9 +248,13 @@ export default function PointsMallPageH5({ logic }: Props) {
|
||||||
quantity={exchangeQuantity}
|
quantity={exchangeQuantity}
|
||||||
expiresAt={pendingExpiresAt}
|
expiresAt={pendingExpiresAt}
|
||||||
loading={exchangeLoading}
|
loading={exchangeLoading}
|
||||||
onSubmit={logic.handleExchangeSubmit}
|
form={exchangeForm}
|
||||||
|
onSubmit={() => {
|
||||||
|
exchangeForm.validateFields().then((values) => logic.handleExchangeSubmit(values));
|
||||||
|
}}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setExchangeModalVisible(false);
|
setExchangeModalVisible(false);
|
||||||
|
exchangeForm.resetFields();
|
||||||
logic.setPendingOrderId(null);
|
logic.setPendingOrderId(null);
|
||||||
logic.setPendingExpiresAt(null);
|
logic.setPendingExpiresAt(null);
|
||||||
logic.setSelectedProduct(null);
|
logic.setSelectedProduct(null);
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
import { Button, Card, Empty, Input, Select, Space, Spin, Tag } from 'antd';
|
import { Button, Card, Empty, Form, Input, Select, Space, Spin, Tag } from 'antd';
|
||||||
import { SearchOutlined } from '@ant-design/icons';
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
import type { PointsMallProduct } from '../../../api';
|
import type { PointsMallProduct } from '../../../api';
|
||||||
import type { PointsMallPageLogicOutput } from '../PointsMallPageLogic';
|
import type { PointsMallPageLogicOutput } from '../PointsMallPageLogic';
|
||||||
import ExchangeModal from '../components/ExchangeModal';
|
import ExchangeModal from '../components/ExchangeModal';
|
||||||
import ConfirmExchangeModal from '../components/ConfirmExchangeModal';
|
import ConfirmExchangeModal from '../components/ConfirmExchangeModal';
|
||||||
|
import type { ExchangeFormValues } from '../types';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
logic: PointsMallPageLogicOutput;
|
logic: PointsMallPageLogicOutput;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PointsMallPageWeb({ logic }: Props) {
|
export default function PointsMallPageWeb({ logic }: Props) {
|
||||||
|
const [exchangeForm] = Form.useForm<ExchangeFormValues>();
|
||||||
const {
|
const {
|
||||||
overview,
|
overview,
|
||||||
overviewLoading,
|
overviewLoading,
|
||||||
|
|
@ -239,9 +241,13 @@ export default function PointsMallPageWeb({ logic }: Props) {
|
||||||
quantity={exchangeQuantity}
|
quantity={exchangeQuantity}
|
||||||
expiresAt={pendingExpiresAt}
|
expiresAt={pendingExpiresAt}
|
||||||
loading={exchangeLoading}
|
loading={exchangeLoading}
|
||||||
onSubmit={logic.handleExchangeSubmit}
|
form={exchangeForm}
|
||||||
|
onSubmit={() => {
|
||||||
|
exchangeForm.validateFields().then((values) => logic.handleExchangeSubmit(values));
|
||||||
|
}}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
setExchangeModalVisible(false);
|
setExchangeModalVisible(false);
|
||||||
|
exchangeForm.resetFields();
|
||||||
logic.setPendingOrderId(null);
|
logic.setPendingOrderId(null);
|
||||||
logic.setPendingExpiresAt(null);
|
logic.setPendingExpiresAt(null);
|
||||||
logic.setSelectedProduct(null);
|
logic.setSelectedProduct(null);
|
||||||
|
|
|
||||||
|
|
@ -62,8 +62,10 @@ export function useTeamsPageLogic() {
|
||||||
handleInvite,
|
handleInvite,
|
||||||
handleDelete,
|
handleDelete,
|
||||||
handleRemoveMember,
|
handleRemoveMember,
|
||||||
|
setActive,
|
||||||
setCreateOpen,
|
setCreateOpen,
|
||||||
setInviteOpen,
|
setInviteOpen,
|
||||||
|
setLastInviteCode,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import {
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { Card, Button, List, Tag, Space, Popconfirm, App as AntApp, Modal, Form, Input, Empty } from 'antd';
|
import { Card, Button, List, Tag, Space, Popconfirm, App as AntApp, Modal, Form, Input, Empty } from 'antd';
|
||||||
import type { Team } from '../../../api';
|
import { TeamAPI, type Team } from '../../../api';
|
||||||
import type { TeamsPageLogicOutput } from '../TeamsPageLogic';
|
import type { TeamsPageLogicOutput } from '../TeamsPageLogic';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import {
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { Card, Button, List, Tag, Space, Popconfirm, App as AntApp, Modal, Form, Input, Empty } from 'antd';
|
import { Card, Button, List, Tag, Space, Popconfirm, App as AntApp, Modal, Form, Input, Empty } from 'antd';
|
||||||
import type { Team } from '../../../api';
|
import { TeamAPI, type Team } from '../../../api';
|
||||||
import type { TeamsPageLogicOutput } from '../TeamsPageLogic';
|
import type { TeamsPageLogicOutput } from '../TeamsPageLogic';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -294,8 +294,7 @@ export default function TeamsPageWeb({ logic }: Props) {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
);
|
)}
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useChatPageLogic } from './chat/ChatPageLogic';
|
import { useChatPageLogic } from './ChatPageLogic';
|
||||||
import ChatPageWeb from './chat/components/ChatPageWeb';
|
import ChatPageWeb from './components/ChatPageWeb';
|
||||||
import ChatPageH5 from './chat/components/ChatPageH5';
|
import ChatPageH5 from './components/ChatPageH5';
|
||||||
|
|
||||||
const isMobileDevice = () => {
|
const isMobileDevice = () => {
|
||||||
if (typeof window === 'undefined') return false;
|
if (typeof window === 'undefined') return false;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||||
import { App as AntApp } from 'antd';
|
import { App as AntApp } from 'antd';
|
||||||
import { SessionAPI } from '../../../api';
|
import type { ModelOverrides } from '../../api';
|
||||||
|
import { SessionAPI } from '../../api';
|
||||||
import { useChatScroll } from './hooks/useChatScroll';
|
import { useChatScroll } from './hooks/useChatScroll';
|
||||||
import { useChatData } from './hooks/useChatData';
|
import { useChatData } from './hooks/useChatData';
|
||||||
import { useChatSender } from './hooks/useChatSender';
|
import { useChatSender } from './hooks/useChatSender';
|
||||||
import { markdownToPlainText } from './utils/copy';
|
|
||||||
|
|
||||||
const lastRoomKey = (agentId: string) => `chat:lastRoom:${agentId}`;
|
const lastRoomKey = (agentId: string) => `chat:lastRoom:${agentId}`;
|
||||||
const isValidRoomId = (v: string | null): v is string => !!v && !String(v).startsWith('legacy_');
|
const isValidRoomId = (v: string | null): v is string => !!v && !String(v).startsWith('legacy_');
|
||||||
|
|
@ -22,6 +22,7 @@ export function useChatPageLogic() {
|
||||||
const [mcpDrawerOpen, setMcpDrawerOpen] = useState(false);
|
const [mcpDrawerOpen, setMcpDrawerOpen] = useState(false);
|
||||||
const [paramsDrawerOpen, setParamsDrawerOpen] = useState(false);
|
const [paramsDrawerOpen, setParamsDrawerOpen] = useState(false);
|
||||||
const [tplDrawerOpen, setTplDrawerOpen] = useState(false);
|
const [tplDrawerOpen, setTplDrawerOpen] = useState(false);
|
||||||
|
const [overrides, setOverrides] = useState<ModelOverrides>({});
|
||||||
|
|
||||||
const abortRef = useRef<AbortController | null>(null);
|
const abortRef = useRef<AbortController | null>(null);
|
||||||
const { bodyRef, scrollBottom, initialScrollDoneRef } = useChatScroll();
|
const { bodyRef, scrollBottom, initialScrollDoneRef } = useChatScroll();
|
||||||
|
|
@ -111,7 +112,7 @@ export function useChatPageLogic() {
|
||||||
setHighlightId,
|
setHighlightId,
|
||||||
scrollBottom,
|
scrollBottom,
|
||||||
initialScrollDoneRef,
|
initialScrollDoneRef,
|
||||||
setOverrides: (updater) => {},
|
setOverrides,
|
||||||
abort: () => abortRef.current?.abort(),
|
abort: () => abortRef.current?.abort(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -120,16 +121,32 @@ export function useChatPageLogic() {
|
||||||
agent,
|
agent,
|
||||||
agentList,
|
agentList,
|
||||||
roomId,
|
roomId,
|
||||||
overrides: {},
|
overrides,
|
||||||
|
setOverrides,
|
||||||
messages,
|
messages,
|
||||||
setMessages,
|
setMessages,
|
||||||
setBranches,
|
setBranches,
|
||||||
loadMessages,
|
loadMessages,
|
||||||
scrollBottom,
|
scrollBottom,
|
||||||
notify: { success: (t) => message.success(t), error: (t) => message.error(t) },
|
notify: { success: (t) => message.success(t), error: (t) => message.error(t) },
|
||||||
abort: abortRef,
|
abortRef,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleNewSession = async () => {
|
||||||
|
if (!id) return;
|
||||||
|
abortRef.current?.abort();
|
||||||
|
try {
|
||||||
|
const created = await SessionAPI.create(id);
|
||||||
|
setRoomId(created.id);
|
||||||
|
setHighlightId(null);
|
||||||
|
setMessages(() => []);
|
||||||
|
setBranches({});
|
||||||
|
message.success('已创建新会话');
|
||||||
|
} catch (e: any) {
|
||||||
|
message.error('创建房间失败:' + (e?.message ?? e));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
roomId,
|
roomId,
|
||||||
|
|
@ -147,10 +164,15 @@ export function useChatPageLogic() {
|
||||||
initialScrollDoneRef,
|
initialScrollDoneRef,
|
||||||
sender,
|
sender,
|
||||||
navigate,
|
navigate,
|
||||||
|
overrides,
|
||||||
|
setOverrides,
|
||||||
|
setRoomId,
|
||||||
|
setHighlightId,
|
||||||
setHistoryDrawerOpen,
|
setHistoryDrawerOpen,
|
||||||
setMcpDrawerOpen,
|
setMcpDrawerOpen,
|
||||||
setParamsDrawerOpen,
|
setParamsDrawerOpen,
|
||||||
setTplDrawerOpen,
|
setTplDrawerOpen,
|
||||||
|
handleNewSession,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
import { Empty } from 'antd';
|
import { App as AntApp, Button, Drawer, Empty, Space } from 'antd';
|
||||||
import AgentSidebar from './AgentSidebar';
|
import { useState } from 'react';
|
||||||
import ChatHeader from './components/ChatHeader';
|
import type { ModelOverrides } from '../../../api';
|
||||||
import ChatBody from './components/ChatBody';
|
|
||||||
import ChatInput from './components/ChatInput';
|
|
||||||
import ChatDrawers from './components/ChatDrawers';
|
|
||||||
import ChatOutline from './components/ChatOutline';
|
|
||||||
import type { ChatPageLogicOutput } from '../ChatPageLogic';
|
import type { ChatPageLogicOutput } from '../ChatPageLogic';
|
||||||
|
import { markdownToPlainText } from '../utils/copy';
|
||||||
|
import AgentSidebar from './AgentSidebar';
|
||||||
|
import ChatBody from './ChatBody';
|
||||||
|
import ChatDrawers from './ChatDrawers';
|
||||||
|
import ChatHeader from './ChatHeader';
|
||||||
|
import ChatInput from './ChatInput';
|
||||||
|
import ChatOutline from './ChatOutline';
|
||||||
|
|
||||||
interface Props {
|
export default function ChatPageH5({ logic }: { logic: ChatPageLogicOutput }) {
|
||||||
logic: ChatPageLogicOutput;
|
const { message } = AntApp.useApp();
|
||||||
}
|
const [agentDrawerOpen, setAgentDrawerOpen] = useState(false);
|
||||||
|
const [outlineDrawerOpen, setOutlineDrawerOpen] = useState(false);
|
||||||
|
|
||||||
export default function ChatPageH5({ logic }: Props) {
|
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
roomId,
|
roomId,
|
||||||
|
|
@ -25,32 +28,81 @@ export default function ChatPageH5({ logic }: Props) {
|
||||||
messages,
|
messages,
|
||||||
branches,
|
branches,
|
||||||
bodyRef,
|
bodyRef,
|
||||||
scrollBottom,
|
|
||||||
initialScrollDoneRef,
|
|
||||||
sender,
|
sender,
|
||||||
navigate,
|
navigate,
|
||||||
|
overrides,
|
||||||
|
setOverrides,
|
||||||
|
setRoomId,
|
||||||
|
setHighlightId,
|
||||||
setHistoryDrawerOpen,
|
setHistoryDrawerOpen,
|
||||||
setMcpDrawerOpen,
|
setMcpDrawerOpen,
|
||||||
setParamsDrawerOpen,
|
setParamsDrawerOpen,
|
||||||
setTplDrawerOpen,
|
setTplDrawerOpen,
|
||||||
|
handleNewSession,
|
||||||
} = logic;
|
} = logic;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="chat-shell h5-chat-shell">
|
<div className="chat-shell h5-chat-shell">
|
||||||
|
<Drawer
|
||||||
|
title="选择智能体"
|
||||||
|
placement="left"
|
||||||
|
open={agentDrawerOpen}
|
||||||
|
onClose={() => setAgentDrawerOpen(false)}
|
||||||
|
width={320}
|
||||||
|
styles={{ body: { padding: 0 } }}
|
||||||
|
>
|
||||||
<AgentSidebar
|
<AgentSidebar
|
||||||
agentList={agentList}
|
agentList={agentList}
|
||||||
activeAgentId={id}
|
activeAgentId={id}
|
||||||
onCreate={() => navigate('/agents/new')}
|
onCreate={() => {
|
||||||
onSelect={(aid) => navigate(`/chat/${aid}`)}
|
setAgentDrawerOpen(false);
|
||||||
|
navigate('/agents/new');
|
||||||
|
}}
|
||||||
|
onSelect={(aid) => {
|
||||||
|
setAgentDrawerOpen(false);
|
||||||
|
navigate(`/chat/${aid}`);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
</Drawer>
|
||||||
|
|
||||||
|
<Drawer
|
||||||
|
title="对话大纲"
|
||||||
|
placement="right"
|
||||||
|
open={outlineDrawerOpen}
|
||||||
|
onClose={() => setOutlineDrawerOpen(false)}
|
||||||
|
width={320}
|
||||||
|
styles={{ body: { padding: 0 } }}
|
||||||
|
>
|
||||||
|
<ChatOutline
|
||||||
|
messages={messages}
|
||||||
|
activeId={highlightId}
|
||||||
|
onJump={(msgId) => {
|
||||||
|
setHighlightId(msgId);
|
||||||
|
setOutlineDrawerOpen(false);
|
||||||
|
const el = document.getElementById('msg-' + msgId);
|
||||||
|
if (el) el.scrollIntoView({ block: 'start', behavior: 'smooth' });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Drawer>
|
||||||
|
|
||||||
<section className="chat-main h5-chat-main">
|
<section className="chat-main h5-chat-main">
|
||||||
{!agent ? (
|
{!agent ? (
|
||||||
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
<div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||||
<Empty description="请在左侧选择一个智能体开始对话" />
|
<Empty description="请选择一个智能体开始对话" />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
<div style={{ padding: '8px 12px', borderBottom: '1px solid var(--color-border)' }}>
|
||||||
|
<Space>
|
||||||
|
<Button size="small" onClick={() => setAgentDrawerOpen(true)}>
|
||||||
|
智能体
|
||||||
|
</Button>
|
||||||
|
<Button size="small" onClick={() => setOutlineDrawerOpen(true)}>
|
||||||
|
大纲
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ChatHeader
|
<ChatHeader
|
||||||
agent={agent}
|
agent={agent}
|
||||||
useStream={sender.useStream}
|
useStream={sender.useStream}
|
||||||
|
|
@ -77,20 +129,9 @@ export default function ChatPageH5({ logic }: Props) {
|
||||||
onSwitchBranch={sender.handleSwitchBranch}
|
onSwitchBranch={sender.handleSwitchBranch}
|
||||||
onCopy={(text, mode) => {
|
onCopy={(text, mode) => {
|
||||||
const content = mode === 'markdown' ? text : markdownToPlainText(text);
|
const content = mode === 'markdown' ? text : markdownToPlainText(text);
|
||||||
return navigator.clipboard
|
navigator.clipboard?.writeText(content).then(() => {
|
||||||
?.writeText(content)
|
message.success(mode === 'markdown' ? '已复制(Markdown)' : '已复制(纯文本)');
|
||||||
?.then(() => message.success(mode === 'markdown' ? '已复制(Markdown)' : '已复制(纯文本)'));
|
});
|
||||||
}}
|
|
||||||
/>
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ChatOutline
|
|
||||||
messages={messages}
|
|
||||||
activeId={highlightId}
|
|
||||||
onJump={(msgId) => {
|
|
||||||
setHighlightId(msgId);
|
|
||||||
const el = document.getElementById('msg-' + msgId);
|
|
||||||
if (el) el.scrollIntoView({ block: 'start', behavior: 'smooth' });
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -109,26 +150,44 @@ export default function ChatPageH5({ logic }: Props) {
|
||||||
onOpenTpl={() => setTplDrawerOpen(true)}
|
onOpenTpl={() => setTplDrawerOpen(true)}
|
||||||
modelOptions={sender.modelOptions}
|
modelOptions={sender.modelOptions}
|
||||||
activeModelValue={sender.activeModelValue}
|
activeModelValue={sender.activeModelValue}
|
||||||
onChangeModel={sender.onChangeModel}
|
onChangeModel={(modelId) => {
|
||||||
|
const picked = sender.modelOptions.find((o) => o.value === modelId);
|
||||||
|
setOverrides((o: ModelOverrides) => ({
|
||||||
|
...o,
|
||||||
|
model_id: modelId,
|
||||||
|
model: picked?.label ?? o.model
|
||||||
|
}));
|
||||||
|
}}
|
||||||
agentList={agentList}
|
agentList={agentList}
|
||||||
onInsertMention={() => {}}
|
onInsertMention={() => {}}
|
||||||
onOpenHistory={() => setHistoryDrawerOpen(true)}
|
onOpenHistory={() => setHistoryDrawerOpen(true)}
|
||||||
onNewSession={async () => {
|
onNewSession={handleNewSession}
|
||||||
if (!id) return;
|
/>
|
||||||
abortRef.current?.abort();
|
</>
|
||||||
try {
|
)}
|
||||||
const created = await SessionAPI.create(id);
|
</section>
|
||||||
setRoomId(created.id);
|
|
||||||
messages.length && messages.splice(0, messages.length);
|
<ChatDrawers
|
||||||
branches && Object.keys(branches).length && Object.values(branches).forEach(() => {});
|
agentId={id}
|
||||||
} catch (e) {
|
agent={agent}
|
||||||
message.error('创建房间失败');
|
input={sender.input}
|
||||||
}
|
setInput={(updater) => sender.setInput(updater)}
|
||||||
}}
|
overrides={overrides}
|
||||||
|
setOverrides={setOverrides}
|
||||||
|
roomId={roomId}
|
||||||
|
setRoomId={setRoomId}
|
||||||
|
setHighlightId={setHighlightId}
|
||||||
|
sessionRefresh={sender.sessionRefresh}
|
||||||
|
mcpDrawerOpen={mcpDrawerOpen}
|
||||||
|
setMcpDrawerOpen={setMcpDrawerOpen}
|
||||||
|
tplDrawerOpen={tplDrawerOpen}
|
||||||
|
setTplDrawerOpen={setTplDrawerOpen}
|
||||||
|
paramsDrawerOpen={paramsDrawerOpen}
|
||||||
|
setParamsDrawerOpen={setParamsDrawerOpen}
|
||||||
|
historyDrawerOpen={historyDrawerOpen}
|
||||||
|
setHistoryDrawerOpen={setHistoryDrawerOpen}
|
||||||
|
notify={{ success: (t) => message.success(t) }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
import { Empty } from 'antd';
|
import { App as AntApp, Empty } from 'antd';
|
||||||
import AgentSidebar from './AgentSidebar';
|
|
||||||
import ChatHeader from './components/ChatHeader';
|
|
||||||
import ChatBody from './components/ChatBody';
|
|
||||||
import ChatInput from './components/ChatInput';
|
|
||||||
import ChatDrawers from './components/ChatDrawers';
|
|
||||||
import ChatOutline from './components/ChatOutline';
|
|
||||||
import type { ChatPageLogicOutput } from '../ChatPageLogic';
|
import type { ChatPageLogicOutput } from '../ChatPageLogic';
|
||||||
|
import { markdownToPlainText } from '../utils/copy';
|
||||||
|
import type { ModelOverrides } from '../../../api';
|
||||||
|
import AgentSidebar from './AgentSidebar';
|
||||||
|
import ChatBody from './ChatBody';
|
||||||
|
import ChatDrawers from './ChatDrawers';
|
||||||
|
import ChatHeader from './ChatHeader';
|
||||||
|
import ChatInput from './ChatInput';
|
||||||
|
import ChatOutline from './ChatOutline';
|
||||||
|
|
||||||
interface Props {
|
export default function ChatPageWeb({ logic }: { logic: ChatPageLogicOutput }) {
|
||||||
logic: ChatPageLogicOutput;
|
const { message } = AntApp.useApp();
|
||||||
}
|
|
||||||
|
|
||||||
export default function ChatPageWeb({ logic }: Props) {
|
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
roomId,
|
roomId,
|
||||||
|
|
@ -25,14 +25,17 @@ export default function ChatPageWeb({ logic }: Props) {
|
||||||
messages,
|
messages,
|
||||||
branches,
|
branches,
|
||||||
bodyRef,
|
bodyRef,
|
||||||
scrollBottom,
|
|
||||||
initialScrollDoneRef,
|
|
||||||
sender,
|
sender,
|
||||||
navigate,
|
navigate,
|
||||||
|
overrides,
|
||||||
|
setOverrides,
|
||||||
|
setRoomId,
|
||||||
|
setHighlightId,
|
||||||
setHistoryDrawerOpen,
|
setHistoryDrawerOpen,
|
||||||
setMcpDrawerOpen,
|
setMcpDrawerOpen,
|
||||||
setParamsDrawerOpen,
|
setParamsDrawerOpen,
|
||||||
setTplDrawerOpen,
|
setTplDrawerOpen,
|
||||||
|
handleNewSession,
|
||||||
} = logic;
|
} = logic;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -77,12 +80,11 @@ export default function ChatPageWeb({ logic }: Props) {
|
||||||
onSwitchBranch={sender.handleSwitchBranch}
|
onSwitchBranch={sender.handleSwitchBranch}
|
||||||
onCopy={(text, mode) => {
|
onCopy={(text, mode) => {
|
||||||
const content = mode === 'markdown' ? text : markdownToPlainText(text);
|
const content = mode === 'markdown' ? text : markdownToPlainText(text);
|
||||||
return navigator.clipboard
|
navigator.clipboard?.writeText(content).then(() => {
|
||||||
?.writeText(content)
|
message.success(mode === 'markdown' ? '已复制(Markdown)' : '已复制(纯文本)');
|
||||||
?.then(() => message.success(mode === 'markdown' ? '已复制(Markdown)' : '已复制(纯文本)'));
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
/>
|
|
||||||
|
|
||||||
<ChatOutline
|
<ChatOutline
|
||||||
messages={messages}
|
messages={messages}
|
||||||
|
|
@ -109,32 +111,30 @@ export default function ChatPageWeb({ logic }: Props) {
|
||||||
onOpenTpl={() => setTplDrawerOpen(true)}
|
onOpenTpl={() => setTplDrawerOpen(true)}
|
||||||
modelOptions={sender.modelOptions}
|
modelOptions={sender.modelOptions}
|
||||||
activeModelValue={sender.activeModelValue}
|
activeModelValue={sender.activeModelValue}
|
||||||
onChangeModel={sender.onChangeModel}
|
onChangeModel={(modelId) => {
|
||||||
|
const picked = sender.modelOptions.find((o) => o.value === modelId);
|
||||||
|
setOverrides((o: ModelOverrides) => ({
|
||||||
|
...o,
|
||||||
|
model_id: modelId,
|
||||||
|
model: picked?.label ?? o.model
|
||||||
|
}));
|
||||||
|
}}
|
||||||
agentList={agentList}
|
agentList={agentList}
|
||||||
onInsertMention={() => {}}
|
onInsertMention={() => {}}
|
||||||
onOpenHistory={() => setHistoryDrawerOpen(true)}
|
onOpenHistory={() => setHistoryDrawerOpen(true)}
|
||||||
onNewSession={async () => {
|
onNewSession={handleNewSession}
|
||||||
if (!id) return;
|
|
||||||
abortRef.current?.abort();
|
|
||||||
try {
|
|
||||||
const created = await SessionAPI.create(id);
|
|
||||||
setRoomId(created.id);
|
|
||||||
messages.length && messages.splice(0, messages.length);
|
|
||||||
branches && Object.keys(branches).length && Object.values(branches).forEach(() => {});
|
|
||||||
} catch (e) {
|
|
||||||
message.error('创建房间失败');
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
)}
|
||||||
|
</section>
|
||||||
|
|
||||||
<ChatDrawers
|
<ChatDrawers
|
||||||
agentId={id}
|
agentId={id}
|
||||||
agent={agent}
|
agent={agent}
|
||||||
input={sender.input}
|
input={sender.input}
|
||||||
setInput={(updater) => sender.setInput(updater(sender.input))}
|
setInput={(updater) => sender.setInput(updater)}
|
||||||
|
overrides={overrides}
|
||||||
|
setOverrides={setOverrides}
|
||||||
roomId={roomId}
|
roomId={roomId}
|
||||||
setRoomId={setRoomId}
|
setRoomId={setRoomId}
|
||||||
setHighlightId={setHighlightId}
|
setHighlightId={setHighlightId}
|
||||||
|
|
@ -149,6 +149,6 @@ export default function ChatPageWeb({ logic }: Props) {
|
||||||
setHistoryDrawerOpen={setHistoryDrawerOpen}
|
setHistoryDrawerOpen={setHistoryDrawerOpen}
|
||||||
notify={{ success: (t) => message.success(t) }}
|
notify={{ success: (t) => message.success(t) }}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
|
||||||
|
|
@ -619,6 +619,64 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sidebar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-shell {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-header {
|
||||||
|
height: auto;
|
||||||
|
min-height: 56px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
gap: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-header-agent-name {
|
||||||
|
max-width: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-header-agent-desc,
|
||||||
|
.chat-header-agent-meta {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-body .messages-container {
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 18px 12px 96px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bubble {
|
||||||
|
max-width: 92%;
|
||||||
|
padding: 12px 14px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input-wrapper {
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 0 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-input-actions {
|
||||||
|
top: -22px;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-model-select {
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 220px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.chat-body .messages-container {
|
.chat-body .messages-container {
|
||||||
max-width: 780px;
|
max-width: 780px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue