Skip to content

Commit 9eb3d46

Browse files
authored
feat: support image editing by nova canvas and cost display
1 parent 9f77176 commit 9eb3d46

25 files changed

+910
-194
lines changed

README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ macOS platforms.
2626
- Understand images, documents and videos with Nova Lite and Pro
2727
- Record 30-second videos directly on Android and iOS for Nova analysis
2828
- Upload large videos (1080p/4K) beyond 8MB with auto compression
29+
- Support using natural language to make Nova Canvas generate images, remove backgrounds, replace backgrounds, and
30+
create images in similar styles.
2931

3032
## Architecture
3133

@@ -92,8 +94,8 @@ can find the **API URL** which looks like: `https://xxx.xxx.awsapprunner.com` or
9294
### Step 3: Download the app and setup with API URL and API Key
9395

9496
1. Download the App
95-
- Android App click to [Download](https://github.com/aws-samples/swift-chat/releases/download/1.6.0/SwiftChat.apk)
96-
- macOS App click to [Download](https://github.com/aws-samples/swift-chat/releases/download/1.6.0/SwiftChat.dmg)
97+
- Android App click to [Download](https://github.com/aws-samples/swift-chat/releases/download/1.7.0/SwiftChat.apk)
98+
- macOS App click to [Download](https://github.com/aws-samples/swift-chat/releases/download/1.7.0/SwiftChat.dmg)
9799
- iOS (Currently we do not provide the iOS version, you can build it locally with Xcode)
98100

99101
2. Launch the App, open the drawer menu, and tap **Settings**.

README_CN.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ macOS 等多个平台。
2525
- 支持 Nova Lite 和 Pro 对图片、文档及视频内容的理解
2626
- 支持直接在安卓和 iOS 设备上录制最长 30 秒的视频供 Nova 分析
2727
- 支持自动压缩上传超过8MB的高清视频(1080p/4K)
28+
- 支持通过自然语言让 Nova Canvas 生成图片,去除背景,替换背景,以及生成类似风格的图片。
2829

2930
## 架构
3031

@@ -83,8 +84,8 @@ macOS 等多个平台。
8384
### 第3步: 下载应用并设置 API URL 和 API Key
8485

8586
1. 下载应用
86-
- Android 应用点击 [下载](https://github.com/aws-samples/swift-chat/releases/download/1.6.0/SwiftChat.apk)
87-
- macOS 应用点击 [下载](https://github.com/aws-samples/swift-chat/releases/download/1.6.0/SwiftChat.dmg)
87+
- Android 应用点击 [下载](https://github.com/aws-samples/swift-chat/releases/download/1.7.0/SwiftChat.apk)
88+
- macOS 应用点击 [下载](https://github.com/aws-samples/swift-chat/releases/download/1.7.0/SwiftChat.dmg)
8889
- iOS (目前不提供 iOS 版本,您可以使用 Xcode 在本地构建)
8990

9091
2. 启动应用,点击左侧菜单按钮,并点击底部的 **Settings**

react-native/android/app/build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ android {
7979
applicationId "com.aws.swiftchat"
8080
minSdkVersion rootProject.ext.minSdkVersion
8181
targetSdkVersion rootProject.ext.targetSdkVersion
82-
versionCode 11
83-
versionName "1.6.0"
82+
versionCode 12
83+
versionName "1.7.0"
8484
ndk {
8585
//noinspection ChromeOsAbiSupport
8686
abiFilters 'arm64-v8a'

react-native/ios/Podfile.lock

+2-2
Original file line numberDiff line numberDiff line change
@@ -982,7 +982,7 @@ PODS:
982982
- Yoga
983983
- react-native-get-random-values (1.11.0):
984984
- React-Core
985-
- react-native-image-picker (7.2.2):
985+
- react-native-image-picker (7.2.3):
986986
- DoubleConversion
987987
- glog
988988
- hermes-engine
@@ -1591,7 +1591,7 @@ SPEC CHECKSUMS:
15911591
react-native-compressor: 2ae9013718fb351264fcfcdf232eccbbf3d280a2
15921592
react-native-document-picker: c4f197741c327270453aa9840932098e0064fd52
15931593
react-native-get-random-values: 21325b2244dfa6b58878f51f9aa42821e7ba3d06
1594-
react-native-image-picker: dd85e2530d366acf77745830b053294afed66339
1594+
react-native-image-picker: fb0c2b3adc3eff6caa3cd6a507a34b9dcc9238dd
15951595
react-native-mmkv: 8c9a677e64a1ac89b0c6cf240feea528318b3074
15961596
react-native-safe-area-context: b7daa1a8df36095a032dff095a1ea8963cb48371
15971597
React-nativeconfig: b0073a590774e8b35192fead188a36d1dca23dec

react-native/ios/SwiftChat.xcodeproj/project.pbxproj

+4-4
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@
485485
CODE_SIGN_ENTITLEMENTS = SwiftChat/SwiftChat.entitlements;
486486
CODE_SIGN_IDENTITY = "Apple Development";
487487
CODE_SIGN_STYLE = Automatic;
488-
CURRENT_PROJECT_VERSION = 11;
488+
CURRENT_PROJECT_VERSION = 12;
489489
DEVELOPMENT_TEAM = BUA6W9H7T3;
490490
ENABLE_BITCODE = NO;
491491
"ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES;
@@ -497,7 +497,7 @@
497497
"$(inherited)",
498498
"@executable_path/Frameworks",
499499
);
500-
MARKETING_VERSION = 1.6.0;
500+
MARKETING_VERSION = 1.7.0;
501501
OTHER_LDFLAGS = (
502502
"$(inherited)",
503503
"-ObjC",
@@ -526,7 +526,7 @@
526526
CODE_SIGN_ENTITLEMENTS = SwiftChat/SwiftChat.entitlements;
527527
CODE_SIGN_IDENTITY = "Apple Development";
528528
CODE_SIGN_STYLE = Automatic;
529-
CURRENT_PROJECT_VERSION = 11;
529+
CURRENT_PROJECT_VERSION = 12;
530530
DEVELOPMENT_TEAM = BUA6W9H7T3;
531531
"ENABLE_HARDENED_RUNTIME[sdk=macosx*]" = YES;
532532
INFOPLIST_FILE = SwiftChat/Info.plist;
@@ -537,7 +537,7 @@
537537
"$(inherited)",
538538
"@executable_path/Frameworks",
539539
);
540-
MARKETING_VERSION = 1.6.0;
540+
MARKETING_VERSION = 1.7.0;
541541
OTHER_LDFLAGS = (
542542
"$(inherited)",
543543
"-ObjC",

react-native/package-lock.json

+15-15
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

react-native/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "swift-chat",
33
"description": "Sample Bedrock Cross-platform App - SwiftChat",
4-
"version": "1.6.0",
4+
"version": "1.7.0",
55
"private": true,
66
"scripts": {
77
"android": "react-native run-android",
@@ -30,7 +30,7 @@
3030
"react-native-get-random-values": "^1.11.0",
3131
"react-native-gifted-chat": "^2.4.0",
3232
"react-native-haptic-feedback": "^2.2.0",
33-
"react-native-image-picker": "^7.2.2",
33+
"react-native-image-picker": "^7.2.3",
3434
"react-native-image-viewing": "^0.2.2",
3535
"react-native-marked": "^6.0.4",
3636
"react-native-mmkv": "^2.12.2",

react-native/src/api/bedrock-api.ts

+26-7
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
import { saveImageToLocal } from '../chat/util/FileUtils.ts';
1717
import {
1818
BedrockMessage,
19+
ImageContent,
20+
ImageInfo,
1921
TextContent,
2022
} from '../chat/util/BedrockMessageConvertor.ts';
2123

@@ -59,7 +61,7 @@ export const invokeBedrockWithCallBack = async (
5961
const url = getApiPrefix() + '/converse';
6062
let intervalId: ReturnType<typeof setInterval>;
6163
let completeMessage = '';
62-
const timeoutId = setTimeout(() => controller.abort(), 20000);
64+
const timeoutId = setTimeout(() => controller.abort(), 60000);
6365
fetch(url!, options)
6466
.then(response => {
6567
return response.body;
@@ -111,7 +113,7 @@ export const invokeBedrockWithCallBack = async (
111113
if (errorMsg.endsWith('AbortError: Aborted')) {
112114
errorMsg = 'Timed out';
113115
}
114-
if (errorMsg.indexOf('Unable to resolve host')) {
116+
if (errorMsg.indexOf('http') >= 0) {
115117
errorMsg = 'Unable to resolve host';
116118
}
117119
const errorInfo = 'Request error: ' + errorMsg;
@@ -122,16 +124,31 @@ export const invokeBedrockWithCallBack = async (
122124
} else {
123125
const prompt = (messages[messages.length - 1].content[0] as TextContent)
124126
.text;
125-
const imageRes = await genImage(prompt, controller);
127+
let image: ImageInfo | undefined;
128+
if (messages[messages.length - 1].content[1]) {
129+
image = (messages[messages.length - 1].content[1] as ImageContent).image;
130+
}
131+
132+
const imageRes = await genImage(prompt, controller, image);
126133
if (imageRes.image.length > 0) {
127134
const localFilePath = await saveImageToLocal(imageRes.image);
135+
const imageSize = getImageSize().split('x')[0].trim();
128136
const usage: Usage = {
129137
modelName: getImageModel().modelName,
130138
inputTokens: 0,
131139
outputTokens: 0,
132140
totalTokens: 0,
133-
imageCount: 1,
141+
smallImageCount: 0,
142+
imageCount: 0,
143+
largeImageCount: 0,
134144
};
145+
if (imageSize === '512') {
146+
usage.smallImageCount = 1;
147+
} else if (imageSize === '1024') {
148+
usage.imageCount = 1;
149+
} else if (imageSize === '2048') {
150+
usage.largeImageCount = 1;
151+
}
135152
if (localFilePath) {
136153
callback(`![](${localFilePath})`, true, false, usage);
137154
}
@@ -143,7 +160,7 @@ export const invokeBedrockWithCallBack = async (
143160
imageRes.error = 'Request timed out';
144161
}
145162
}
146-
if (imageRes.error.indexOf('Unable to resolve host')) {
163+
if (imageRes.error.indexOf('http') >= 0) {
147164
imageRes.error = 'Request error: Unable to resolve host';
148165
}
149166
callback(imageRes.error, true, true);
@@ -210,7 +227,8 @@ export const requestUpgradeInfo = async (
210227

211228
export const genImage = async (
212229
prompt: string,
213-
controller: AbortController
230+
controller: AbortController,
231+
image?: ImageInfo
214232
): Promise<ImageRes> => {
215233
if (!isConfigured()) {
216234
return {
@@ -224,6 +242,7 @@ export const genImage = async (
224242
const height = imageSize[1].trim();
225243
const bodyObject = {
226244
prompt: prompt,
245+
refImages: image ? [image] : undefined,
227246
modelId: getImageModel().modelId,
228247
region: getRegion(),
229248
width: width,
@@ -242,7 +261,7 @@ export const genImage = async (
242261
};
243262

244263
try {
245-
const timeoutMs = parseInt(width, 10) >= 1024 ? 90000 : 60000;
264+
const timeoutMs = parseInt(width, 10) >= 1024 ? 120000 : 90000;
246265
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
247266
const response = await fetch(url, options);
248267
if (!response.ok) {

react-native/src/chat/ChatScreen.tsx

+6-4
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,14 @@ const createBotMessage = (mode: string) => {
7575
const imagePlaceholder = '![](bedrock://imgProgress)';
7676
const textPlaceholder = '...';
7777
type ChatScreenRouteProp = RouteProp<RouteParamList, 'Bedrock'>;
78+
let currentMode = ChatMode.Text;
7879

7980
function ChatScreen(): React.JSX.Element {
8081
const navigation = useNavigation();
8182
const route = useRoute<ChatScreenRouteProp>();
8283
const initialSessionId = route.params?.sessionId;
8384
const tapIndex = route.params?.tapIndex;
84-
const mode = route.params?.mode ?? ChatMode.Text;
85+
const mode = route.params?.mode ?? currentMode;
8586
const modeRef = useRef(mode);
8687

8788
const [messages, setMessages] = useState<IMessage[]>([]);
@@ -130,6 +131,7 @@ function ChatScreen(): React.JSX.Element {
130131

131132
// header text and right button click
132133
React.useLayoutEffect(() => {
134+
currentMode = mode;
133135
const headerOptions: HeaderOptions = {
134136
// eslint-disable-next-line react/no-unstable-nested-components
135137
headerTitle: () => (
@@ -499,10 +501,10 @@ function ChatScreen(): React.JSX.Element {
499501
if (isUpdate) {
500502
setSelectedFiles(files);
501503
} else {
502-
console.log('handleNewFileSelected');
503504
handleNewFileSelected(files);
504505
}
505506
}}
507+
chatMode={modeRef.current}
506508
/>
507509
)
508510
}
@@ -528,9 +530,9 @@ function ChatScreen(): React.JSX.Element {
528530
if (
529531
isMac &&
530532
inputTexRef.current.length > 0 &&
533+
text[text.length - 1] === '\n' &&
534+
text[text.length - 2] !== ' ' &&
531535
text.length - inputTexRef.current.length === 1 &&
532-
!text.endsWith(' \n') &&
533-
text.endsWith('\n') &&
534536
chatStatusRef.current !== ChatStatus.Running
535537
) {
536538
setTimeout(() => {

0 commit comments

Comments
 (0)