Skip to content

Commit 2b75884

Browse files
committed
Merge branch 'main' into renovate/major-react-monorepo
2 parents 2142436 + e60ad34 commit 2b75884

File tree

10 files changed

+364
-13
lines changed

10 files changed

+364
-13
lines changed

.github/workflows/codeql-analysis.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ on:
1717
pull_request:
1818
# The branches below must be a subset of the branches above
1919
branches: [main]
20+
merge_group:
2021
schedule:
2122
- cron: '29 19 * * 6'
2223

@@ -42,7 +43,7 @@ jobs:
4243

4344
# Initializes the CodeQL tools for scanning.
4445
- name: Initialize CodeQL
45-
uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
46+
uses: github/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
4647
with:
4748
languages: ${{ matrix.language }}
4849
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -53,7 +54,7 @@ jobs:
5354
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
5455
# If this step fails, then you should remove it and run the build manually (see below)
5556
- name: Autobuild
56-
uses: github/codeql-action/autobuild@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
57+
uses: github/codeql-action/autobuild@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
5758

5859
# ℹ️ Command-line programs to run using the OS shell.
5960
# 📚 https://git.io/JvXDl
@@ -67,4 +68,4 @@ jobs:
6768
# make release
6869

6970
- name: Perform CodeQL Analysis
70-
uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
71+
uses: github/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3

.github/workflows/main.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ on:
1111
- 'main'
1212
- 'next'
1313
- '+([0-9])?(.{+([0-9]),x}).x'
14-
pull_request: {}
14+
pull_request:
15+
merge_group:
1516

1617
jobs:
1718
reuse-compliance:

.github/workflows/open-source-security.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ jobs:
1616
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
1717

1818
- name: artifactPrepareVersion
19-
uses: SAP/project-piper-action@143ff7e5a8cbcf9eb50eb83185031d00be6bc4e8 # v1.23.1
19+
uses: SAP/project-piper-action@e700a9bc1ba5625bf77eb671fb2195b7085d514a # v1.23.2
2020
with:
2121
step-name: artifactPrepareVersion
2222
flags: --versioningType cloud_noTag
2323

2424
- name: detectExecuteScan
25-
uses: SAP/project-piper-action@143ff7e5a8cbcf9eb50eb83185031d00be6bc4e8 # v1.23.1
25+
uses: SAP/project-piper-action@e700a9bc1ba5625bf77eb671fb2195b7085d514a # v1.23.2
2626
with:
2727
step-name: detectExecuteScan
2828
flags: '--token ${{ secrets.DETECT_TOKEN }}'

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
24.11.0
1+
24.11.1

.storybook/utils.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type * as CEM from '@ui5/webcomponents-tools/lib/cem/types-internal';
2-
import { useMemo, useRef, useState, useTransition } from 'react';
2+
import { useEffect, useMemo, useRef, useState, useTransition } from 'react';
33
// @ts-expect-error: storybook can handle this
44
import cemAi from './custom-element-manifests/ai.json';
55
// @ts-expect-error: storybook can handle this
@@ -91,13 +91,17 @@ type StartStreamOptions = {
9191
onComplete?: (fullText: string) => void;
9292
onProcessingComplete?: () => void;
9393
};
94-
export function useFakeStream(typingDelay = 10, startingDelay = 1500) {
95-
const [value, setValue] = useState('');
94+
export function useFakeStream(initialValue = '', typingDelay = 10, startingDelay = 1500) {
95+
const [value, setValue] = useState(initialValue);
9696
const [transitionIsPending, startTransition] = useTransition(); // active character updates
9797
const [isProcessing, setIsProcessing] = useState(false); // starting delay
9898
const [isTyping, setIsTyping] = useState(false); // actively typing characters
9999
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
100100
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
101+
const isProcessingRef = useRef(isProcessing);
102+
const isTypingRef = useRef(isTyping);
103+
isProcessingRef.current = isProcessing;
104+
isTypingRef.current = isTyping;
101105

102106
const startStream = ({ text, onComplete, onProcessingComplete }: StartStreamOptions) => {
103107
// Stop previous stream and timeout
@@ -161,3 +165,24 @@ export function useFakeStream(typingDelay = 10, startingDelay = 1500) {
161165

162166
return { value, transitionIsPending, isProcessing, isTyping, setValue, startStream, stopStream };
163167
}
168+
169+
export function useStopStreamByESC(loading: boolean, stopStream: () => void, onStop?: () => void) {
170+
const loadingRef = useRef(loading);
171+
loadingRef.current = loading;
172+
173+
useEffect(() => {
174+
const handleKeyDown = (e: KeyboardEvent) => {
175+
if (e.key === 'Escape' && loadingRef.current) {
176+
stopStream();
177+
if (onStop) {
178+
onStop();
179+
}
180+
}
181+
};
182+
183+
window.addEventListener('keydown', handleKeyDown);
184+
return () => {
185+
window.removeEventListener('keydown', handleKeyDown);
186+
};
187+
}, [stopStream, onStop]);
188+
}

config/version-info.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,5 @@
5858
"2.13.1": "2.13.0",
5959
"2.14.0": "2.14.0",
6060
"2.15.0": "2.15.0",
61-
"2.16.0": "2.16.1"
61+
"2.16.1": "2.16.0"
6262
}

packages/ai/src/components/Input/Input.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ Input component implementing simple stream handling.
206206
const [placeholder, setPlaceholder] = useState(initialPlaceholder);
207207
const hasHistory = versionHistory.length > 0;
208208
const currentActionRef = useRef<string>('');
209-
const { value, isTyping, isProcessing, setValue, startStream, stopStream } = useFakeStream(50);
209+
const { value, isTyping, isProcessing, setValue, startStream, stopStream } = useFakeStream();
210210

211211
const handleVersionChange: InputPropTypes['onVersionChange'] = (e) => {
212212
setCurrentHistoryIndex((prev) => (e.detail.backwards ? prev - 1 : prev + 1));

packages/ai/src/components/Input/Input.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ export const WithFakeStream: Story = {
130130
const [placeholder, setPlaceholder] = useState(initialPlaceholder);
131131
const hasHistory = versionHistory.length > 0;
132132
const currentActionRef = useRef<string>('');
133-
const { value, isTyping, isProcessing, setValue, startStream, stopStream } = useFakeStream(50);
133+
const { value, isTyping, isProcessing, setValue, startStream, stopStream } = useFakeStream();
134134

135135
const handleVersionChange: InputPropTypes['onVersionChange'] = (e) => {
136136
setCurrentHistoryIndex((prev) => (e.detail.backwards ? prev - 1 : prev + 1));

packages/ai/src/components/TextArea/TextArea.mdx

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,221 @@ import * as ComponentStories from './TextArea.stories.tsx';
1616

1717
<ControlsWithNote of={ComponentStories.Default} />
1818

19+
## TextArea with Fake Stream
20+
21+
TextArea component implementing simple stream handling.
22+
23+
<Canvas of={ComponentStories.WithFakeStream} />
24+
25+
<details>
26+
27+
<summary>Show Static Code</summary>
28+
29+
```tsx
30+
import { TextArea, TextAreaPropTypes } from '@ui5/webcomponents-ai-react';
31+
import { Menu, MenuItem, MenuPropTypes } from '@ui5/webcomponents-react';
32+
import { useEffect, useRef, useState, useTransition } from 'react';
33+
34+
type StartStreamOptions = {
35+
text: string;
36+
onComplete?: (fullText: string) => void;
37+
onProcessingComplete?: () => void;
38+
};
39+
export function useFakeStream(initialValue = '', typingDelay = 10, startingDelay = 1500) {
40+
const [value, setValue] = useState(initialValue);
41+
const [transitionIsPending, startTransition] = useTransition(); // active character updates
42+
const [isProcessing, setIsProcessing] = useState(false); // starting delay
43+
const [isTyping, setIsTyping] = useState(false); // actively typing characters
44+
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
45+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
46+
const isProcessingRef = useRef(isProcessing);
47+
const isTypingRef = useRef(isTyping);
48+
isProcessingRef.current = isProcessing;
49+
isTypingRef.current = isTyping;
50+
51+
const startStream = ({ text, onComplete, onProcessingComplete }: StartStreamOptions) => {
52+
// Stop previous stream and timeout
53+
if (intervalRef.current) {
54+
clearInterval(intervalRef.current);
55+
intervalRef.current = null;
56+
}
57+
if (timeoutRef.current) {
58+
clearTimeout(timeoutRef.current);
59+
timeoutRef.current = null;
60+
}
61+
62+
setValue('');
63+
setIsProcessing(true);
64+
65+
timeoutRef.current = setTimeout(() => {
66+
setIsProcessing(false);
67+
68+
if (onProcessingComplete) {
69+
onProcessingComplete();
70+
}
71+
72+
setIsTyping(true);
73+
let index = 0;
74+
75+
intervalRef.current = setInterval(() => {
76+
if (index < text.length) {
77+
const nextChar = text[index];
78+
index++;
79+
80+
startTransition(() => {
81+
setValue((prev) => prev + nextChar);
82+
});
83+
} else {
84+
if (intervalRef.current) {
85+
clearInterval(intervalRef.current);
86+
intervalRef.current = null;
87+
}
88+
setIsTyping(false);
89+
90+
if (onComplete) {
91+
onComplete(text);
92+
}
93+
}
94+
}, typingDelay);
95+
}, startingDelay);
96+
};
97+
98+
const stopStream = () => {
99+
if (intervalRef.current) {
100+
clearInterval(intervalRef.current);
101+
intervalRef.current = null;
102+
}
103+
if (timeoutRef.current) {
104+
clearTimeout(timeoutRef.current);
105+
timeoutRef.current = null;
106+
}
107+
setIsProcessing(false);
108+
setIsTyping(false);
109+
};
110+
111+
return { value, transitionIsPending, isProcessing, isTyping, setValue, startStream, stopStream };
112+
}
113+
114+
export function useStopStreamByESC(loading: boolean, stopStream: () => void, onStop?: () => void) {
115+
const loadingRef = useRef(loading);
116+
loadingRef.current = loading;
117+
118+
useEffect(() => {
119+
const handleKeyDown = (e: KeyboardEvent) => {
120+
if (e.key === 'Escape' && loadingRef.current) {
121+
stopStream();
122+
if (onStop) {
123+
onStop();
124+
}
125+
}
126+
};
127+
128+
window.addEventListener('keydown', handleKeyDown);
129+
return () => {
130+
window.removeEventListener('keydown', handleKeyDown);
131+
};
132+
}, [stopStream, onStop]);
133+
}
134+
135+
const SAMPLE_TEXT =
136+
'Innovation managers operate with both creativity and business acumen, driving initiatives that cultivate an innovation-friendly culture, streamline the execution of new ideas, and ultimately unlock value for the organization and its customers.';
137+
138+
type VersionHistoryItem = {
139+
action: string;
140+
endAction: string;
141+
timestamp: string;
142+
value: string;
143+
promptDescription: string;
144+
};
145+
146+
function AITextArea(props) {
147+
const { value, isTyping, isProcessing, setValue, startStream, stopStream } = useFakeStream();
148+
const [versionHistory, setVersionHistory] = useState<VersionHistoryItem[]>([]);
149+
const [currentHistoryIndex, setCurrentHistoryIndex] = useState(-1);
150+
const [promptDescription, setPromptDescription] = useState('');
151+
const currentActionRef = useRef<string>('');
152+
const isLoading = isProcessing || isTyping;
153+
154+
const handleItemClick: MenuPropTypes['onItemClick'] = (e) => {
155+
const { action } = e.detail.item.dataset;
156+
if (isProcessing || !action) {
157+
return;
158+
}
159+
currentActionRef.current = action;
160+
setPromptDescription('Generating text...');
161+
startStream({
162+
text: SAMPLE_TEXT,
163+
onComplete: (fullText) => {
164+
setVersionHistory((prev) => [
165+
...prev,
166+
{
167+
action,
168+
endAction: 'completed',
169+
timestamp: new Date().toISOString(),
170+
value: fullText,
171+
promptDescription: 'Generated text',
172+
},
173+
]);
174+
setCurrentHistoryIndex((prev) => prev + 1);
175+
setValue('');
176+
setPromptDescription('');
177+
},
178+
});
179+
};
180+
181+
const handleStopGeneration: TextAreaPropTypes['onStopGeneration'] = () => {
182+
stopStream();
183+
handleStop();
184+
};
185+
186+
const handleStop = () => {
187+
setVersionHistory((prev) => [
188+
...prev,
189+
{
190+
action: currentActionRef.current,
191+
endAction: 'stopped',
192+
timestamp: new Date().toISOString(),
193+
value: value,
194+
promptDescription: 'Generated text (stopped)',
195+
},
196+
]);
197+
setCurrentHistoryIndex((prev) => prev + 1);
198+
setValue('');
199+
setPromptDescription('');
200+
};
201+
202+
const handleVersionChange: TextAreaPropTypes['onVersionChange'] = (e) => {
203+
setCurrentHistoryIndex((prev) => (e.detail.backwards ? prev - 1 : prev + 1));
204+
setValue('');
205+
};
206+
207+
const handleInput: TextAreaPropTypes['onInput'] = (e) => {
208+
setValue(e.target.value);
209+
};
210+
211+
useStopStreamByESC(isLoading, stopStream, handleStop);
212+
213+
return (
214+
<TextArea
215+
{...props}
216+
value={value || versionHistory[currentHistoryIndex]?.value || ''}
217+
currentVersion={currentHistoryIndex + 1}
218+
totalVersions={versionHistory.length}
219+
loading={isLoading}
220+
promptDescription={promptDescription || versionHistory[currentHistoryIndex]?.promptDescription || ''}
221+
onStopGeneration={handleStopGeneration}
222+
onVersionChange={handleVersionChange}
223+
onInput={handleInput}
224+
menu={
225+
<Menu onItemClick={handleItemClick}>
226+
<MenuItem text="Generate text" data-action="generate" />
227+
</Menu>
228+
}
229+
/>
230+
);
231+
}
232+
```
233+
234+
</details>
235+
19236
<Footer />

0 commit comments

Comments
 (0)