Skip to content

Commit 658c294

Browse files
authored
💄 style: add lab to support disable/enable rich text (lobehub#9652)
* add abstract chunk prompt eval * add labs page
1 parent f71d1f4 commit 658c294

File tree

11 files changed

+275
-67
lines changed

11 files changed

+275
-67
lines changed

packages/prompts/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"test": "vitest",
1010
"test:coverage": "vitest --coverage --silent='passed-only'",
1111
"test:prompts": "pnpm test:prompts:translate && pnpm test:prompts:summary && pnpm test:prompts:lang && pnpm test:prompts:emoji && pnpm test:prompts:qa",
12+
"test:prompts:abstract-chunk": "promptfoo eval -c promptfoo/abstract-chunk/eval.yaml",
1213
"test:prompts:emoji": "promptfoo eval -c promptfoo/emoji-picker/eval.yaml",
1314
"test:prompts:lang": "promptfoo eval -c promptfoo/language-detection/eval.yaml",
1415
"test:prompts:qa": "promptfoo eval -c promptfoo/knowledge-qa/eval.yaml",
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
description: Test chunk text summarization in different languages
2+
3+
providers:
4+
- openai:chat:gpt-5-mini
5+
- openai:chat:claude-3-5-haiku-latest
6+
- openai:chat:gemini-flash-latest
7+
- openai:chat:deepseek-chat
8+
9+
prompts:
10+
- file://promptfoo/abstract-chunk/prompt.ts
11+
12+
tests:
13+
# English technical content
14+
- vars:
15+
text: "React is a JavaScript library for building user interfaces. It was developed by Facebook and is now maintained by Facebook and the community. React makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes. Declarative views make your code more predictable and easier to debug."
16+
assert:
17+
- type: llm-rubric
18+
provider: openai:gpt-5-mini
19+
value: "The summary should be 1-2 sentences in English, capturing the main topic about React being a JavaScript library for UIs"
20+
- type: contains-any
21+
value: ["React", "JavaScript", "library", "UI", "user interface"]
22+
- type: javascript
23+
value: "output.split(/[.!?]/).filter(s => s.trim()).length <= 2" # At most 2 sentences
24+
25+
# Chinese content
26+
- vars:
27+
text: "深度学习是机器学习的一个分支,它使用多层神经网络来学习数据的表示。近年来,深度学习在图像识别、自然语言处理、语音识别等领域取得了突破性进展。卷积神经网络(CNN)在计算机视觉任务中表现优异,而循环神经网络(RNN)和Transformer架构在序列建模任务中非常有效。"
28+
assert:
29+
- type: llm-rubric
30+
provider: openai:gpt-5-mini
31+
value: "The summary should be 1-2 sentences in Chinese, summarizing deep learning and its applications"
32+
- type: contains-any
33+
value: ["深度学习", "神经网络", "机器学习"]
34+
- type: not-contains
35+
value: "摘要" # Should not contain meta labels
36+
- type: javascript
37+
value: "output.split(/[。!?]/).filter(s => s.trim()).length <= 2" # At most 2 sentences
38+
39+
# Japanese content
40+
- vars:
41+
text: "人工知能(AI)は、コンピュータシステムが人間の知能を模倣する技術です。AIは、学習、推論、問題解決などの認知機能を実行できます。現代のAIシステムは、大量のデータから学習し、パターンを認識して予測を行います。"
42+
assert:
43+
- type: llm-rubric
44+
provider: openai:gpt-5-mini
45+
value: "The summary should be 1-2 sentences in Japanese about artificial intelligence"
46+
- type: contains-any
47+
value: ["人工知能", "AI", "コンピュータ"]
48+
- type: javascript
49+
value: "output.split(/[。!?]/).filter(s => s.trim()).length <= 2"
50+
51+
# Spanish content
52+
- vars:
53+
text: "El cambio climático es uno de los mayores desafíos que enfrenta la humanidad en el siglo XXI. Las temperaturas globales están aumentando debido a las emisiones de gases de efecto invernadero producidas por actividades humanas como la quema de combustibles fósiles, la deforestación y la agricultura industrial. Los efectos incluyen el derretimiento de los glaciares, el aumento del nivel del mar y eventos climáticos extremos más frecuentes."
54+
assert:
55+
- type: llm-rubric
56+
provider: openai:gpt-5-mini
57+
value: "The summary should be 1-2 sentences in Spanish about climate change"
58+
- type: contains-any
59+
value: ["cambio climático", "temperatura", "clima"]
60+
- type: javascript
61+
value: "output.split(/[.!?]/).filter(s => s.trim()).length <= 2"
62+
63+
# Short technical content (English)
64+
- vars:
65+
text: "TypeScript is a strongly typed programming language that builds on JavaScript. It adds static type definitions to JavaScript, making code more robust and maintainable."
66+
assert:
67+
- type: llm-rubric
68+
provider: openai:gpt-5-mini
69+
value: "The summary should be 1-2 sentences in English about TypeScript"
70+
- type: contains-any
71+
value: ["TypeScript", "JavaScript", "type"]
72+
- type: javascript
73+
value: "output.split(/[.!?]/).filter(s => s.trim()).length <= 2"
74+
75+
# Mixed technical terms in Chinese
76+
- vars:
77+
text: "Docker 是一个开源的容器化平台,它允许开发者将应用程序及其依赖项打包到一个可移植的容器中。通过使用 Docker,可以确保应用在任何环境中都能一致地运行。Docker 容器比传统虚拟机更轻量级,启动速度更快,资源占用更少。"
78+
assert:
79+
- type: llm-rubric
80+
provider: openai:gpt-5-mini
81+
value: "The summary should be 1-2 sentences in Chinese, keeping 'Docker' in English"
82+
- type: contains
83+
value: "Docker" # Technical term should be preserved
84+
- type: contains-any
85+
value: ["容器", "平台", "应用"]
86+
- type: javascript
87+
value: "output.split(/[。!?]/).filter(s => s.trim()).length <= 2"
88+
89+
# German content
90+
- vars:
91+
text: "Die Quantenphysik ist ein fundamentaler Zweig der Physik, der sich mit dem Verhalten von Materie und Energie auf atomarer und subatomarer Ebene befasst. Im Gegensatz zur klassischen Physik beschreibt die Quantenphysik Phänomene, bei denen Teilchen sowohl Wellen- als auch Teilcheneigenschaften aufweisen können."
92+
assert:
93+
- type: llm-rubric
94+
provider: openai:gpt-5-mini
95+
value: "The summary should be 1-2 sentences in German about quantum physics"
96+
- type: contains-any
97+
value: ["Quantenphysik", "Physik", "Materie"]
98+
- type: javascript
99+
value: "output.split(/[.!?]/).filter(s => s.trim()).length <= 2"
100+
101+
# Code snippet in content (English)
102+
- vars:
103+
text: "The useState hook in React allows you to add state to functional components. For example: const [count, setCount] = useState(0). This creates a state variable 'count' with initial value 0 and a setter function 'setCount' to update it."
104+
assert:
105+
- type: llm-rubric
106+
provider: openai:gpt-5-mini
107+
value: "The summary should be 1-2 sentences in English about useState hook, may preserve code syntax"
108+
- type: contains-any
109+
value: ["useState", "React", "state", "hook"]
110+
- type: javascript
111+
value: "output.split(/[.!?]/).filter(s => s.trim()).length <= 2"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// TypeScript prompt wrapper that uses actual chain implementation
2+
import { chainAbstractChunkText } from '@lobechat/prompts';
3+
4+
interface PromptVars {
5+
text: string;
6+
}
7+
8+
export default function generatePrompt({ vars }: { vars: PromptVars }) {
9+
const { text } = vars;
10+
11+
// Use the actual chain function from src
12+
const result = chainAbstractChunkText(text);
13+
14+
// Return messages array as expected by promptfoo
15+
return result.messages || [];
16+
}

packages/prompts/promptfooconfig.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ testPaths:
77
- promptfoo/language-detection/eval.yaml
88
- promptfoo/emoji-picker/eval.yaml
99
- promptfoo/knowledge-qa/eval.yaml
10+
- promptfoo/abstract-chunk/eval.yaml
1011

1112
# Output configuration
1213
outputPath: promptfoo-results.json
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`chainAbstractChunkText > should generate correct chat payload for chunk text 1`] = `
4+
{
5+
"messages": [
6+
{
7+
"content": "You are a summarization expert. Generate a concise summary from the provided text chunk.
8+
9+
Rules:
10+
- Output ONLY the summary text itself, nothing else
11+
- NO labels, prefixes, or meta-text (like "Summary:", "摘要:", etc.)
12+
- NO explanations, commentary, or additional context
13+
- MUST be 1-2 complete sentences maximum (count carefully!)
14+
- MUST use the SAME language as the input text
15+
- Preserve technical terms, proper nouns, and code identifiers exactly as they appear
16+
- Focus on capturing the main topic or key information
17+
- Keep it concise and direct
18+
19+
<examples>
20+
<input>React is a JavaScript library for building user interfaces...</input>
21+
<output>React is a JavaScript library developed by Facebook for building interactive user interfaces with declarative views.</output>
22+
23+
<input>The useState hook in React allows you to add state...</input>
24+
<output>The useState hook in React enables functional components to manage state using a state variable and setter function.</output>
25+
26+
<input>深度学习是机器学习的一个分支...</input>
27+
<output>深度学习是机器学习的一个分支,使用多层神经网络学习数据表示,在图像识别、自然语言处理等领域取得突破。</output>
28+
</examples>
29+
",
30+
"role": "system",
31+
},
32+
{
33+
"content": "This is a sample chunk of text that needs to be summarized.",
34+
"role": "user",
35+
},
36+
],
37+
}
38+
`;

packages/prompts/src/chains/__tests__/abstractChunk.test.ts

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,15 @@ describe('chainAbstractChunkText', () => {
88

99
const result = chainAbstractChunkText(testText);
1010

11-
expect(result).toEqual({
12-
messages: [
13-
{
14-
content:
15-
'你是一名擅长从 chunk 中提取摘要的助理,你需要将用户的会话总结为 1~2 句话的摘要,输出成 chunk 所使用的语种',
16-
role: 'system',
17-
},
18-
{
19-
content: `chunk: ${testText}`,
20-
role: 'user',
21-
},
22-
],
23-
});
24-
});
25-
26-
it('should handle empty text', () => {
27-
const result = chainAbstractChunkText('');
28-
29-
expect(result.messages).toHaveLength(2);
30-
expect(result.messages![1].content).toBe('chunk: ');
11+
expect(result).toMatchSnapshot();
3112
});
3213

3314
it('should handle text with special characters', () => {
3415
const testText = 'Text with special chars: @#$%^&*()';
3516

3617
const result = chainAbstractChunkText(testText);
3718

38-
expect(result.messages![1].content).toBe(`chunk: ${testText}`);
19+
expect(result.messages![1].content).toBe(testText);
3920
});
4021

4122
it('should always use system role for first message', () => {

packages/prompts/src/chains/abstractChunk.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,33 @@ export const chainAbstractChunkText = (text: string): Partial<ChatStreamPayload>
44
return {
55
messages: [
66
{
7-
content:
8-
'你是一名擅长从 chunk 中提取摘要的助理,你需要将用户的会话总结为 1~2 句话的摘要,输出成 chunk 所使用的语种',
7+
content: `You are a summarization expert. Generate a concise summary from the provided text chunk.
8+
9+
Rules:
10+
- Output ONLY the summary text itself, nothing else
11+
- NO labels, prefixes, or meta-text (like "Summary:", "摘要:", etc.)
12+
- NO explanations, commentary, or additional context
13+
- MUST be 1-2 complete sentences maximum (count carefully!)
14+
- MUST use the SAME language as the input text
15+
- Preserve technical terms, proper nouns, and code identifiers exactly as they appear
16+
- Focus on capturing the main topic or key information
17+
- Keep it concise and direct
18+
19+
<examples>
20+
<input>React is a JavaScript library for building user interfaces...</input>
21+
<output>React is a JavaScript library developed by Facebook for building interactive user interfaces with declarative views.</output>
22+
23+
<input>The useState hook in React allows you to add state...</input>
24+
<output>The useState hook in React enables functional components to manage state using a state variable and setter function.</output>
25+
26+
<input>深度学习是机器学习的一个分支...</input>
27+
<output>深度学习是机器学习的一个分支,使用多层神经网络学习数据表示,在图像识别、自然语言处理等领域取得突破。</output>
28+
</examples>
29+
`,
930
role: 'system',
1031
},
1132
{
12-
content: `chunk: ${text}`,
33+
content: text,
1334
role: 'user',
1435
},
1536
],

src/app/[variants]/(main)/_layout/Desktop/SideBar/BottomActions.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { ActionIcon, ActionIconProps } from '@lobehub/ui';
2-
import { Github } from 'lucide-react';
2+
import { FlaskConical, Github } from 'lucide-react';
33
import Link from 'next/link';
44
import { memo } from 'react';
5+
import { useTranslation } from 'react-i18next';
56
import { Flexbox } from 'react-layout-kit';
67

78
import { GITHUB } from '@/const/url';
@@ -14,7 +15,8 @@ const ICON_SIZE: ActionIconProps['size'] = {
1415
};
1516

1617
const BottomActions = memo(() => {
17-
// const { t } = useTranslation('common');
18+
const { t } = useTranslation('common');
19+
1820
const { hideGitHub } = useServerConfigStore(featureFlagsSelectors);
1921

2022
return (
@@ -29,14 +31,14 @@ const BottomActions = memo(() => {
2931
/>
3032
</Link>
3133
)}
32-
{/*<Link aria-label={t('labs')} href={'/labs'}>*/}
33-
{/* <ActionIcon*/}
34-
{/* icon={FlaskConical}*/}
35-
{/* size={ICON_SIZE}*/}
36-
{/* title={t('labs')}*/}
37-
{/* tooltipProps={{ placement: 'right' }}*/}
38-
{/* />*/}
39-
{/*</Link>*/}
34+
<Link aria-label={t('labs')} href={'/labs'}>
35+
<ActionIcon
36+
icon={FlaskConical}
37+
size={ICON_SIZE}
38+
title={t('labs')}
39+
tooltipProps={{ placement: 'right' }}
40+
/>
41+
</Link>
4042
</Flexbox>
4143
);
4244
});

src/app/[variants]/(main)/labs/components/LabCard.tsx

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Flexbox } from 'react-layout-kit';
77

88
interface LabCardProps {
99
checked: boolean;
10+
cover?: string;
1011
desc: string;
1112
loading: boolean;
1213
meta?: string;
@@ -35,14 +36,24 @@ const useStyles = createStyles(({ css, token }) => ({
3536
`,
3637
row: css`
3738
display: grid;
38-
grid-template-columns: 240px 1fr 80px;
39+
grid-template-columns: 250px 1fr 80px;
3940
gap: 16px;
4041
align-items: center;
4142
`,
4243
thumb: css`
43-
height: 128px;
44+
overflow: hidden;
45+
46+
width: 250px;
47+
height: 150px;
4448
border-radius: ${token.borderRadiusLG}px;
49+
4550
background: linear-gradient(135deg, ${token.colorFillTertiary}, ${token.colorFillQuaternary});
51+
52+
img {
53+
width: 100%;
54+
height: 100%;
55+
object-fit: cover;
56+
}
4657
`,
4758
title: css`
4859
font-size: 16px;
@@ -55,14 +66,14 @@ const useStyles = createStyles(({ css, token }) => ({
5566
}));
5667

5768
const LabCard = memo<PropsWithChildren<LabCardProps>>(
58-
({ title, desc, checked, onChange, meta, loading }) => {
69+
({ title, desc, checked, onChange, meta, loading, cover }) => {
5970
const { styles } = useStyles();
6071

6172
return (
6273
<div className={styles.wrap}>
6374
<div className={styles.card}>
6475
<div className={styles.row}>
65-
<div className={styles.thumb} />
76+
<div className={styles.thumb}>{cover && <img alt={title} src={cover} />}</div>
6677
<Flexbox gap={6}>
6778
<div className={styles.title}>{title}</div>
6879
<div className={styles.desc}>{desc}</div>

0 commit comments

Comments
 (0)