Skip to content

Commit cfa3ece

Browse files
committed
Implement full support for the GitHub Copilot
1 parent d0ad281 commit cfa3ece

29 files changed

Lines changed: 230 additions & 46 deletions

File tree

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ Install the [open-source](https://github.com/robertpiosik/CodeWebChat/blob/dev/p
6666
- DeepSeek
6767
- Doubao
6868
- Gemini
69+
- GitHub Copilot
6970
- Grok
7071
- Kimi
7172
- Mistral

‎packages/browser/src/content-scripts/send-prompt-content-script/chatbots/ai-studio.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,6 @@ export const ai_studio: Chatbot = {
427427
edit_format,
428428
footer,
429429
get_chat_turn: (f) => f.closest('ms-chat-turn'),
430-
get_code_blocks: (t) => t.querySelectorAll('ms-code-block code'),
431430
perform_copy: (f) => {
432431
const chat_turn_container = f.closest('.chat-turn-container')
433432
if (!chat_turn_container) {

‎packages/browser/src/content-scripts/send-prompt-content-script/chatbots/chatgpt.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ export const chatgpt: Chatbot = {
120120
edit_format,
121121
footer,
122122
get_chat_turn: (f) => f.closest('.agent-turn'),
123-
get_code_blocks: (t) => t.querySelectorAll('code'),
124123
perform_copy: (f) => {
125124
const copy_button = f.querySelector(
126125
'button[data-testid="copy-turn-action-button"]'

‎packages/browser/src/content-scripts/send-prompt-content-script/chatbots/claude.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,6 @@ export const claude: Chatbot = {
161161
edit_format,
162162
footer,
163163
get_chat_turn: (f) => f.closest('div[data-is-streaming="false"]'),
164-
get_code_blocks: (t) => t.querySelectorAll('code'),
165164
perform_copy: (f) => {
166165
const copy_button = f.querySelector(
167166
'button[data-testid="action-bar-copy"]'

‎packages/browser/src/content-scripts/send-prompt-content-script/chatbots/copilot.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,6 @@ export const copilot: Chatbot = {
131131
edit_format,
132132
footer,
133133
get_chat_turn: (f) => f.closest('div[data-content="ai-message"]'),
134-
get_code_blocks: (t) => t.querySelectorAll('code'),
135134
perform_copy: (f) => {
136135
const copy_button = f.querySelector(
137136
'button[data-testid="copy-message-button"]'

‎packages/browser/src/content-scripts/send-prompt-content-script/chatbots/deepseek.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ export const deepseek: Chatbot = {
9999
f.parentElement?.parentElement?.querySelector(
100100
'.ds-markdown'
101101
) as HTMLElement,
102-
get_code_blocks: (t) => t.querySelectorAll('pre'),
103102
perform_copy: (f) => {
104103
const copy_button = f.querySelector(
105104
'div[role="button"]'

‎packages/browser/src/content-scripts/send-prompt-content-script/chatbots/doubao.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ export const doubao: Chatbot = {
120120
edit_format,
121121
footer,
122122
get_chat_turn: (f) => f.closest('div[data-testid="receive_message"]'),
123-
get_code_blocks: (t) => t.querySelectorAll('code'),
124123
perform_copy: (f) => {
125124
const copy_button = f.querySelector(
126125
'button[data-testid="message_action_copy"]'

‎packages/browser/src/content-scripts/send-prompt-content-script/chatbots/gemini.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@ export const gemini: Chatbot = {
140140
edit_format,
141141
footer,
142142
get_chat_turn: (f) => f.closest('response-container'),
143-
get_code_blocks: (t) => t.querySelectorAll('code'),
144143
perform_copy: (f) => {
145144
const copy_button = f.querySelector(
146145
'copy-button > button'
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { Chatbot } from '../types/chatbot'
2+
import { CHATBOTS } from '@shared/constants/chatbots'
3+
import {
4+
add_apply_response_button,
5+
observe_for_responses
6+
} from '../utils/add-apply-response-button'
7+
import {
8+
InitializationError,
9+
report_initialization_error
10+
} from '../utils/report-initialization-error'
11+
12+
export const github_copilot: Chatbot = {
13+
wait_until_ready: async () => {
14+
await new Promise((resolve) => {
15+
const check_for_element = () => {
16+
if (document.querySelector('form button[aria-haspopup="true"]')) {
17+
resolve(null)
18+
} else {
19+
setTimeout(check_for_element, 100)
20+
}
21+
}
22+
check_for_element()
23+
})
24+
},
25+
set_model: async (model?: string) => {
26+
if (!model) return
27+
28+
const model_selector_button = document.querySelector(
29+
'form button[aria-haspopup="true"]'
30+
) as HTMLButtonElement
31+
if (!model_selector_button) {
32+
report_initialization_error({
33+
function_name: 'set_model',
34+
log_message: 'Model selector button not found',
35+
alert_message: InitializationError.UNABLE_TO_SET_MODEL
36+
})
37+
return
38+
}
39+
40+
const model_label_to_find = (CHATBOTS['GitHub Copilot'].models as any)[
41+
model
42+
]?.label
43+
if (!model_label_to_find) return
44+
45+
if (model_selector_button.textContent?.includes(model_label_to_find)) {
46+
return
47+
}
48+
49+
model_selector_button.click()
50+
await new Promise((r) => requestAnimationFrame(r))
51+
52+
const options_container = document.querySelector(
53+
'div[data-variant="anchored"][data-width-medium]'
54+
)
55+
if (!options_container) {
56+
report_initialization_error({
57+
function_name: 'set_model',
58+
log_message: 'Model options container not found',
59+
alert_message: InitializationError.UNABLE_TO_SET_MODEL
60+
})
61+
// click again to close
62+
model_selector_button.click()
63+
return
64+
}
65+
66+
const options = options_container.querySelectorAll(
67+
'li[role="menuitemradio"]'
68+
)
69+
70+
let found = false
71+
for (const option of Array.from(options)) {
72+
if (option.textContent?.includes(model_label_to_find)) {
73+
;(option as HTMLElement).click()
74+
found = true
75+
break
76+
}
77+
}
78+
79+
if (!found) {
80+
report_initialization_error({
81+
function_name: 'set_model',
82+
log_message: `Model option "${model_label_to_find}" not found`,
83+
alert_message: InitializationError.UNABLE_TO_SET_MODEL
84+
})
85+
// click again to close dropdown
86+
model_selector_button.click()
87+
}
88+
89+
await new Promise((r) => requestAnimationFrame(r))
90+
},
91+
enter_message_and_send: async (params) => {
92+
const input_element = document.querySelector(
93+
'textarea'
94+
) as HTMLTextAreaElement
95+
if (!input_element) {
96+
report_initialization_error({
97+
function_name: 'enter_message_and_send',
98+
log_message: 'Message input textarea not found for GitHub Copilot',
99+
alert_message: InitializationError.UNABLE_TO_SEND_MESSAGE
100+
})
101+
return
102+
}
103+
input_element.value = params.message
104+
input_element.dispatchEvent(new Event('input', { bubbles: true }))
105+
input_element.dispatchEvent(new Event('change', { bubbles: true }))
106+
await new Promise((r) => requestAnimationFrame(r))
107+
108+
if (params.without_submission) return
109+
110+
const enter_event = new KeyboardEvent('keydown', {
111+
key: 'Enter',
112+
code: 'Enter',
113+
keyCode: 13,
114+
which: 13,
115+
bubbles: true,
116+
cancelable: true
117+
})
118+
input_element.dispatchEvent(enter_event)
119+
},
120+
inject_apply_response_button: (
121+
client_id: number,
122+
raw_instructions?: string,
123+
edit_format?: string
124+
) => {
125+
const add_buttons = (footer: Element) => {
126+
add_apply_response_button({
127+
client_id,
128+
raw_instructions,
129+
edit_format,
130+
footer,
131+
get_chat_turn: (f) => f.closest('div.message-container') as HTMLElement,
132+
perform_copy: (f) => {
133+
const copy_button = f.querySelector(
134+
'button:nth-child(5)'
135+
) as HTMLElement
136+
if (!copy_button) {
137+
report_initialization_error({
138+
function_name: 'github_copilot.perform_copy',
139+
log_message: 'Copy button not found',
140+
alert_message: InitializationError.UNABLE_TO_COPY_RESPONSE
141+
})
142+
return
143+
}
144+
copy_button.click()
145+
},
146+
insert_button: (f, b) => f.insertBefore(b, f.children[7])
147+
})
148+
}
149+
150+
observe_for_responses({
151+
chatbot_name: 'GitHub Copilot',
152+
is_generating: () =>
153+
!!document.querySelector(
154+
'path[d="M5.75 4h4.5c.966 0 1.75.784 1.75 1.75v4.5A1.75 1.75 0 0 1 10.25 12h-4.5A1.75 1.75 0 0 1 4 10.25v-4.5C4 4.784 4.784 4 5.75 4Z"]'
155+
),
156+
footer_selector: 'div[data-testid="nonshared-toolbar"]',
157+
add_buttons
158+
})
159+
}
160+
}

‎packages/browser/src/content-scripts/send-prompt-content-script/chatbots/grok.ts‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ export const grok: Chatbot = {
102102
edit_format,
103103
footer,
104104
get_chat_turn: (f) => f.closest('.items-start'),
105-
get_code_blocks: (t) => t.querySelectorAll('code'),
106105
perform_copy: (f) => {
107106
const copy_button = f.querySelector(
108107
'button:nth-child(4)'

0 commit comments

Comments
 (0)