Skip to content

Commit 33b547d

Browse files
committed
implement chat gpt webview
1 parent 3e530de commit 33b547d

1 file changed

Lines changed: 174 additions & 0 deletions

File tree

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package gr.sqlbrowserfx.nodes;
2+
3+
import org.fxmisc.wellbehaved.event.EventPattern;
4+
import org.fxmisc.wellbehaved.event.InputMap;
5+
import org.fxmisc.wellbehaved.event.Nodes;
6+
7+
import gr.sqlbrowserfx.utils.JavaFXUtils;
8+
import javafx.scene.control.ContextMenu;
9+
import javafx.scene.control.MenuItem;
10+
import javafx.scene.input.Clipboard;
11+
import javafx.scene.input.ClipboardContent;
12+
import javafx.scene.input.KeyCode;
13+
import javafx.scene.input.KeyCombination;
14+
import javafx.scene.input.MouseButton;
15+
import javafx.scene.input.MouseEvent;
16+
import javafx.scene.layout.BorderPane;
17+
import javafx.scene.web.WebEngine;
18+
import javafx.scene.web.WebView;
19+
20+
public class ChatGptWebView extends BorderPane implements ContextMenuOwner, InputMapOwner {
21+
22+
private final String darkModeCssJs = """
23+
(function() {
24+
const style = document.createElement('style');
25+
style.textContent = `
26+
:root {
27+
color-scheme: dark;
28+
background-color: #222222 !important;
29+
color: #e0e0e0 !important;
30+
}
31+
html, body *:not(pre):not(pre *) {
32+
background-color: #222222 !important;
33+
color: #e0e0e0 !important;
34+
margin: 0 !important;
35+
box-shadow: none !important;
36+
text-shadow: none !important;
37+
}
38+
pre * {
39+
background-color: #2d2d2d !important;
40+
color: #e0e0e0 !important;
41+
border-radius: 5px !important;
42+
}
43+
button {
44+
border-radius: 5px !important;
45+
border-width: 2px !important;
46+
border-color: #2d2d2d !important;
47+
}
48+
button:hover {
49+
border-color: #196de3 !important;
50+
}
51+
form {
52+
border-radius: 5px !important;
53+
border-width: 2px !important;
54+
border-color: #2d2d2d !important;
55+
}
56+
`;
57+
document.documentElement.appendChild(style);
58+
})();
59+
""";
60+
private final String selectedTextJs = """
61+
(function() {
62+
let text = "";
63+
64+
if (window.getSelection) {
65+
text = window.getSelection().toString();
66+
} else if (document.selection && document.selection.type != "Control") {
67+
text = document.selection.createRange().text;
68+
}
69+
70+
return text;
71+
})();
72+
""";
73+
74+
private final String clickAskButtonJs = """
75+
(function() {
76+
const btn = document.getElementById('composer-submit-button');
77+
btn.click();
78+
})();
79+
""";
80+
81+
private final WebView webView = new WebView();;
82+
83+
public ChatGptWebView() {
84+
var webEngine = webView.getEngine();
85+
// Load ChatGPT and then apply dark mode styling
86+
webEngine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> {
87+
if (newState == javafx.concurrent.Worker.State.SUCCEEDED) {
88+
webEngine.executeScript(this.darkModeCssJs);
89+
}
90+
});
91+
webEngine.load("https://chatgpt.com/");
92+
93+
this.setContextMenu();
94+
this.setInputMap();
95+
this.setCenter(webView);
96+
}
97+
98+
public WebEngine getEngine() {
99+
return this.webView.getEngine();
100+
}
101+
102+
private void setContextMenu() {
103+
var contextMenu = this.createContextMenu();
104+
webView.setContextMenuEnabled(false); // disable native WebView menu
105+
webView.addEventFilter(MouseEvent.MOUSE_PRESSED, mouseEvent -> {
106+
if (mouseEvent.getButton() == MouseButton.SECONDARY) { // right-click
107+
contextMenu.show(webView, mouseEvent.getScreenX(), mouseEvent.getScreenY());
108+
mouseEvent.consume(); // prevent default
109+
} else {
110+
contextMenu.hide();
111+
}
112+
});
113+
}
114+
115+
@Override
116+
public void setInputMap() {
117+
var copy = InputMap.consume(
118+
EventPattern.keyPressed(KeyCode.C, KeyCombination.CONTROL_DOWN),
119+
action -> this.copySelectedTextToClipoboard());
120+
121+
var ask = InputMap.consume(
122+
EventPattern.keyPressed(KeyCode.ENTER, KeyCombination.CONTROL_DOWN),
123+
action -> this.clickAskButton());
124+
125+
Nodes.addInputMap(this.webView, ask);
126+
Nodes.addInputMap(this.webView, copy);
127+
Nodes.addInputMap(this, copy);
128+
}
129+
130+
private String pasteAndAskJs(String question) {
131+
var safeText = question.replace("\\", "\\\\").replace("\"", "\\\"")
132+
.replace("\n", "\\n").replace("\r", "");
133+
134+
return """
135+
(function() {
136+
const el = document.getElementById('prompt-textarea');
137+
el.innerText = "%s";
138+
setTimeout(() => {
139+
const btn = document.getElementById('composer-submit-button');
140+
btn.click();
141+
}, 500);
142+
143+
})();
144+
""".formatted(safeText);
145+
}
146+
147+
public void askChatGpt(String question) {
148+
this.getEngine().executeScript(this.pasteAndAskJs(question));
149+
}
150+
151+
private void clickAskButton() {
152+
this.getEngine().executeScript(this.clickAskButtonJs);
153+
}
154+
155+
private void copySelectedTextToClipoboard() {
156+
var text = (String) webView.getEngine().executeScript(this.selectedTextJs);
157+
var clipboard = Clipboard.getSystemClipboard();
158+
var content = new ClipboardContent();
159+
content.putString(text);
160+
clipboard.setContent(content);
161+
}
162+
163+
@Override
164+
public ContextMenu createContextMenu() {
165+
var copySelectedHtmlText = new MenuItem("Copy Selected Text", JavaFXUtils.createIcon("/icons/copy.png"));
166+
copySelectedHtmlText.setOnAction(copyAction -> this.copySelectedTextToClipoboard());
167+
168+
var refresh = new MenuItem("Refresh", JavaFXUtils.createIcon("/icons/refresh.png"));
169+
refresh.setOnAction(copyAction -> this.getEngine().reload());
170+
171+
return new ContextMenu(copySelectedHtmlText, refresh);
172+
}
173+
174+
}

0 commit comments

Comments
 (0)