image attachment added cont.

main_production_copilot
Sikander Saleem 3 weeks ago
parent 994acccc9d
commit 61d20d20fd

@ -200,7 +200,7 @@ class ApiManager {
/// POST FormData request that returns StreamedResponse for progress tracking /// POST FormData request that returns StreamedResponse for progress tracking
/// This method is specifically for form-data requests (multipart/form-data) /// This method is specifically for form-data requests (multipart/form-data)
Future<http.StreamedResponse> postFormDataStreaming(String url, {Map<String, String>? headers, required Map<String, String> body}) async { Future<http.StreamedResponse> postFormDataStreaming(String url, {Map<String, String>? headers, required Map<String, String> body, File? file}) async {
// Create headers for form data // Create headers for form data
Map<String, String> formHeaders = { Map<String, String> formHeaders = {
'X-Timezone-Offset': DateTime.now().timeZoneOffset.toString().split(".").first, 'X-Timezone-Offset': DateTime.now().timeZoneOffset.toString().split(".").first,
@ -221,6 +221,9 @@ class ApiManager {
var request = http.MultipartRequest('POST', url0); var request = http.MultipartRequest('POST', url0);
request.fields.addAll(body); request.fields.addAll(body);
request.headers.addAll(headers); request.headers.addAll(headers);
if (file != null) {
request.files.add(await MultipartFile.fromPath('ImageFile', file.path));
}
http.StreamedResponse streamedResponse = await _httpClient.send(request); http.StreamedResponse streamedResponse = await _httpClient.send(request);
@ -468,7 +471,7 @@ class ApiManager {
return response; return response;
} }
Future<http.Response> multiPart(String url, {Map<String, String>? headers, required Map<String, String> body, bool showToast=true, required List<Future<MultipartFile>> files}) async { Future<http.Response> multiPart(String url, {Map<String, String>? headers, required Map<String, String> body, bool showToast = true, required List<Future<MultipartFile>> files}) async {
Map<String, String> headers0 = _headers; Map<String, String> headers0 = _headers;
headers0["Content-Type"] = 'multipart/form-data'; headers0["Content-Type"] = 'multipart/form-data';
@ -502,7 +505,7 @@ class ApiManager {
final responseBody = jsonDecode(response.body); final responseBody = jsonDecode(response.body);
if (responseBody is Map<String, dynamic>) { if (responseBody is Map<String, dynamic>) {
final message = responseBody["message"]; // Reuse parsed body final message = responseBody["message"]; // Reuse parsed body
if (message != null && message.toString().isNotEmpty&&showToast) { if (message != null && message.toString().isNotEmpty && showToast) {
Fluttertoast.showToast(msg: message, toastLength: Toast.LENGTH_LONG); Fluttertoast.showToast(msg: message, toastLength: Toast.LENGTH_LONG);
} }
} }

@ -33,6 +33,7 @@ class CopilotProvider extends ChangeNotifier {
HubConnection? _pttHub; HubConnection? _pttHub;
PttSignalRHttpClient? _pttHttpClient; PttSignalRHttpClient? _pttHttpClient;
// Serializes connect/disconnect so they never overlap when the user navigates // Serializes connect/disconnect so they never overlap when the user navigates
// in and out of the Push-to-Talk screen quickly. // in and out of the Push-to-Talk screen quickly.
Future<void> _pttOpQueue = Future.value(); Future<void> _pttOpQueue = Future.value();
@ -147,7 +148,7 @@ class CopilotProvider extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
Future<void> sendText(String query, String language) async { Future<void> sendText(String query, String language, File? image) async {
if (isStreaming || query.trim().isEmpty) return; if (isStreaming || query.trim().isEmpty) return;
sessionId ??= _generateSessionId(); sessionId ??= _generateSessionId();
@ -163,11 +164,13 @@ class CopilotProvider extends ChangeNotifier {
URLs.copilotQueryStream, URLs.copilotQueryStream,
body: { body: {
"sessionId": sessionId!, "sessionId": sessionId!,
"isImage": "${image != null}",
"isAudio": "false", "isAudio": "false",
"langId": "$language", "langId": "$language",
'isFromMobile': 'true', 'isFromMobile': 'true',
"query": query, "query": query,
}, },
file: image,
); );
await _consumeStream(streamedResponse, assistantMsg, null); await _consumeStream(streamedResponse, assistantMsg, null);
} catch (e) { } catch (e) {

@ -13,6 +13,7 @@ import 'package:test_sa/extensions/widget_extensions.dart';
import 'package:test_sa/modules/copilot/screens/copilot_history_page.dart'; import 'package:test_sa/modules/copilot/screens/copilot_history_page.dart';
import 'package:test_sa/modules/copilot/screens/copilot_live_talk_page.dart'; import 'package:test_sa/modules/copilot/screens/copilot_live_talk_page.dart';
import 'package:test_sa/modules/copilot/screens/copilot_push_to_talk_page.dart'; import 'package:test_sa/modules/copilot/screens/copilot_push_to_talk_page.dart';
import 'package:test_sa/modules/cx_module/chat/helper/chat_file_picker.dart';
import 'package:test_sa/new_views/app_style/app_color.dart'; import 'package:test_sa/new_views/app_style/app_color.dart';
import 'package:test_sa/new_views/common_widgets/default_app_bar.dart'; import 'package:test_sa/new_views/common_widgets/default_app_bar.dart';
import 'package:test_sa/thinking_orbs/src/thinking_orb.dart'; import 'package:test_sa/thinking_orbs/src/thinking_orb.dart';
@ -39,6 +40,8 @@ class _CopilotChatPageState extends State<CopilotChatPage> {
_InputState _inputState = _InputState.idle; _InputState _inputState = _InputState.idle;
String? _recordedFilePath; String? _recordedFilePath;
File? image;
CopilotProvider? _copilotProvider; CopilotProvider? _copilotProvider;
@override @override
@ -73,7 +76,9 @@ class _CopilotChatPageState extends State<CopilotChatPage> {
final text = _textController.text.trim(); final text = _textController.text.trim();
if (text.isEmpty) return; if (text.isEmpty) return;
_textController.clear(); _textController.clear();
context.read<CopilotProvider>().sendText(text, context.settingProvider.language!); context.read<CopilotProvider>().sendText(text, context.settingProvider.language!, image);
image = null;
setState(() {});
_scrollToBottom(); _scrollToBottom();
} }
@ -228,7 +233,7 @@ class _CopilotChatPageState extends State<CopilotChatPage> {
child: ConstrainedBox( child: ConstrainedBox(
constraints: const BoxConstraints(minHeight: 56), constraints: const BoxConstraints(minHeight: 56),
child: Container( child: Container(
height: 50, // height: 50,
margin: const EdgeInsets.all(16), margin: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppColor.background(context), color: AppColor.background(context),
@ -240,15 +245,64 @@ class _CopilotChatPageState extends State<CopilotChatPage> {
reserveGlowSpace: false, reserveGlowSpace: false,
borderRadius: 35, borderRadius: 35,
strength: 0.7, strength: 0.7,
child: Row( child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (image != null)
SizedBox(
width: 80.toScreenWidth,
height: 80.toScreenWidth,
child: Stack(
// alignment: AlignmentDirectional.topEnd,
children: [
Positioned.fill(
child: Container(
margin: EdgeInsetsDirectional.only(top: 8.toScreenHeight, end: 8.toScreenWidth),
decoration: BoxDecoration(
color: AppColor.neutral90,
border: Border.all(width: 1, color: AppColor.border2Color(context)),
image: DecorationImage(
image: FileImage(image!),
fit: BoxFit.cover,
),
borderRadius: BorderRadius.circular(12),
),
child: MaterialButton(
padding: EdgeInsets.zero,
onPressed: () async {
context.fullScreenImageViewer(Image.file(image!));
},
),
)),
if (image != null)
Positioned(
top: 4,
right: 4,
child: IconButton(
padding: EdgeInsets.zero,
icon: const Icon(Icons.clear),
iconSize: 18,
style: IconButton.styleFrom(elevation: 2, tapTargetSize: MaterialTapTargetSize.shrinkWrap, backgroundColor: AppColor.red30, foregroundColor: Colors.white),
visualDensity: VisualDensity.compact,
constraints: const BoxConstraints(),
onPressed: () {
setState(() {
image = null;
});
},
)),
],
),
),
Row(
children: [ children: [
if (_inputState == _InputState.idle) ...[ if (_inputState == _InputState.idle) ...[
TextFormField( TextFormField(
controller: _textController, controller: _textController,
cursorColor: AppColor.loadingColor(context), cursorColor: AppColor.loadingColor(context),
style: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 14, color: AppColor.textStyleColor(context)), style: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 12, color: AppColor.textStyleColor(context)),
minLines: 1, minLines: 1,
maxLines: 3, maxLines: 2,
textInputAction: TextInputAction.none, textInputAction: TextInputAction.none,
keyboardType: TextInputType.multiline, keyboardType: TextInputType.multiline,
decoration: InputDecoration( decoration: InputDecoration(
@ -264,16 +318,46 @@ class _CopilotChatPageState extends State<CopilotChatPage> {
hintStyle: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 12, color: AppColor.lightTextColor(context)), hintStyle: TextStyle(fontWeight: FontWeight.w500, fontSize: context.isTablet() ? 16 : 12, color: AppColor.lightTextColor(context)),
), ),
).expanded, ).expanded,
// if (image == null)
// IconButton(
// onPressed: () async {
// FocusScope.of(context).unfocus();
// File? file = (await showModalBottomSheet(
// context: context,
// backgroundColor: AppColor.bottomSheetColor(context),
// shape: const RoundedRectangleBorder(
// borderRadius: BorderRadius.vertical(
// top: Radius.circular(20),
// ),
// ),
// clipBehavior: Clip.antiAliasWithSaveLayer,
// builder: (BuildContext context) => ChatFilePicker())) as File?;
// if (file != null) {
// image = file;
// setState(() {});
// }
// },
// style: const ButtonStyle(
// tapTargetSize: MaterialTapTargetSize.shrinkWrap, // or .padded
// ),
// icon: "chat_attachment".toSvgAsset(width: 24, height: 24, color: AppColor.icon2Color(context)),
// constraints: const BoxConstraints(),
// ),
Consumer<CopilotProvider>( Consumer<CopilotProvider>(
builder: (_, provider, __) => IconButton( builder: (_, provider, __) => IconButton(
onPressed: provider.isTranscribing ? null : _startRecording, onPressed: provider.isTranscribing ? null : _startRecording,
style: const ButtonStyle(tapTargetSize: MaterialTapTargetSize.shrinkWrap), style: const ButtonStyle(tapTargetSize: MaterialTapTargetSize.shrinkWrap),
icon: provider.isTranscribing icon: provider.isTranscribing
? const SizedBox( ? ThinkingOrb(
height: 20, size: 20,
width: 20, state: ThinkingOrbState.working,
child: CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2), theme: context.isDark ? ThinkingOrbTheme.dark : ThinkingOrbTheme.light,
) )
// SizedBox(
// height: 20,
// width: 20,
// child: CircularProgressIndicator(color: AppColor.primary10, strokeWidth: 2),
// )
: "chat_mic".toSvgAsset(width: 24, height: 24, color: AppColor.icon2Color(context)), : "chat_mic".toSvgAsset(width: 24, height: 24, color: AppColor.icon2Color(context)),
constraints: const BoxConstraints(), constraints: const BoxConstraints(),
), ),
@ -361,6 +445,8 @@ class _CopilotChatPageState extends State<CopilotChatPage> {
8.width, 8.width,
], ],
), ),
],
),
), ),
), ),
), ),

Loading…
Cancel
Save