fix(style): text overflow

This commit is contained in:
jialin
2024-10-25 18:36:18 +08:00
parent ba49fa6c88
commit abdc78e123
14 changed files with 119 additions and 99 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ const isProduction = env === 'production';
const t = Date.now(); const t = Date.now();
export default defineConfig({ export default defineConfig({
proxy: { proxy: {
...proxy('http://192.168.50.3') ...proxy()
}, },
history: { history: {
type: 'hash' type: 'hash'
+16 -9
View File
@@ -1,5 +1,5 @@
import { Tag, Tooltip, type TagProps } from 'antd'; import { Tag, Tooltip, type TagProps } from 'antd';
import debounce from 'lodash/debounce'; import { throttle } from 'lodash';
import React, { import React, {
useCallback, useCallback,
useEffect, useEffect,
@@ -7,6 +7,7 @@ import React, {
useRef, useRef,
useState useState
} from 'react'; } from 'react';
import TitleTip from './title-tip';
// type TagProps = React.ComponentProps<typeof Tag>; // type TagProps = React.ComponentProps<typeof Tag>;
@@ -34,6 +35,7 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
}) => { }) => {
const contentRef = useRef<HTMLDivElement>(null); const contentRef = useRef<HTMLDivElement>(null);
const [isOverflowing, setIsOverflowing] = useState(false); const [isOverflowing, setIsOverflowing] = useState(false);
const resizeObserver = useRef<ResizeObserver>();
const checkOverflow = useCallback(() => { const checkOverflow = useCallback(() => {
if (contentRef.current) { if (contentRef.current) {
@@ -42,19 +44,14 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
} }
}, [contentRef.current]); }, [contentRef.current]);
const debouncedCheckOverflow = useMemo(
() => debounce(checkOverflow, 200),
[checkOverflow]
);
useEffect(() => { useEffect(() => {
checkOverflow(); const debouncedCheckOverflow = throttle(checkOverflow, 200);
window.addEventListener('resize', debouncedCheckOverflow); window.addEventListener('resize', debouncedCheckOverflow);
return () => { return () => {
window.removeEventListener('resize', debouncedCheckOverflow); window.removeEventListener('resize', debouncedCheckOverflow);
debouncedCheckOverflow.cancel(); debouncedCheckOverflow.cancel();
}; };
}, [checkOverflow, debouncedCheckOverflow]); }, [checkOverflow]);
useEffect(() => { useEffect(() => {
checkOverflow(); checkOverflow();
@@ -74,7 +71,17 @@ const AutoTooltip: React.FC<AutoTooltipProps> = ({
return ( return (
<Tooltip <Tooltip
title={isOverflowing || showTitle ? title || children : ''} overlayInnerStyle={{ paddingInline: 0 }}
destroyTooltipOnHide={false}
title={
<TitleTip
isOverflowing={isOverflowing}
title={title}
showTitle={showTitle}
>
{children}
</TitleTip>
}
{...tooltipProps} {...tooltipProps}
> >
{ghost ? ( {ghost ? (
+31
View File
@@ -0,0 +1,31 @@
import useOverlayScroller from '@/hooks/use-overlay-scroller';
import React from 'react';
interface TitleTipProps {
isOverflowing: boolean;
showTitle: boolean;
title: React.ReactNode;
children: React.ReactNode;
}
const TitleTip: React.FC<TitleTipProps> = (props) => {
const { isOverflowing, showTitle, title, children } = props;
const { initialize } = useOverlayScroller();
const scrollRef = React.useRef<any>(null);
React.useEffect(() => {
if (scrollRef.current) {
initialize(scrollRef.current);
}
}, [initialize, scrollRef.current]);
return (
<div style={{ maxHeight: 200, overflowY: 'auto' }} ref={scrollRef}>
<div style={{ width: 'max-content', maxWidth: 250, paddingInline: 10 }}>
{isOverflowing || showTitle ? title || children : ''}
</div>
</div>
);
};
export default React.memo(TitleTip);
@@ -73,6 +73,7 @@ const CodeViewer: React.FC<CodeViewerProps> = (props) => {
}} }}
> >
<code <code
style={{ minHeight: height }}
className={highlightedCode.className} className={highlightedCode.className}
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: highlightedCode.value __html: highlightedCode.value
+2
View File
@@ -9,9 +9,11 @@
button { button {
color: rgba(255, 255, 255, 70%); color: rgba(255, 255, 255, 70%);
background-color: rgba(71, 71, 71, 100%);
&:hover { &:hover {
color: rgba(255, 255, 255, 90%) !important; color: rgba(255, 255, 255, 90%) !important;
background-color: rgba(71, 71, 71, 100%) !important;
} }
} }
} }
+9 -6
View File
@@ -3,7 +3,7 @@ import useOverlayScroller from '@/hooks/use-overlay-scroller';
import '@xterm/xterm/css/xterm.css'; import '@xterm/xterm/css/xterm.css';
import classNames from 'classnames'; import classNames from 'classnames';
import _ from 'lodash'; import _ from 'lodash';
import { memo, useCallback, useEffect, useRef, useState } from 'react'; import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import CopyButton from '../copy-button'; import CopyButton from '../copy-button';
import './index.less'; import './index.less';
import useParseAnsi from './parse-ansi'; import useParseAnsi from './parse-ansi';
@@ -90,6 +90,13 @@ const LogsViewer: React.FC<LogsViewerProps> = (props) => {
updateScrollerPosition(0); updateScrollerPosition(0);
}, 200); }, 200);
const copyText = useMemo(() => {
if (!logs.length) {
return '';
}
return logs?.map((item) => item.content).join('\n');
}, [logs]);
useEffect(() => { useEffect(() => {
createChunkConnection(); createChunkConnection();
return () => { return () => {
@@ -121,11 +128,7 @@ const LogsViewer: React.FC<LogsViewerProps> = (props) => {
return ( return (
<div className="logs-viewer-wrap-w2"> <div className="logs-viewer-wrap-w2">
<span className="copy"> <span className="copy">
<CopyButton <CopyButton text={copyText} type="text" size="small"></CopyButton>
text={logs?.map((item) => item.content).join('\n')}
type="text"
size="small"
></CopyButton>
</span> </span>
<div <div
className="wrap" className="wrap"
+1 -1
View File
@@ -11,7 +11,7 @@ const VersionInfo: React.FC<{ intl: any }> = ({ intl }) => {
const currentVersion = getAtomStorage(GPUStackVersionAtom)?.version; const currentVersion = getAtomStorage(GPUStackVersionAtom)?.version;
const isProd = const isProd =
currentVersion !== '0.0.0' && currentVersion.indexOf('rc') === -1; currentVersion !== '0.0.0' && currentVersion?.indexOf('rc') === -1;
const uiVersion = document.documentElement.getAttribute('data-version'); const uiVersion = document.documentElement.getAttribute('data-version');
@@ -5,11 +5,6 @@ import _ from 'lodash';
import { memo, useContext, useMemo } from 'react'; import { memo, useContext, useMemo } from 'react';
import { DashboardContext } from '../config/dashboard-context'; import { DashboardContext } from '../config/dashboard-context';
const chartColorMap = {
tickLineColor: 'rgba(217,217,217,0.5)',
axislabelColor: 'rgba(0, 0, 0, 0.4)'
};
const TypeKeyMap = { const TypeKeyMap = {
cpu: { cpu: {
label: 'CPU', label: 'CPU',
+2 -2
View File
@@ -60,7 +60,7 @@ const ModelCard: React.FC<{
} }
}; };
const removeMetadata = (str: string) => { const removeMetadata = useCallback((str: string) => {
let indexes = []; let indexes = [];
let index = str.indexOf('---'); let index = str.indexOf('---');
@@ -75,7 +75,7 @@ const ModelCard: React.FC<{
return str.slice(indexes[1] + 3); return str.slice(indexes[1] + 3);
} }
return str; return str;
}; }, []);
// huggingface model card data // huggingface model card data
const getHuggingfaceModelDetail = async () => { const getHuggingfaceModelDetail = async () => {
@@ -10,6 +10,7 @@ import {
memo, memo,
useEffect, useEffect,
useImperativeHandle, useImperativeHandle,
useMemo,
useRef, useRef,
useState useState
} from 'react'; } from 'react';
@@ -51,7 +52,6 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
const currentMessageRef = useRef<any>(null); const currentMessageRef = useRef<any>(null);
const paramsRef = useRef<any>(null); const paramsRef = useRef<any>(null);
const messageListLengthCache = useRef<number>(0); const messageListLengthCache = useRef<number>(0);
const [viewCodeMessage, setViewCodeMessage] = useState<any[]>([]);
const { initialize, updateScrollerPosition } = useOverlayScroller(); const { initialize, updateScrollerPosition } = useOverlayScroller();
const { initialize: innitializeParams } = useOverlayScroller(); const { initialize: innitializeParams } = useOverlayScroller();
@@ -68,6 +68,13 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
}; };
}); });
const viewCodeMessage = useMemo(() => {
return generateMessages([
{ role: Roles.System, content: systemMessage },
...messageList
]);
}, [messageList, systemMessage]);
const setMessageId = () => { const setMessageId = () => {
messageId.current = messageId.current + 1; messageId.current = messageId.current + 1;
}; };
@@ -134,10 +141,6 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
setMessageList((pre) => { setMessageList((pre) => {
return [...pre, ...currentMessageRef.current]; return [...pre, ...currentMessageRef.current];
}); });
console.log('messageList:', [
...messageList,
...currentMessageRef.current
]);
const messageParams = [ const messageParams = [
{ role: Roles.System, content: systemMessage }, { role: Roles.System, content: systemMessage },
@@ -147,8 +150,6 @@ const GroundLeft: React.FC<MessageProps> = forwardRef((props, ref) => {
const messages = generateMessages(messageParams); const messages = generateMessages(messageParams);
setViewCodeMessage(messages);
const chatParams = { const chatParams = {
messages: messages, messages: messages,
...parameters, ...parameters,
@@ -242,6 +242,7 @@ const MessageInput: React.FC<MessageInputProps> = ({
const handleOnPaste = (e: any) => { const handleOnPaste = (e: any) => {
e.preventDefault(); e.preventDefault();
const text = e.clipboardData.getData('text'); const text = e.clipboardData.getData('text');
if (text) { if (text) {
const startPos = e.target.selectionStart; const startPos = e.target.selectionStart;
@@ -253,6 +254,11 @@ const MessageInput: React.FC<MessageInputProps> = ({
text + text +
message.content.slice(endPos) message.content.slice(endPos)
}); });
if (endPos !== startPos) {
setTimeout(() => {
e.target.setSelectionRange(endPos, endPos);
}, 0);
}
} else { } else {
getPasteContent(e); getPasteContent(e);
} }
@@ -116,6 +116,11 @@ const ContentItem: React.FC<MessageItemProps> = ({
data.content.slice(0, startPos) + text + data.content.slice(endPos), data.content.slice(0, startPos) + text + data.content.slice(endPos),
uid: data.uid uid: data.uid
}); });
if (endPos !== startPos) {
setTimeout(() => {
e.target.setSelectionRange(endPos, endPos);
}, 0);
}
} else { } else {
getPasteContent(e); getPasteContent(e);
} }
@@ -67,10 +67,16 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
const currentMessageRef = useRef<MessageItem[]>([]); const currentMessageRef = useRef<MessageItem[]>([]);
const modelScrollRef = useRef<any>(null); const modelScrollRef = useRef<any>(null);
const messageListLengthCache = useRef<number>(0); const messageListLengthCache = useRef<number>(0);
const [viewCodeMessage, setViewCodeMessage] = useState<any[]>([]);
const { initialize, updateScrollerPosition } = useOverlayScroller(); const { initialize, updateScrollerPosition } = useOverlayScroller();
const viewCodeMessage = useMemo(() => {
return generateMessages([
{ role: Roles.System, content: systemMessage },
...messageList
]);
}, [messageList, systemMessage]);
const setMessageId = () => { const setMessageId = () => {
messageId.current = messageId.current + 1; messageId.current = messageId.current + 1;
}; };
@@ -135,8 +141,6 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
const messages = generateMessages(messageParams); const messages = generateMessages(messageParams);
setViewCodeMessage(messages);
const chatParams = { const chatParams = {
messages: messages, messages: messages,
...params, ...params,
@@ -362,14 +366,29 @@ const ModelItem: React.FC<ModelItemProps> = forwardRef(
options={modelFullList} options={modelFullList}
onChange={handleModelChange} onChange={handleModelChange}
value={params.model} value={params.model}
optionRender={(data) => { labelRender={(data) => {
return ( return (
<AutoTooltip <AutoTooltip
title={data.label}
ghost ghost
tooltipProps={{ tooltipProps={{
placement: 'right' placement: 'right'
}} }}
minWidth={60}
maxWidth={180}
>
{data.label}
</AutoTooltip>
);
}}
optionRender={(data) => {
return (
<AutoTooltip
ghost
tooltipProps={{
placement: 'right'
}}
minWidth={60}
maxWidth={180}
> >
{data.label} {data.label}
</AutoTooltip> </AutoTooltip>
@@ -4,7 +4,7 @@ import { BulbOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Button, Modal } from 'antd'; import { Button, Modal } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import React, { useEffect, useState } from 'react'; import React, { useMemo, useState } from 'react';
type ViewModalProps = { type ViewModalProps = {
systemMessage?: string; systemMessage?: string;
@@ -33,14 +33,12 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
title, title,
open, open,
onCancel, onCancel,
systemMessage,
messageList, messageList,
parameters = {}, parameters = {},
apiType = 'chat' apiType = 'chat'
} = props || {}; } = props || {};
const intl = useIntl(); const intl = useIntl();
const [codeValue, setCodeValue] = useState('');
const [lang, setLang] = useState(langMap.shell); const [lang, setLang] = useState(langMap.shell);
const BaseURL = `${window.location.origin}/v1-openai`; const BaseURL = `${window.location.origin}/v1-openai`;
@@ -49,40 +47,7 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
const logcommand = const logcommand =
apiType === 'chat' ? 'choices[0].message.content' : 'data[0].embedding'; apiType === 'chat' ? 'choices[0].message.content' : 'data[0].embedding';
const generateCode = () => { const codeValue = useMemo(() => {
// const systemList = systemMessage
// ? [
// {
// role: 'system',
// content: [
// {
// type: 'text',
// text: systemMessage
// }
// ]
// }
// ]
// : [];
// const formatMessageList = _.map(messageList, (item: any) => {
// return {
// role: item.role,
// content: [
// {
// type: 'text',
// text: item.content
// },
// ..._.map(item.imgs, (img: any) => {
// return {
// type: 'image_url',
// image_url: {
// url: img.dataUrl
// }
// };
// })
// ]
// };
// });
if (lang === langMap.shell) { if (lang === langMap.shell) {
const messages = messageList; const messages = messageList;
const code = `curl ${window.location.origin}/v1-openai/${api} \\\n-H "Content-Type: application/json" \\\n-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\\n-d '${JSON.stringify( const code = `curl ${window.location.origin}/v1-openai/${api} \\\n-H "Content-Type: application/json" \\\n-H "Authorization: Bearer $\{YOUR_GPUSTACK_API_KEY}" \\\n-d '${JSON.stringify(
@@ -93,8 +58,9 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
null, null,
2 2
)}'`; )}'`;
setCodeValue(code); return code;
} else if (lang === langMap.javascript) { }
if (lang === langMap.javascript) {
const messages = messageList; const messages = messageList;
const code = `const OpenAI = require("openai");\n\nconst openai = new OpenAI({\n "apiKey": "YOUR_GPUSTACK_API_KEY",\n "baseURL": "${BaseURL}"\n});\n\nasync function main(){\n const params = ${JSON.stringify( const code = `const OpenAI = require("openai");\n\nconst openai = new OpenAI({\n "apiKey": "YOUR_GPUSTACK_API_KEY",\n "baseURL": "${BaseURL}"\n});\n\nasync function main(){\n const params = ${JSON.stringify(
{ {
@@ -104,8 +70,10 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
null, null,
4 4
)};\nconst response = await openai.${ClientType}.create(params);\n console.log(response.${logcommand});\n}\nmain();`; )};\nconst response = await openai.${ClientType}.create(params);\n console.log(response.${logcommand});\n}\nmain();`;
setCodeValue(code);
} else if (lang === langMap.python) { return code;
}
if (lang === langMap.python) {
const formattedParams = _.keys(parameters).reduce( const formattedParams = _.keys(parameters).reduce(
(acc: string, key: string) => { (acc: string, key: string) => {
if (parameters[key] === null) { if (parameters[key] === null) {
@@ -124,9 +92,10 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
? `messages=${JSON.stringify(messageList, null, 2)}` ? `messages=${JSON.stringify(messageList, null, 2)}`
: ''; : '';
const code = `from openai import OpenAI\n\nclient = OpenAI(\n base_url="${BaseURL}", \n api_key="YOUR_GPUSTACK_API_KEY"\n)\n\nresponse = client.${ClientType}.create(\n${formattedParams} ${messages})\nprint(response.${logcommand})`; const code = `from openai import OpenAI\n\nclient = OpenAI(\n base_url="${BaseURL}", \n api_key="YOUR_GPUSTACK_API_KEY"\n)\n\nresponse = client.${ClientType}.create(\n${formattedParams} ${messages})\nprint(response.${logcommand})`;
setCodeValue(code); return code;
} }
}; return '';
}, [lang, messageList, parameters]);
const handleOnChangeLang = (value: string) => { const handleOnChangeLang = (value: string) => {
setLang(value); setLang(value);
@@ -136,25 +105,6 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
setLang(langMap.shell); setLang(langMap.shell);
onCancel(); onCancel();
}; };
const editorConfig = {
minimap: {
enabled: false
},
hover: {
enabled: false
},
readOnly: true,
formatOnType: true,
formatOnPaste: true,
fontWeight: 'bold',
scrollbar: {
verticalSliderSize: 8
}
};
useEffect(() => {
generateCode();
}, [lang, messageList, parameters]);
return ( return (
<> <>
@@ -228,4 +178,4 @@ const ViewCodeModal: React.FC<ViewModalProps> = (props) => {
); );
}; };
export default ViewCodeModal; export default React.memo(ViewCodeModal);