-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Replace custom input with UTI in the contextual sheet #9091
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
adddd0d
79adbe5
7677a92
1ea9097
6e28196
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,6 +40,20 @@ import org.json.JSONArray | |
| import org.json.JSONObject | ||
| import javax.inject.Inject | ||
|
|
||
| /** | ||
| * A prompt submitted from the unified input widget while the contextual sheet is in its initial | ||
| * (INPUT) state. Carries the widget's current model/reasoning/tool/attachment selections so the | ||
| * new chat starts with everything the user configured. | ||
| */ | ||
| data class NativeInputPrompt( | ||
| val prompt: String, | ||
| val modelId: String?, | ||
| val reasoningEffort: String?, | ||
| val selectedTool: String?, | ||
| val imagesJson: JSONArray?, | ||
| val filesJson: JSONArray?, | ||
| ) | ||
|
|
||
| interface ContextualNativeInputManager { | ||
| fun init( | ||
| tabId: String, | ||
|
|
@@ -51,6 +65,9 @@ interface ContextualNativeInputManager { | |
| onSearchSubmitted: (String) -> Unit, | ||
| onCameraCaptureRequested: (ValueCallback<Array<Uri>>) -> Unit = {}, | ||
| onFilePickerRequested: (ValueCallback<Array<Uri>>, List<String>) -> Unit = { _, _ -> }, | ||
| // Invoked when the widget submits a prompt while the sheet is in INPUT mode: starts a new chat. | ||
| // WEBVIEW-mode submissions keep going through the in-chat JS event path (see setupWidget). | ||
| onNewChatPromptSubmitted: (NativeInputPrompt) -> Unit = {}, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would |
||
| ) | ||
|
|
||
| fun onWebViewMode() | ||
|
|
@@ -90,13 +107,14 @@ class RealContextualNativeInputManager @Inject constructor( | |
| onSearchSubmitted: (String) -> Unit, | ||
| onCameraCaptureRequested: (ValueCallback<Array<Uri>>) -> Unit, | ||
| onFilePickerRequested: (ValueCallback<Array<Uri>>, List<String>) -> Unit, | ||
| onNewChatPromptSubmitted: (NativeInputPrompt) -> Unit, | ||
| ) { | ||
| this.card = card | ||
| this.jsMessaging = jsMessaging | ||
| this.widget = widget | ||
|
|
||
| applyCardShape(card) | ||
| setupWidget(tabId, widget, chatIdFlow, onSearchSubmitted, onCameraCaptureRequested, onFilePickerRequested) | ||
| setupWidget(tabId, widget, chatIdFlow, onSearchSubmitted, onCameraCaptureRequested, onFilePickerRequested, onNewChatPromptSubmitted) | ||
| observeNativeInputSetting(lifecycleOwner) | ||
| } | ||
|
|
||
|
|
@@ -125,20 +143,25 @@ class RealContextualNativeInputManager @Inject constructor( | |
|
|
||
| override fun onInputMode() { | ||
| lastMode = Mode.INPUT | ||
| card?.gone() | ||
| // INPUT mode is a new chat: restore the picker | ||
| if (isNativeInputEnabled) modelPickerEnabled.value = true | ||
| if (isNativeInputEnabled) { | ||
| // The unified input widget is the composer for the initial sheet. | ||
| card?.show() | ||
| // INPUT mode is a new chat: restore the picker so the user can pick a model before starting. | ||
| modelPickerEnabled.value = true | ||
| } else { | ||
| // Flag off: the legacy EditText composer is shown instead, so keep the widget card hidden. | ||
| card?.gone() | ||
| } | ||
| } | ||
|
|
||
| private fun applyCardShape(card: MaterialCardView) { | ||
| // The card floats within the input area (margins on all sides), matching the Duck.ai omnibar | ||
| // card, so all four corners are rounded rather than the docked top-only shape. | ||
| val radius = card.resources.getDimension( | ||
| com.duckduckgo.mobile.android.R.dimen.extraLargeShapeCornerRadius, | ||
| ) | ||
| card.shapeAppearanceModel = card.shapeAppearanceModel.toBuilder() | ||
| .setTopLeftCornerSize(radius) | ||
| .setTopRightCornerSize(radius) | ||
| .setBottomLeftCornerSize(0f) | ||
| .setBottomRightCornerSize(0f) | ||
| .setAllCornerSizes(radius) | ||
| .build() | ||
| } | ||
|
|
||
|
|
@@ -149,6 +172,7 @@ class RealContextualNativeInputManager @Inject constructor( | |
| onSearchSubmitted: (String) -> Unit, | ||
| onCameraCaptureRequested: (ValueCallback<Array<Uri>>) -> Unit, | ||
| onFilePickerRequested: (ValueCallback<Array<Uri>>, List<String>) -> Unit, | ||
| onNewChatPromptSubmitted: (NativeInputPrompt) -> Unit, | ||
| ) { | ||
| widget.configureContextual(tabId) | ||
| widget.bindChatIdSource(chatIdFlow) | ||
|
|
@@ -167,8 +191,19 @@ class RealContextualNativeInputManager @Inject constructor( | |
| onChatSubmitted = { prompt -> | ||
| val imagesJson = widget.getImageAttachmentsJson() | ||
| val filesJson = widget.getFileAttachmentsJson() | ||
| val modelId = widget.getSelectedModelId() | ||
| val reasoningEffort = widget.getResolvedReasoningEffort() | ||
| val selectedTool = widget.getSelectedTool() | ||
| widget.clearAttachments() | ||
| sendPrompt(prompt, widget.getSelectedModelId(), widget.getResolvedReasoningEffort(), widget.getSelectedTool(), imagesJson, filesJson) | ||
| if (lastMode == Mode.INPUT) { | ||
| // Initial sheet: start a new chat via the ViewModel so the sheet switches to WEBVIEW. | ||
| onNewChatPromptSubmitted( | ||
| NativeInputPrompt(prompt, modelId, reasoningEffort, selectedTool, imagesJson, filesJson), | ||
| ) | ||
| } else { | ||
| // Chat already in progress: submit into the running chat via the JS event. | ||
| sendPrompt(prompt, modelId, reasoningEffort, selectedTool, imagesJson, filesJson) | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unset mode uses in-chat submitMedium Severity New chat routing requires Reviewed by Cursor Bugbot for commit 80b3243. Configure here. |
||
| widget.clearSelectedTool() | ||
| widget.text = "" | ||
| }, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,6 +64,7 @@ import com.duckduckgo.common.ui.DuckDuckGoFragment | |
| import com.duckduckgo.common.ui.menu.PopupMenu | ||
| import com.duckduckgo.common.ui.view.PopupMenuItemView | ||
| import com.duckduckgo.common.ui.view.dialog.ActionBottomSheetDialog | ||
| import com.duckduckgo.common.ui.view.getColorFromAttr | ||
| import com.duckduckgo.common.ui.view.gone | ||
| import com.duckduckgo.common.ui.view.makeSnackbarWithNoBottomInset | ||
| import com.duckduckgo.common.ui.view.show | ||
|
|
@@ -85,7 +86,7 @@ import com.duckduckgo.duckchat.api.DuckChatHistoryNoParams | |
| import com.duckduckgo.duckchat.api.viewmodel.DuckChatSharedViewModel | ||
| import com.duckduckgo.duckchat.impl.DuckChatInternal | ||
| import com.duckduckgo.duckchat.impl.R | ||
| import com.duckduckgo.duckchat.impl.databinding.FragmentContextualDuckAiBinding | ||
| import com.duckduckgo.duckchat.impl.databinding.FragmentContextualDuckAiNativeBinding | ||
| import com.duckduckgo.duckchat.impl.feature.AIChatDownloadFeature | ||
| import com.duckduckgo.duckchat.impl.helper.DuckChatJSHelper | ||
| import com.duckduckgo.duckchat.impl.helper.Mode | ||
|
|
@@ -120,7 +121,7 @@ import javax.inject.Named | |
|
|
||
| @InjectWith(FragmentScope::class) | ||
| class DuckChatContextualFragment : | ||
| DuckDuckGoFragment(R.layout.fragment_contextual_duck_ai), | ||
| DuckDuckGoFragment(R.layout.fragment_contextual_duck_ai_native), | ||
| DownloadConfirmationDialogListener { | ||
|
|
||
| @Inject | ||
|
|
@@ -209,7 +210,7 @@ class DuckChatContextualFragment : | |
| private var pendingFileDownload: FileDownloader.PendingFileDownload? = null | ||
| private val downloadMessagesJob = ConflatedJob() | ||
|
|
||
| private val binding: FragmentContextualDuckAiBinding by viewBinding() | ||
| private val binding: FragmentContextualDuckAiNativeBinding by viewBinding() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Keyboard resize ignores UTI focusMedium Severity The IME visibility listener only calls Reviewed by Cursor Bugbot for commit 172be5e. Configure here. |
||
| private var pendingUploadTask: ValueCallback<Array<Uri>>? = null | ||
|
|
||
| private val root: ViewGroup by lazy { binding.root } | ||
|
|
@@ -472,6 +473,16 @@ class DuckChatContextualFragment : | |
| onFilePickerRequested = { callback, mimeTypes -> | ||
| launchNativeFilePicker(callback, mimeTypes) | ||
| }, | ||
| onNewChatPromptSubmitted = { submitted -> | ||
| viewModel.onPromptSent( | ||
| prompt = submitted.prompt, | ||
| modelId = submitted.modelId, | ||
| reasoningEffort = submitted.reasoningEffort, | ||
| selectedTool = submitted.selectedTool, | ||
| imagesJson = submitted.imagesJson, | ||
| filesJson = submitted.filesJson, | ||
| ) | ||
| }, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Quick action ignores UTI textMedium Severity With Reviewed by Cursor Bugbot for commit 172be5e. Configure here. |
||
| ) | ||
| observeViewModel() | ||
|
|
||
|
|
@@ -704,8 +715,11 @@ class DuckChatContextualFragment : | |
|
|
||
| when (viewState.sheetMode) { | ||
| DuckChatContextualViewModel.SheetMode.INPUT -> { | ||
| binding.contextualModeNativeContent.show() | ||
| binding.contextualWebviewContainer.gone() | ||
| binding.contextualModePrompts.show() | ||
| binding.contextualInputContainer.setBackgroundColor( | ||
| requireContext().getColorFromAttr(com.duckduckgo.mobile.android.R.attr.daxColorSurface), | ||
| ) | ||
| contextualNativeInputManager.onInputMode() | ||
|
|
||
| if (viewState.showChatsIcon) { | ||
|
|
@@ -715,28 +729,40 @@ class DuckChatContextualFragment : | |
| } | ||
| binding.contextualFire.gone() | ||
|
|
||
| renderPageContext(viewState.contextTitle, viewState.contextUrl, viewState.tabId) | ||
|
|
||
| if (viewState.quickActionState == DuckChatContextualViewModel.QuickActionState.ASK_ABOUT_PAGE) { | ||
| binding.duckAiContextualLayout.gone() | ||
| binding.duckAiAttachContextLayout.gone() | ||
| } else if (viewState.showContext) { | ||
| binding.duckAiContextualLayout.show() | ||
| binding.duckAiAttachContextLayout.gone() | ||
| if (viewState.nativeChatInputEnabled) { | ||
| // The unified input widget is the composer; ContextualNativeInputManager.onInputMode() | ||
| // shows its card. Hide the legacy EditText composer and its context chip/placeholder. | ||
| binding.contextualModeNativeContent.gone() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Manual page attach UI hiddenMedium Severity With native chat input enabled, the entire legacy composer block—including Reviewed by Cursor Bugbot for commit 172be5e. Configure here. |
||
| } else { | ||
| binding.duckAiContextualLayout.gone() | ||
| binding.duckAiAttachContextLayout.show() | ||
| } | ||
| if (viewState.prompt.isNotEmpty()) { | ||
| binding.inputField.setText(viewState.prompt) | ||
| binding.inputField.setSelection(viewState.prompt.length) | ||
| } else { | ||
| clearInputField() | ||
| binding.contextualModeNativeContent.show() | ||
|
|
||
| renderPageContext(viewState.contextTitle, viewState.contextUrl, viewState.tabId) | ||
|
|
||
| if (viewState.quickActionState == DuckChatContextualViewModel.QuickActionState.ASK_ABOUT_PAGE) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I think a |
||
| binding.duckAiContextualLayout.gone() | ||
| binding.duckAiAttachContextLayout.gone() | ||
| } else if (viewState.showContext) { | ||
| binding.duckAiContextualLayout.show() | ||
| binding.duckAiAttachContextLayout.gone() | ||
| } else { | ||
| binding.duckAiContextualLayout.gone() | ||
| binding.duckAiAttachContextLayout.show() | ||
| } | ||
| if (viewState.prompt.isNotEmpty()) { | ||
| binding.inputField.setText(viewState.prompt) | ||
| binding.inputField.setSelection(viewState.prompt.length) | ||
| } else { | ||
| clearInputField() | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prompt state not synced to widgetMedium Severity When Reviewed by Cursor Bugbot for commit 172be5e. Configure here. |
||
| } | ||
| } | ||
|
|
||
| DuckChatContextualViewModel.SheetMode.WEBVIEW -> { | ||
| binding.contextualModeNativeContent.gone() | ||
| binding.contextualModePrompts.gone() | ||
| binding.contextualInputContainer.setBackgroundColor( | ||
| requireContext().getColorFromAttr(com.duckduckgo.mobile.android.R.attr.daxColorDuckAiBackground), | ||
| ) | ||
| binding.contextualWebviewContainer.show() | ||
| binding.contextualNewChat.show() | ||
| if (viewState.isFireButtonEnabled) binding.contextualFire.show() else binding.contextualFire.gone() | ||
|
|
||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this something we should extract in order to share between both managers (and then refactor)?