@@ -29,6 +29,8 @@ struct ChatModelEditView: View {
2929 OllamaForm ( store: store)
3030 case . claude:
3131 ClaudeForm ( store: store)
32+ case . gitHubCopilot:
33+ GitHubCopilotForm ( store: store)
3234 }
3335 }
3436 . padding ( )
@@ -86,31 +88,44 @@ struct ChatModelEditView: View {
8688 var body : some View {
8789 WithPerceptionTracking {
8890 Picker (
89- selection: $store. format,
91+ selection: Binding (
92+ get: { . init( store. format) } ,
93+ set: { store. send ( . selectModelFormat( $0) ) }
94+ ) ,
9095 content: {
9196 ForEach (
92- ChatModel . Format . allCases,
93- id: \. rawValue
97+ ChatModelEdit . ModelFormat . allCases,
98+ id: \. self
9499 ) { format in
95100 switch format {
96101 case . openAI:
97- Text ( " OpenAI " ) . tag ( format )
102+ Text ( " OpenAI " )
98103 case . azureOpenAI:
99- Text ( " Azure OpenAI " ) . tag ( format )
104+ Text ( " Azure OpenAI " )
100105 case . openAICompatible:
101- Text ( " OpenAI Compatible " ) . tag ( format )
106+ Text ( " OpenAI Compatible " )
102107 case . googleAI:
103- Text ( " Google Generative AI " ) . tag ( format )
108+ Text ( " Google AI " )
104109 case . ollama:
105- Text ( " Ollama " ) . tag ( format )
110+ Text ( " Ollama " )
106111 case . claude:
107- Text ( " Claude " ) . tag ( format)
112+ Text ( " Claude " )
113+ case . gitHubCopilot:
114+ Text ( " GitHub Copilot " )
115+ case . deepSeekOpenAICompatible:
116+ Text ( " DeepSeek (OpenAI Compatible) " )
117+ case . openRouterOpenAICompatible:
118+ Text ( " OpenRouter (OpenAI Compatible) " )
119+ case . grokOpenAICompatible:
120+ Text ( " Grok (OpenAI Compatible) " )
121+ case . mistralOpenAICompatible:
122+ Text ( " Mistral (OpenAI Compatible) " )
108123 }
109124 }
110125 } ,
111126 label: { Text ( " Format " ) }
112127 )
113- . pickerStyle ( . segmented )
128+ . pickerStyle ( . menu )
114129 }
115130 }
116131 }
@@ -243,7 +258,7 @@ struct ChatModelEditView: View {
243258
244259 MaxTokensTextField ( store: store)
245260 SupportsFunctionCallingToggle ( store: store)
246-
261+
247262 TextField ( text: $store. openAIOrganizationID, prompt: Text ( " Optional " ) ) {
248263 Text ( " Organization ID " )
249264 }
@@ -321,11 +336,15 @@ struct ChatModelEditView: View {
321336 Toggle ( isOn: $store. enforceMessageOrder) {
322337 Text ( " Enforce message order to be user/assistant alternated " )
323338 }
324-
339+
325340 Toggle ( isOn: $store. openAICompatibleSupportsMultipartMessageContent) {
326341 Text ( " Support multi-part message content " )
327342 }
328343
344+ Toggle ( isOn: $store. requiresBeginWithUserMessage) {
345+ Text ( " Requires the first message to be from the user " )
346+ }
347+
329348 Button ( " Custom Headers " ) {
330349 isEditingCustomHeader. toggle ( )
331350 }
@@ -375,12 +394,16 @@ struct ChatModelEditView: View {
375394
376395 struct OllamaForm : View {
377396 @Perception . Bindable var store : StoreOf < ChatModelEdit >
397+ @State var isEditingCustomHeader = false
398+
378399 var body : some View {
379400 WithPerceptionTracking {
380401 BaseURLTextField ( store: store, prompt: Text ( " http://127.0.0.1:11434 " ) ) {
381402 Text ( " /api/chat " )
382403 }
383404
405+ ApiKeyNamePicker ( store: store)
406+
384407 TextField ( " Model Name " , text: $store. modelName)
385408
386409 MaxTokensTextField ( store: store)
@@ -389,12 +412,19 @@ struct ChatModelEditView: View {
389412 Text ( " Keep Alive " )
390413 }
391414
415+ Button ( " Custom Headers " ) {
416+ isEditingCustomHeader. toggle ( )
417+ }
418+
392419 VStack ( alignment: . leading, spacing: 8 ) {
393420 Text ( Image ( systemName: " exclamationmark.triangle.fill " ) ) + Text(
394421 " For more details, please visit [https://ollama.com](https://ollama.com). "
395422 )
396423 }
397424 . padding ( . vertical)
425+
426+ } . sheet ( isPresented: $isEditingCustomHeader) {
427+ CustomHeaderSettingsView ( headers: $store. customHeaders)
398428 }
399429 }
400430 }
@@ -442,6 +472,61 @@ struct ChatModelEditView: View {
442472 }
443473 }
444474 }
475+
476+ struct GitHubCopilotForm : View {
477+ @Perception . Bindable var store : StoreOf < ChatModelEdit >
478+ @State var isEditingCustomHeader = false
479+
480+ var body : some View {
481+ WithPerceptionTracking {
482+ TextField ( " Model Name " , text: $store. modelName)
483+ . overlay ( alignment: . trailing) {
484+ Picker (
485+ " " ,
486+ selection: $store. modelName,
487+ content: {
488+ if AvailableGitHubCopilotModel ( rawValue: store. modelName) == nil {
489+ Text ( " Custom Model " ) . tag ( store. modelName)
490+ }
491+ ForEach ( AvailableGitHubCopilotModel . allCases, id: \. self) { model in
492+ Text ( model. rawValue) . tag ( model. rawValue)
493+ }
494+ }
495+ )
496+ . frame ( width: 20 )
497+ }
498+
499+ MaxTokensTextField ( store: store)
500+ SupportsFunctionCallingToggle ( store: store)
501+
502+ Toggle ( isOn: $store. enforceMessageOrder) {
503+ Text ( " Enforce message order to be user/assistant alternated " )
504+ }
505+
506+ Toggle ( isOn: $store. openAICompatibleSupportsMultipartMessageContent) {
507+ Text ( " Support multi-part message content " )
508+ }
509+
510+ Button ( " Custom Headers " ) {
511+ isEditingCustomHeader. toggle ( )
512+ }
513+
514+ VStack ( alignment: . leading, spacing: 8 ) {
515+ Text ( Image ( systemName: " exclamationmark.triangle.fill " ) ) + Text(
516+ " Please login in the GitHub Copilot settings to use the model. "
517+ )
518+
519+ Text ( Image ( systemName: " exclamationmark.triangle.fill " ) ) + Text(
520+ " This will call the APIs directly, which may not be allowed by GitHub. But it's used in other popular apps like Zed. "
521+ )
522+ }
523+ . dynamicHeightTextInFormWorkaround ( )
524+ . padding ( . vertical)
525+ } . sheet ( isPresented: $isEditingCustomHeader) {
526+ CustomHeaderSettingsView ( headers: $store. customHeaders)
527+ }
528+ }
529+ }
445530}
446531
447532#Preview( " OpenAI " ) {
0 commit comments