├── XCAChatGPT
├── Assets.xcassets
│ ├── Contents.json
│ ├── palm.imageset
│ │ ├── palm.png
│ │ └── Contents.json
│ ├── openai.imageset
│ │ ├── openai.png
│ │ └── Contents.json
│ ├── profile.imageset
│ │ ├── unnamed.jpg
│ │ └── Contents.json
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── TextView.swift
├── TokenizerViewModel.swift
├── XCAChatGPTApp.swift
└── TokenizerView.swift
├── XCAChatGPTMac
├── Assets.xcassets
│ ├── Contents.json
│ ├── palm.imageset
│ │ ├── palm.png
│ │ └── Contents.json
│ ├── icon.imageset
│ │ ├── Shape.png
│ │ └── Contents.json
│ ├── openai.imageset
│ │ ├── openai.png
│ │ └── Contents.json
│ ├── profile.imageset
│ │ ├── unnamed.jpg
│ │ └── Contents.json
│ ├── AccentColor.colorset
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── XCAChatGPTMac.entitlements
└── XCAChatGPTMacApp.swift
├── XCAChatGPTTV
├── Assets.xcassets
│ ├── Contents.json
│ ├── palm.imageset
│ │ ├── palm.png
│ │ └── Contents.json
│ ├── openai.imageset
│ │ ├── openai.png
│ │ └── Contents.json
│ ├── profile.imageset
│ │ ├── unnamed.jpg
│ │ └── Contents.json
│ ├── App Icon & Top Shelf Image.brandassets
│ │ ├── App Icon.imagestack
│ │ │ ├── Back.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ ├── Front.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ ├── Middle.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── App Icon - App Store.imagestack
│ │ │ ├── Back.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ ├── Front.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ ├── Middle.imagestacklayer
│ │ │ │ ├── Contents.json
│ │ │ │ └── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── Top Shelf Image.imageset
│ │ │ └── Contents.json
│ │ ├── Top Shelf Image Wide.imageset
│ │ │ └── Contents.json
│ │ └── Contents.json
│ └── AccentColor.colorset
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── ScrollView.swift
└── XCAChatGPTTVApp.swift
├── XCAChatGPTWatch Watch App
├── Assets.xcassets
│ ├── Contents.json
│ ├── palm.imageset
│ │ ├── palm.png
│ │ └── Contents.json
│ ├── openai.imageset
│ │ ├── openai.png
│ │ └── Contents.json
│ ├── profile.imageset
│ │ ├── unnamed.jpg
│ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── AccentColor.colorset
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
└── XCAChatGPTWatchApp.swift
├── XCAChatGPT.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
└── project.pbxproj
├── Shared
├── ParserResult.swift
├── LLM Provider
│ ├── LLMClient.swift
│ ├── LLMConfig.swift
│ ├── PaLMChatAPI.swift
│ └── LLMProvider.swift
├── ResponseParsingTask.swift
├── MessageRow.swift
├── DotLoadingView.swift
├── ChatGPTAPIModels.swift
├── LLMConfigView.swift
├── CodeBlockView.swift
├── ContentView.swift
├── ChatGPTAPI.swift
├── MessageRowView.swift
├── ViewModel.swift
└── MarkdownAttributedStringParser.swift
├── LICENSE
├── README.MD
└── .gitignore
/XCAChatGPT/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/XCAChatGPTMac/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/XCAChatGPTWatch Watch App/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/XCAChatGPT/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/XCAChatGPTMac/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/XCAChatGPT/Assets.xcassets/palm.imageset/palm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alfianlosari/ChatGPTSwiftUI/HEAD/XCAChatGPT/Assets.xcassets/palm.imageset/palm.png
--------------------------------------------------------------------------------
/XCAChatGPTMac/Assets.xcassets/palm.imageset/palm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alfianlosari/ChatGPTSwiftUI/HEAD/XCAChatGPTMac/Assets.xcassets/palm.imageset/palm.png
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/palm.imageset/palm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alfianlosari/ChatGPTSwiftUI/HEAD/XCAChatGPTTV/Assets.xcassets/palm.imageset/palm.png
--------------------------------------------------------------------------------
/XCAChatGPTWatch Watch App/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/XCAChatGPT/Assets.xcassets/openai.imageset/openai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alfianlosari/ChatGPTSwiftUI/HEAD/XCAChatGPT/Assets.xcassets/openai.imageset/openai.png
--------------------------------------------------------------------------------
/XCAChatGPT/Assets.xcassets/profile.imageset/unnamed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alfianlosari/ChatGPTSwiftUI/HEAD/XCAChatGPT/Assets.xcassets/profile.imageset/unnamed.jpg
--------------------------------------------------------------------------------
/XCAChatGPTMac/Assets.xcassets/icon.imageset/Shape.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alfianlosari/ChatGPTSwiftUI/HEAD/XCAChatGPTMac/Assets.xcassets/icon.imageset/Shape.png
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/openai.imageset/openai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alfianlosari/ChatGPTSwiftUI/HEAD/XCAChatGPTTV/Assets.xcassets/openai.imageset/openai.png
--------------------------------------------------------------------------------
/XCAChatGPTMac/Assets.xcassets/openai.imageset/openai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alfianlosari/ChatGPTSwiftUI/HEAD/XCAChatGPTMac/Assets.xcassets/openai.imageset/openai.png
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/profile.imageset/unnamed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alfianlosari/ChatGPTSwiftUI/HEAD/XCAChatGPTTV/Assets.xcassets/profile.imageset/unnamed.jpg
--------------------------------------------------------------------------------
/XCAChatGPTMac/Assets.xcassets/profile.imageset/unnamed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alfianlosari/ChatGPTSwiftUI/HEAD/XCAChatGPTMac/Assets.xcassets/profile.imageset/unnamed.jpg
--------------------------------------------------------------------------------
/XCAChatGPTWatch Watch App/Assets.xcassets/palm.imageset/palm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alfianlosari/ChatGPTSwiftUI/HEAD/XCAChatGPTWatch Watch App/Assets.xcassets/palm.imageset/palm.png
--------------------------------------------------------------------------------
/XCAChatGPTWatch Watch App/Assets.xcassets/openai.imageset/openai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alfianlosari/ChatGPTSwiftUI/HEAD/XCAChatGPTWatch Watch App/Assets.xcassets/openai.imageset/openai.png
--------------------------------------------------------------------------------
/XCAChatGPTWatch Watch App/Assets.xcassets/profile.imageset/unnamed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alfianlosari/ChatGPTSwiftUI/HEAD/XCAChatGPTWatch Watch App/Assets.xcassets/profile.imageset/unnamed.jpg
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/XCAChatGPT.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/XCAChatGPT/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/XCAChatGPTMac/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/XCAChatGPT/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "ios",
6 | "size" : "1024x1024"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/XCAChatGPTWatch Watch App/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "platform" : "watchos",
6 | "size" : "1024x1024"
7 | }
8 | ],
9 | "info" : {
10 | "author" : "xcode",
11 | "version" : 1
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/XCAChatGPT.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "platform" : "universal",
6 | "reference" : "systemBlueColor"
7 | },
8 | "idiom" : "universal"
9 | }
10 | ],
11 | "info" : {
12 | "author" : "xcode",
13 | "version" : 1
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/XCAChatGPTWatch Watch App/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "platform" : "universal",
6 | "reference" : "systemTealColor"
7 | },
8 | "idiom" : "universal"
9 | }
10 | ],
11 | "info" : {
12 | "author" : "xcode",
13 | "version" : 1
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Shared/ParserResult.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParserResult.swift
3 | // XCAChatGPT
4 | //
5 | // Created by Alfian Losari on 19/04/23.
6 | //
7 |
8 | import Foundation
9 |
10 | struct ParserResult: Identifiable {
11 |
12 | let id = UUID()
13 | let attributedString: AttributedString
14 | let isCodeBlock: Bool
15 | let codeBlockLanguage: String?
16 |
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "author" : "xcode",
14 | "version" : 1
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "layers" : [
7 | {
8 | "filename" : "Front.imagestacklayer"
9 | },
10 | {
11 | "filename" : "Middle.imagestacklayer"
12 | },
13 | {
14 | "filename" : "Back.imagestacklayer"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | },
6 | "layers" : [
7 | {
8 | "filename" : "Front.imagestacklayer"
9 | },
10 | {
11 | "filename" : "Middle.imagestacklayer"
12 | },
13 | {
14 | "filename" : "Back.imagestacklayer"
15 | }
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/XCAChatGPT/Assets.xcassets/palm.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "palm.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/XCAChatGPT/Assets.xcassets/openai.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "openai.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/XCAChatGPT/Assets.xcassets/profile.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "unnamed.jpg",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/XCAChatGPTMac/Assets.xcassets/icon.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "Shape.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/XCAChatGPTMac/Assets.xcassets/palm.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "palm.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/XCAChatGPTMac/XCAChatGPTMac.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 | com.apple.security.network.client
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/openai.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "openai.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/palm.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "palm.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/XCAChatGPTMac/Assets.xcassets/openai.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "openai.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/XCAChatGPTMac/Assets.xcassets/profile.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "unnamed.jpg",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/profile.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "unnamed.jpg",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/XCAChatGPTWatch Watch App/Assets.xcassets/palm.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "palm.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/XCAChatGPTWatch Watch App/Assets.xcassets/openai.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "openai.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/XCAChatGPTWatch Watch App/Assets.xcassets/profile.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "unnamed.jpg",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Shared/LLM Provider/LLMClient.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LLMClient.swift
3 | // XCAChatGPT
4 | //
5 | // Created by Alfian Losari on 03/06/23.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol LLMClient {
11 |
12 | var provider: LLMProvider { get }
13 |
14 | func sendMessageStream(text: String) async throws -> AsyncThrowingStream
15 | func sendMessage(_ text: String) async throws -> String
16 | func deleteHistoryList()
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/ScrollView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ScrollView.swift
3 | // XCAChatGPTTV
4 | //
5 | // Created by Alfian Losari on 06/02/23.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 | import SwiftUI
11 |
12 | //struct ScrollViewWrapper: UIViewRepresentable {
13 | //
14 | //
15 | // func makeUIView(context: Context) -> some UIView {
16 | // return UIScrollView()
17 | // }
18 | //
19 | // func updateUIView(_ uiView: UIScrollView, context: Context) {
20 | //
21 | // }
22 | //
23 | //}
24 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "tv-marketing",
13 | "scale" : "1x"
14 | },
15 | {
16 | "idiom" : "tv-marketing",
17 | "scale" : "2x"
18 | }
19 | ],
20 | "info" : {
21 | "author" : "xcode",
22 | "version" : 1
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "tv-marketing",
13 | "scale" : "1x"
14 | },
15 | {
16 | "idiom" : "tv-marketing",
17 | "scale" : "2x"
18 | }
19 | ],
20 | "info" : {
21 | "author" : "xcode",
22 | "version" : 1
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "tv-marketing",
13 | "scale" : "1x"
14 | },
15 | {
16 | "idiom" : "tv-marketing",
17 | "scale" : "2x"
18 | }
19 | ],
20 | "info" : {
21 | "author" : "xcode",
22 | "version" : 1
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "tv-marketing",
13 | "scale" : "1x"
14 | },
15 | {
16 | "idiom" : "tv-marketing",
17 | "scale" : "2x"
18 | }
19 | ],
20 | "info" : {
21 | "author" : "xcode",
22 | "version" : 1
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | },
11 | {
12 | "idiom" : "tv-marketing",
13 | "scale" : "1x"
14 | },
15 | {
16 | "idiom" : "tv-marketing",
17 | "scale" : "2x"
18 | }
19 | ],
20 | "info" : {
21 | "author" : "xcode",
22 | "version" : 1
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Shared/ResponseParsingTask.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ResponseParsingTask.swift
3 | // XCAChatGPT
4 | //
5 | // Created by Alfian Losari on 19/04/23.
6 | //
7 |
8 | import Foundation
9 | import Markdown
10 |
11 | actor ResponseParsingTask {
12 |
13 | func parse(text: String) async -> AttributedOutput {
14 | let document = Document(parsing: text)
15 | var markdownParser = MarkdownAttributedStringParser()
16 | let results = markdownParser.parserResults(from: document)
17 | return AttributedOutput(string: text, results: results)
18 | }
19 |
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/Shared/LLM Provider/LLMConfig.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LLMConfig.swift
3 | // XCAChatGPT
4 | //
5 | // Created by Alfian Losari on 03/06/23.
6 | //
7 |
8 | import Foundation
9 |
10 | struct LLMConfig: Identifiable, Hashable {
11 |
12 | var id: String { apiKey }
13 |
14 | let apiKey: String
15 | let type: ConfigType
16 |
17 | enum ConfigType: Hashable {
18 | case chatGPT(ChatGPTModel)
19 | case palm
20 | }
21 |
22 | func createClient() -> LLMClient {
23 | switch self.type {
24 | case .chatGPT(let model):
25 | return ChatGPTAPI(apiKey: apiKey, model: model.rawValue)
26 | case .palm:
27 | return PaLMChatAPI(apiKey: apiKey)
28 | }
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "assets" : [
3 | {
4 | "filename" : "App Icon - App Store.imagestack",
5 | "idiom" : "tv",
6 | "role" : "primary-app-icon",
7 | "size" : "1280x768"
8 | },
9 | {
10 | "filename" : "App Icon.imagestack",
11 | "idiom" : "tv",
12 | "role" : "primary-app-icon",
13 | "size" : "400x240"
14 | },
15 | {
16 | "filename" : "Top Shelf Image Wide.imageset",
17 | "idiom" : "tv",
18 | "role" : "top-shelf-image-wide",
19 | "size" : "2320x720"
20 | },
21 | {
22 | "filename" : "Top Shelf Image.imageset",
23 | "idiom" : "tv",
24 | "role" : "top-shelf-image",
25 | "size" : "1920x720"
26 | }
27 | ],
28 | "info" : {
29 | "author" : "xcode",
30 | "version" : 1
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Shared/MessageRow.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MessageRow.swift
3 | // XCAChatGPT
4 | //
5 | // Created by Alfian Losari on 02/02/23.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AttributedOutput {
11 | let string: String
12 | let results: [ParserResult]
13 | }
14 |
15 | enum MessageRowType {
16 | case attributed(AttributedOutput)
17 | case rawText(String)
18 |
19 | var text: String {
20 | switch self {
21 | case .attributed(let attributedOutput):
22 | return attributedOutput.string
23 | case .rawText(let string):
24 | return string
25 | }
26 | }
27 | }
28 |
29 | struct MessageRow: Identifiable {
30 |
31 | let id = UUID()
32 |
33 | var isInteracting: Bool
34 |
35 | let sendImage: String
36 | var send: MessageRowType
37 | var sendText: String {
38 | send.text
39 | }
40 |
41 | let responseImage: String
42 | var response: MessageRowType?
43 | var responseText: String? {
44 | response?.text
45 | }
46 |
47 | var responseError: String?
48 |
49 | }
50 |
51 |
52 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Alfian Losari
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/XCAChatGPTMac/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "scale" : "1x",
6 | "size" : "16x16"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "scale" : "2x",
11 | "size" : "16x16"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "scale" : "1x",
16 | "size" : "32x32"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "scale" : "2x",
21 | "size" : "32x32"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "scale" : "1x",
26 | "size" : "128x128"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "scale" : "2x",
31 | "size" : "128x128"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "scale" : "1x",
36 | "size" : "256x256"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "scale" : "2x",
41 | "size" : "256x256"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "scale" : "1x",
46 | "size" : "512x512"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "scale" : "2x",
51 | "size" : "512x512"
52 | }
53 | ],
54 | "info" : {
55 | "author" : "xcode",
56 | "version" : 1
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Shared/LLM Provider/PaLMChatAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PaLMChatAPI.swift
3 | // XCAChatGPT
4 | //
5 | // Created by Alfian Losari on 03/06/23.
6 | //
7 |
8 | import Foundation
9 | import GoogleGenerativeAI
10 |
11 | class PaLMChatAPI: LLMClient {
12 |
13 | var provider: LLMProvider { .palm }
14 | private let palmClient: GenerativeLanguage
15 | private var history = [GoogleGenerativeAI.Message]()
16 |
17 | init(apiKey: String) {
18 | self.palmClient = .init(apiKey: apiKey)
19 | }
20 |
21 | func sendMessage(_ text: String) async throws -> String {
22 | let response = try await palmClient.chat(message: text, history: history)
23 | if let candidate = response.candidates?.first, let responseText = candidate.content {
24 | if let historicMessages = response.messages {
25 | self.history = historicMessages
26 | self.history.append(candidate)
27 | }
28 | return responseText
29 | } else {
30 | throw "No response"
31 | }
32 | }
33 |
34 | func sendMessageStream(text: String) async throws -> AsyncThrowingStream {
35 | fatalError("Not supported")
36 | }
37 |
38 | func deleteHistoryList() {
39 | self.history = []
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/XCAChatGPT/TextView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TextView.swift
3 | // XCAChatGPT
4 | //
5 | // Created by Alfian Losari on 28/03/23.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | struct TextView: UIViewRepresentable {
12 |
13 | let colors = [
14 | UIColor(red: 199/255, green: 195/255, blue: 212/255, alpha: 1),
15 | UIColor(red: 202/255, green: 236/255, blue: 202/255, alpha: 1),
16 | UIColor(red: 241/255, green: 218/255, blue: 181/255, alpha: 1),
17 | UIColor(red: 236/255, green: 180/255, blue: 180/255, alpha: 1),
18 | UIColor(red: 183/255, green: 219/255, blue: 241/255, alpha: 1)
19 | ]
20 |
21 | let output: TokenOutput
22 |
23 | func updateUIView(_ textView: UITextView, context: Context) {
24 | let attributedText = NSMutableAttributedString()
25 | output.stringTokens.enumerated().forEach { index, string in
26 | let attributes: [NSAttributedString.Key: Any] = [
27 | .font: UIFont.preferredFont(forTextStyle: .body),
28 | .kern: 1,
29 | .backgroundColor: colors[index % colors.count],
30 | ]
31 |
32 | let attributedTokenText = NSAttributedString(string: string, attributes: attributes)
33 | attributedText.append(attributedTokenText)
34 |
35 | }
36 | textView.attributedText = attributedText
37 | }
38 |
39 | func makeUIView(context: Context) -> UITextView {
40 | let tv = UITextView()
41 | tv.isEditable = false
42 | return tv
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # PaLMChat & ChatGPT SwiftUI iOS, macOS, watchOS, tvOS App
2 |
3 | 
4 |
5 | This is a native iOS, macOS, watchOS, tvOS App for interacting with PaLM API & ChatGPT LLM Chatbots built using SwiftUI, OpenAPI Official ChatGPT API, & Google Generative AI SDK SPM.
6 |
7 | It is also able to render response with markdown and code syntax highlighting.
8 |
9 | ## Video tutorial
10 | - [iOS YouTube](https://youtu.be/PLEgTCT20zU)
11 | - [macOS YouTube](https://youtu.be/Wl1cDvwpJoE)
12 | - [watchOS YouTube](https://youtu.be/DwXy0gKz1GY)
13 | - [tvOS YouTube](https://youtu.be/7RQHG7GXJ_U)
14 | - [Upgrade to Official API YouTube](https://youtu.be/9byLhs5hQjI)
15 |
16 | ## Requirements
17 | - Xcode 14
18 | - Register at openai.com/api
19 | - Create API Key from either OpenAI or PaLM API MakerSuite
20 |
21 | ## ChatGPTSwift API Lib
22 | You can use this standalone api client to access ChatGPT API, you can add dependency for [ChatGPTSwift](https://github.com/alfianlosari/ChatGPTSwift) to access the API only if you want to integrate into your own app.
23 |
24 | ## GPT Encoder Lib
25 | I've also created [GPTEncoder](https://github.com/alfianlosari/GPTEncoder) Swift BPE Encoder/Decoder for OpenAI GPT Models. A programmatic interface for tokenizing text for OpenAI GPT API.
26 |
27 | ## GPT Tokenizer UI Lib
28 | I've also created [GPTTokenizerUI](https://github.com/alfianlosari/GPTTokenizerUI), a SPM lib you can integrate in your app for providing GUI to input text and show the tokenization results used by GPT API.
29 |
30 | 
31 |
--------------------------------------------------------------------------------
/Shared/DotLoadingView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DotLoadingView.swift
3 | // XCAChatGPT
4 | //
5 | // Created by Alfian Losari on 02/02/23.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct DotLoadingView: View {
11 |
12 | @State private var showCircle1 = false
13 | @State private var showCircle2 = false
14 | @State private var showCircle3 = false
15 |
16 | var body: some View {
17 | HStack {
18 | Circle()
19 | .opacity(showCircle1 ? 1 : 0)
20 | Circle()
21 | .opacity(showCircle2 ? 1 : 0)
22 | Circle()
23 | .opacity(showCircle3 ? 1 : 0)
24 | }
25 | .foregroundColor(.gray.opacity(0.5))
26 | .onAppear { performAnimation() }
27 | }
28 |
29 | func performAnimation() {
30 | let animation = Animation.easeInOut(duration: 0.4)
31 | withAnimation(animation) {
32 | self.showCircle1 = true
33 | self.showCircle3 = false
34 | }
35 |
36 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
37 | withAnimation(animation) {
38 | self.showCircle2 = true
39 | self.showCircle1 = false
40 | }
41 | }
42 |
43 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
44 | withAnimation(animation) {
45 | self.showCircle2 = false
46 | self.showCircle3 = true
47 | }
48 | }
49 |
50 | DispatchQueue.main.asyncAfter(deadline: .now() + 1.2) {
51 | self.performAnimation()
52 | }
53 | }
54 | }
55 |
56 | struct DotLoadingView_Previews: PreviewProvider {
57 | static var previews: some View {
58 | DotLoadingView()
59 | }
60 | }
61 |
62 |
--------------------------------------------------------------------------------
/Shared/ChatGPTAPIModels.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChatGPTAPIModels.swift
3 | // XCAChatGPT
4 | //
5 | // Created by Alfian Losari on 03/03/23.
6 | //
7 |
8 | import Foundation
9 |
10 | enum ChatGPTModel: String, Identifiable, CaseIterable {
11 |
12 | var id: Self { self }
13 |
14 | case gpt3Turbo = "gpt-3.5-turbo"
15 | case gpt4 = "gpt-4"
16 |
17 | var text: String {
18 | switch self {
19 | case .gpt3Turbo:
20 | return "GPT-3.5"
21 | case .gpt4:
22 | return "GPT-4"
23 | }
24 | }
25 | }
26 |
27 | struct Message: Codable {
28 | let role: String
29 | let content: String
30 | }
31 |
32 | extension Array where Element == Message {
33 |
34 | var contentCount: Int { reduce(0, { $0 + $1.content.count })}
35 | }
36 |
37 | struct Request: Codable {
38 | let model: String
39 | let temperature: Double
40 | let messages: [Message]
41 | let stream: Bool
42 | }
43 |
44 | struct ErrorRootResponse: Decodable {
45 | let error: ErrorResponse
46 | }
47 |
48 | struct ErrorResponse: Decodable {
49 | let message: String
50 | let type: String?
51 | }
52 |
53 | struct StreamCompletionResponse: Decodable {
54 | let choices: [StreamChoice]
55 | }
56 |
57 | struct CompletionResponse: Decodable {
58 | let choices: [Choice]
59 | let usage: Usage?
60 | }
61 |
62 | struct Usage: Decodable {
63 | let promptTokens: Int?
64 | let completionTokens: Int?
65 | let totalTokens: Int?
66 | }
67 |
68 | struct Choice: Decodable {
69 | let message: Message
70 | let finishReason: String?
71 | }
72 |
73 | struct StreamChoice: Decodable {
74 | let finishReason: String?
75 | let delta: StreamMessage
76 | }
77 |
78 | struct StreamMessage: Decodable {
79 | let role: String?
80 | let content: String?
81 | }
82 |
83 |
--------------------------------------------------------------------------------
/XCAChatGPT.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "generative-ai-swift",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/google/generative-ai-swift",
7 | "state" : {
8 | "revision" : "c16f7c335a45b5541c85bebcdc1443305c10926f",
9 | "version" : "0.2.0"
10 | }
11 | },
12 | {
13 | "identity" : "get",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/kean/Get",
16 | "state" : {
17 | "revision" : "12830cc64f31789ae6f4352d2d51d03a25fc3741",
18 | "version" : "2.1.6"
19 | }
20 | },
21 | {
22 | "identity" : "gptencoder",
23 | "kind" : "remoteSourceControl",
24 | "location" : "https://github.com/alfianlosari/GPTEncoder.git",
25 | "state" : {
26 | "revision" : "8d2057a7073fdbf8fb3ac7b3006e1514cf2f92fb",
27 | "version" : "1.0.3"
28 | }
29 | },
30 | {
31 | "identity" : "highlighterswift",
32 | "kind" : "remoteSourceControl",
33 | "location" : "https://github.com/alfianlosari/HighlighterSwift",
34 | "state" : {
35 | "branch" : "main",
36 | "revision" : "c15eea4e5e8f18b0a2d2173830fcaa250f742bc0"
37 | }
38 | },
39 | {
40 | "identity" : "swift-cmark",
41 | "kind" : "remoteSourceControl",
42 | "location" : "https://github.com/apple/swift-cmark.git",
43 | "state" : {
44 | "branch" : "gfm",
45 | "revision" : "86aeb491675de6f077a3a6df6cbcac1a25dcbee1"
46 | }
47 | },
48 | {
49 | "identity" : "swift-markdown",
50 | "kind" : "remoteSourceControl",
51 | "location" : "https://github.com/apple/swift-markdown",
52 | "state" : {
53 | "branch" : "main",
54 | "revision" : "528d49cb5130e9c84081e13e7bd639a011b9f89d"
55 | }
56 | },
57 | {
58 | "identity" : "urlqueryencoder",
59 | "kind" : "remoteSourceControl",
60 | "location" : "https://github.com/CreateAPI/URLQueryEncoder",
61 | "state" : {
62 | "revision" : "4ce950479707ea109f229d7230ec074a133b15d7",
63 | "version" : "0.2.1"
64 | }
65 | }
66 | ],
67 | "version" : 2
68 | }
69 |
--------------------------------------------------------------------------------
/Shared/LLM Provider/LLMProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LLMProvider.swift
3 | // XCAChatGPT
4 | //
5 | // Created by Alfian Losari on 03/06/23.
6 | //
7 |
8 | import Foundation
9 |
10 | enum LLMProvider: Identifiable, CaseIterable {
11 |
12 | case chatGPT
13 | case palm
14 |
15 | var id: Self { self }
16 |
17 | var text: String {
18 | switch self {
19 | case .chatGPT:
20 | return "OpenAI ChatGPT"
21 | case .palm:
22 | return "Google PaLM"
23 | }
24 | }
25 |
26 | var footerInfo: String {
27 | switch self {
28 | case .chatGPT:
29 | return """
30 | ChatGPT is an artificial intelligence (AI) chatbot developed by OpenAI and released in November 2022. The name "ChatGPT" combines "Chat", referring to its chatbot functionality, and "GPT", which stands for Generative Pre-trained Transformer, a type of large language model (LLM). ChatGPT is built upon OpenAI's foundational GPT models, specifically GPT-3.5 and GPT-4, and has been fine-tuned (an approach to transfer learning) for conversational applications using a combination of supervised and reinforcement learning techniques.
31 | """
32 | case .palm:
33 | return """
34 | PaLM (Pathways Language Model) is a 540 billion parameter transformer-based large language model developed by Google AI.Researchers also trained smaller versions of PaLM, 8 and 62 billion parameter models, to test the effects of model scale.
35 |
36 | PaLM is capable of a wide range of tasks, including commonsense reasoning, arithmetic reasoning, joke explanation, code generation, and translation. When combined with chain-of-thought prompting, PaLM achieved significantly better performance on datasets requiring reasoning of multiple steps, such as word problems and logic-based questions.
37 | """
38 | }
39 | }
40 |
41 | var navigationTitle: String {
42 | switch self {
43 | case .chatGPT:
44 | return "XCA ChatGPT"
45 |
46 | case .palm:
47 | return "XCA PaLMChat"
48 | }
49 | }
50 |
51 | var imageName: String {
52 | switch self {
53 | case .chatGPT:
54 | return "openai"
55 | case .palm:
56 | return "palm"
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/XCAChatGPTMac/XCAChatGPTMacApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XCAChatGPTMacApp.swift
3 | // XCAChatGPTMac
4 | //
5 | // Created by Alfian Losari on 04/02/23.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct XCAChatGPTMacApp: App {
12 |
13 | @StateObject var vm = ViewModel(api: ChatGPTAPI(apiKey: "API_KEY"))
14 | @State var llmConfig: LLMConfig?
15 |
16 | var body: some Scene {
17 | MenuBarExtra(vm.title, systemImage: "bubbles.and.sparkles") {
18 | VStack(spacing: 16) {
19 | HStack {
20 | Text(vm.title).font(.title)
21 | Spacer()
22 |
23 | Button {
24 | exit(0)
25 | } label: {
26 | Image(systemName: "xmark.circle.fill")
27 | .symbolRenderingMode(.multicolor)
28 | .font(.system(size: 24))
29 | }
30 |
31 | .buttonStyle(.borderless)
32 | }.padding()
33 |
34 | LLMConfigView { config in
35 | vm.updateClient(config.createClient())
36 | llmConfig = config
37 | }
38 | }
39 | .frame(width: 480, height: 648)
40 |
41 | .sheet(item: $llmConfig) { _ in
42 | VStack(spacing: 0) {
43 | HStack {
44 | Text(vm.navigationTitle)
45 | .font(.title)
46 | Spacer()
47 |
48 | Button("Switch LLM", role: .destructive) {
49 | llmConfig = nil
50 | }
51 |
52 | Button {
53 | guard !vm.isInteracting else { return }
54 | vm.clearMessages()
55 | } label: {
56 | Image(systemName: "trash")
57 | .symbolRenderingMode(.multicolor)
58 | .font(.system(size: 24))
59 | }
60 |
61 | .buttonStyle(.borderless)
62 | }
63 | .padding()
64 |
65 | ContentView(vm: vm)
66 | }
67 | .frame(width: 480, height: 648)
68 | }
69 | }.menuBarExtraStyle(.window)
70 | }
71 | }
72 |
73 |
--------------------------------------------------------------------------------
/XCAChatGPT/TokenizerViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TokenizerViewModel.swift
3 | // XCAChatGPT
4 | //
5 | // Created by Alfian Losari on 28/03/23.
6 | //
7 |
8 | import Combine
9 | import Foundation
10 | import SwiftUI
11 | import GPTEncoder
12 |
13 | struct TokenOutput {
14 | let text: String
15 | let stringTokens: [String]
16 | let tokens: [Int]
17 | }
18 |
19 | enum OutputType: Identifiable {
20 | case text, tokenIds
21 | var id: Self { self }
22 | }
23 |
24 | class TokenizerViewModel: ObservableObject, @unchecked Sendable {
25 |
26 | let tokenizer = GPTEncoder()
27 |
28 | @Published var inputText = ""
29 | @Published var output: TokenOutput?
30 | @Published var isTokenizing = false
31 | @Published var error: String?
32 | @Published var isShowingError = false
33 | @Published var outputType = OutputType.text
34 |
35 | var cancellables = Set()
36 | var task: Task<(), Never>?
37 |
38 | init() {
39 | startObserve()
40 | }
41 |
42 | func startObserve() {
43 | $inputText
44 | .filter { !$0.isEmpty }
45 | .debounce(for: 0.3, scheduler: DispatchQueue.main)
46 | .sink { [weak self] value in
47 | guard let self = self else { return }
48 | self.task?.cancel()
49 | if self.inputText.isEmpty {
50 | return
51 | }
52 | self.task = Task { await self.tokenize(value: value) }
53 | }.store(in: &cancellables)
54 |
55 |
56 | $inputText
57 | .filter { $0.isEmpty }
58 | .sink { [weak self] _ in
59 | withAnimation { self?.output = nil }
60 | }.store(in: &cancellables)
61 | }
62 |
63 | func tokenize(value: String) async {
64 | if Task.isCancelled { return }
65 |
66 | Task { @MainActor [weak self] in
67 | withAnimation {
68 | self?.isTokenizing = true
69 | }
70 | }
71 |
72 | let tokens = self.tokenizer.encode(text: value)
73 | let stringTokens = tokens.map { tokenizer.decode(tokens: [$0]) }
74 |
75 | Task { @MainActor [weak self] in
76 | if Task.isCancelled { return }
77 | withAnimation {
78 | self?.output = .init(text: value, stringTokens: stringTokens, tokens: tokens)
79 | self?.isTokenizing = false
80 | }
81 | }
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore,
4 | .DS_Store
5 | Objective-C.gitignore & Swift.gitignore
6 |
7 | ## User settings
8 | xcuserdata/
9 |
10 | ## compatibility with Xcode 8 and earlier (ignoring not required starting
11 | Xcode 9)
12 | *.xcscmblueprint
13 | *.xccheckout
14 |
15 | ## compatibility with Xcode 3 and earlier (ignoring not required starting
16 | Xcode 4)
17 | build/
18 | DerivedData/
19 | *.moved-aside
20 | *.pbxuser
21 | !default.pbxuser
22 | *.mode1v3
23 | !default.mode1v3
24 | *.mode2v3
25 | !default.mode2v3
26 | *.perspectivev3
27 | !default.perspectivev3
28 |
29 | ## Obj-C/Swift specific
30 | *.hmap
31 |
32 | ## App packaging
33 | *.ipa
34 | *.dSYM.zip
35 | *.dSYM
36 |
37 | ## Playgrounds
38 | timeline.xctimeline
39 | playground.xcworkspace
40 |
41 | # Swift Package Manager
42 | #
43 | # Add this line if you want to avoid checking in source code from Swift
44 | Package Manager dependencies.
45 | # Packages/
46 | # Package.pins
47 | # Package.resolved
48 | # *.xcodeproj
49 | #
50 | # Xcode automatically generates this directory with a .xcworkspacedata
51 | file and xcuserdata
52 | # hence it is not needed unless you have added a package configuration
53 | file to your project
54 | # .swiftpm
55 |
56 | .build/
57 |
58 | # CocoaPods
59 | #
60 | # We recommend against adding the Pods directory to your .gitignore.
61 | However
62 | # you should judge for yourself, the pros and cons are mentioned at:
63 | #
64 | https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
65 | #
66 | # Pods/
67 | #
68 | # Add this line if you want to avoid checking in source code from the
69 | Xcode workspace
70 | # *.xcworkspace
71 |
72 | # Carthage
73 | #
74 | # Add this line if you want to avoid checking in source code from Carthage
75 | dependencies.
76 | # Carthage/Checkouts
77 |
78 | Carthage/Build/
79 |
80 | # Accio dependency management
81 | Dependencies/
82 | .accio/
83 |
84 | # fastlane
85 | #
86 | # It is recommended to not store the screenshots in the git repo.
87 | # Instead, use fastlane to re-generate the screenshots whenever they are
88 | needed.
89 | # For more information about the recommended setup visit:
90 | #
91 | https://docs.fastlane.tools/best-practices/source-control/#source-control
92 |
93 | fastlane/report.xml
94 | fastlane/Preview.html
95 | fastlane/screenshots/**/*.png
96 | fastlane/test_output
97 |
98 | # Code Injection
99 | #
100 | # After new code Injection tools there's a generated folder
101 | /iOSInjectionProject
102 | # https://github.com/johnno1962/injectionforxcode
103 |
104 | iOSInjectionProject/
105 |
--------------------------------------------------------------------------------
/XCAChatGPT/XCAChatGPTApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XCAChatGPTApp.swift
3 | // XCAChatGPT
4 | //
5 | // Created by Alfian Losari on 01/02/23.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct XCAChatGPTApp: App {
12 |
13 | @StateObject var vm = ViewModel(api: ChatGPTAPI(apiKey: "PROVIDE_API_KEY"))
14 | @State var isShowingTokenizer = false
15 | @State var llmConfig: LLMConfig?
16 |
17 | var body: some Scene {
18 | WindowGroup {
19 | NavigationStack {
20 | LLMConfigView { config in
21 | vm.updateClient(config.createClient())
22 | llmConfig = config
23 | }
24 | .navigationTitle(vm.title)
25 | }
26 | .fullScreenCover(item: $llmConfig) { config in
27 | NavigationStack {
28 | ContentView(vm: vm)
29 | .toolbar {
30 | ToolbarItemGroup(placement: .navigationBarTrailing) {
31 | if case .chatGPT = llmConfig?.type {
32 | Button("Tokenizer") {
33 | self.isShowingTokenizer = true
34 | }
35 | .disabled(vm.isInteracting)
36 | }
37 |
38 | Button("Clear", role: .destructive) {
39 | vm.clearMessages()
40 | }
41 | .disabled(vm.isInteracting)
42 | }
43 |
44 | ToolbarItem(placement: .navigationBarLeading) {
45 | Button("Switch LLM", role: .destructive) {
46 | llmConfig = nil
47 | }
48 | }
49 | }
50 | }
51 | .fullScreenCover(isPresented: $isShowingTokenizer) {
52 | NavigationTokenView()
53 | }
54 | }
55 | }
56 | }
57 | }
58 |
59 |
60 | struct NavigationTokenView: View {
61 |
62 | @Environment(\.dismiss) var dismiss
63 |
64 | var body: some View {
65 | NavigationStack {
66 | TokenizerView()
67 | .toolbar {
68 | ToolbarItem(placement: .navigationBarTrailing) {
69 | Button("Close") {
70 | dismiss()
71 | }
72 | }
73 | }
74 | }
75 | .interactiveDismissDisabled()
76 | }
77 | }
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/Shared/LLMConfigView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LLMConfigView.swift
3 | // XCAChatGPT
4 | //
5 | // Created by Alfian Losari on 03/06/23.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct LLMConfigView: View {
11 |
12 | let onStartChatTapped: (_ result: LLMConfig) -> Void
13 | @State var apiKey = ""
14 | @State var llmProvider = LLMProvider.chatGPT
15 | @State var chatGPTModel = ChatGPTModel.gpt3Turbo
16 |
17 | var body: some View {
18 | #if os(macOS)
19 | ScrollView { sectionsView }
20 | .padding(.horizontal)
21 | #else
22 | List { sectionsView }
23 | #endif
24 | }
25 |
26 | @ViewBuilder
27 | var sectionsView: some View {
28 | Section("LLM Provider") {
29 | Picker("Provider", selection: $llmProvider) {
30 | ForEach(LLMProvider.allCases) {
31 | Text($0.text).id($0)
32 | }
33 | }
34 | #if !os(watchOS)
35 | .pickerStyle(.segmented)
36 | #endif
37 | }
38 |
39 | Section("Configuration") {
40 | TextField("Enter API Key", text: $apiKey)
41 | .autocorrectionDisabled()
42 | #if os(macOS)
43 | .textCase(.none)
44 | .textFieldStyle(.roundedBorder)
45 | #else
46 | .textInputAutocapitalization(.never)
47 | #endif
48 |
49 | if llmProvider == .chatGPT {
50 | Picker("Model", selection: $chatGPTModel) {
51 | ForEach(ChatGPTModel.allCases) {
52 | Text($0.text).id($0)
53 | }
54 | }
55 | #if !os(watchOS)
56 | .pickerStyle(.segmented)
57 | #endif
58 | }
59 | }
60 |
61 | Section {
62 | Button("Start Chat") {
63 | let type: LLMConfig.ConfigType
64 | switch llmProvider {
65 | case .chatGPT:
66 | type = .chatGPT(chatGPTModel)
67 | case .palm:
68 | type = .palm
69 | }
70 |
71 | self.onStartChatTapped(.init(apiKey: apiKey, type: type))
72 | }
73 | #if os(macOS)
74 | .buttonStyle(.borderedProminent)
75 | #endif
76 | .frame(maxWidth: .infinity)
77 | .disabled(apiKey.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
78 |
79 | } footer: {
80 | Text(llmProvider.footerInfo)
81 | .padding(.vertical)
82 | }
83 | }
84 | }
85 |
86 | struct LLMConfigView_Previews: PreviewProvider {
87 | static var previews: some View {
88 | NavigationStack {
89 | LLMConfigView { result in
90 |
91 | }
92 | }
93 | }
94 | }
95 |
96 |
--------------------------------------------------------------------------------
/XCAChatGPTWatch Watch App/XCAChatGPTWatchApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XCAChatGPTWatchApp.swift
3 | // XCAChatGPTWatch Watch App
4 | //
5 | // Created by Alfian Losari on 05/02/23.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct XCAChatGPTWatch_Watch_AppApp: App {
12 |
13 | @StateObject var vm = ViewModel(api: ChatGPTAPI(apiKey: "API_KEY"))
14 | @State private var presentedConfigs = [LLMConfig]()
15 |
16 | var body: some Scene {
17 | WindowGroup {
18 | NavigationStack(path: $presentedConfigs) {
19 | LLMConfigView { config in
20 | vm.updateClient(config.createClient())
21 | presentedConfigs.append(config)
22 | }
23 | .navigationTitle(vm.title)
24 | .navigationDestination(for: LLMConfig.self) { _ in
25 | ContentView(vm: vm)
26 | .edgesIgnoringSafeArea([.horizontal, .bottom])
27 | .navigationBarTitleDisplayMode(.inline)
28 | .overlay {
29 | if vm.messages.isEmpty {
30 | Text("Scroll to top and tap send to Chat")
31 | }
32 | }
33 | .toolbar {
34 | ToolbarItemGroup {
35 | HStack {
36 | Button("Send") {
37 | self.presentInputController(withSuggestions: []) { result in
38 | Task { @MainActor in
39 | guard !result.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return }
40 | vm.inputMessage = result.trimmingCharacters(in: .whitespacesAndNewlines)
41 | await vm.sendTapped()
42 | }
43 | }
44 | }
45 |
46 | Button("Clear", role: .destructive) {
47 | vm.clearMessages()
48 | }
49 | .tint(.red)
50 | .disabled(vm.isInteracting || vm.messages.isEmpty)
51 | }
52 | .padding(.bottom)
53 | }
54 | }
55 | }
56 |
57 | }
58 |
59 | }
60 | }
61 | }
62 |
63 | extension App {
64 | typealias StringCompletion = (String) -> Void
65 |
66 | func presentInputController(withSuggestions suggestions: [String], completion: @escaping StringCompletion) {
67 | WKExtension.shared()
68 | .visibleInterfaceController?
69 | .presentTextInputController(withSuggestions: suggestions,
70 | allowedInputMode: .plain) { result in
71 |
72 | guard let result = result as? [String], let firstElement = result.first else {
73 | completion("")
74 | return
75 | }
76 |
77 | completion(firstElement)
78 | }
79 | }
80 | }
81 |
82 |
--------------------------------------------------------------------------------
/Shared/CodeBlockView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CodeBlockView.swift
3 | // XCAChatGPT
4 | //
5 | // Created by Alfian Losari on 19/04/23.
6 | //
7 |
8 | import SwiftUI
9 | import Markdown
10 |
11 | enum HighlighterConstants {
12 | static let color = Color(red: 38/255, green: 38/255, blue: 38/255)
13 | }
14 |
15 | struct CodeBlockView: View {
16 |
17 | let parserResult: ParserResult
18 | @State var isCopied = false
19 |
20 | var body: some View {
21 | VStack(alignment: .leading) {
22 | header
23 | .padding(.horizontal)
24 | .padding(.vertical, 8)
25 | .background(Color(red: 9/255, green: 49/255, blue: 69/255))
26 |
27 | ScrollView(.horizontal, showsIndicators: true) {
28 | Text(parserResult.attributedString)
29 | .padding(.horizontal, 16)
30 | .textSelection(.enabled)
31 | }
32 | }
33 | .background(HighlighterConstants.color)
34 | .cornerRadius(8)
35 | }
36 |
37 | var header: some View {
38 | HStack {
39 | if let codeBlockLanguage = parserResult.codeBlockLanguage {
40 | Text(codeBlockLanguage.capitalized)
41 | .font(.headline.monospaced())
42 | .foregroundColor(.white)
43 | }
44 | Spacer()
45 | button
46 | }
47 | }
48 |
49 | @ViewBuilder
50 | var button: some View {
51 | if isCopied {
52 | HStack {
53 | Text("Copied")
54 | .foregroundColor(.white)
55 | .font(.subheadline.monospaced().bold())
56 | Image(systemName: "checkmark.circle.fill")
57 | .imageScale(.large)
58 | .symbolRenderingMode(.multicolor)
59 | }
60 | .frame(alignment: .trailing)
61 | } else {
62 | Button {
63 | let string = NSAttributedString(parserResult.attributedString).string
64 | UIPasteboard.general.string = string
65 | withAnimation {
66 | isCopied = true
67 | }
68 | DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
69 | withAnimation {
70 | isCopied = false
71 | }
72 | }
73 | } label: {
74 | Image(systemName: "doc.on.doc")
75 | }
76 | .foregroundColor(.white)
77 | }
78 | }
79 | }
80 |
81 | struct CodeBlockView_Previews: PreviewProvider {
82 |
83 | static var markdownString = """
84 | ```swift
85 | let api = ChatGPTAPI(apiKey: "API_KEY")
86 |
87 | Task {
88 | do {
89 | let stream = try await api.sendMessageStream(text: "What is ChatGPT?")
90 | for try await line in stream {
91 | print(line)
92 | }
93 | } catch {
94 | print(error.localizedDescription)
95 | }
96 | }
97 | ```
98 | """
99 |
100 | static let parserResult: ParserResult = {
101 | let document = Document(parsing: markdownString)
102 | var parser = MarkdownAttributedStringParser()
103 | return parser.parserResults(from: document)[0]
104 | }()
105 |
106 | static var previews: some View {
107 | CodeBlockView(parserResult: parserResult)
108 | }
109 | }
110 |
111 |
112 |
--------------------------------------------------------------------------------
/XCAChatGPTTV/XCAChatGPTTVApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // XCAChatGPTTVApp.swift
3 | // XCAChatGPTTV
4 | //
5 | // Created by Alfian Losari on 05/02/23.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct XCAChatGPTTVApp: App {
12 |
13 | @StateObject var vm = ViewModel(api: ChatGPTAPI(apiKey: "API_KEY"), enableSpeech: true)
14 | @State var llmConfig: LLMConfig?
15 |
16 | @FocusState var isTextFieldFocused: Bool
17 |
18 | var body: some Scene {
19 | WindowGroup {
20 | VStack(alignment: .center) {
21 | Text(vm.title).font(.largeTitle)
22 | LLMConfigView { config in
23 | vm.updateClient(config.createClient())
24 | llmConfig = config
25 | }
26 | .frame(width: 1280)
27 | }
28 | .opacity(llmConfig == nil ? 1 : 0)
29 | .fullScreenCover(item: $llmConfig) { _ in
30 | VStack {
31 | Text(vm.navigationTitle).font(.largeTitle)
32 | HStack(alignment: .top) {
33 | ContentView(vm: vm)
34 | .cornerRadius(32)
35 | .overlay {
36 | if vm.messages.isEmpty {
37 | Text("Click send to start interacting with ChatGPT")
38 | .multilineTextAlignment(.center)
39 | .font(.headline)
40 | .foregroundColor(Color(UIColor.placeholderText))
41 | } else {
42 | EmptyView()
43 | }
44 | }
45 |
46 | VStack {
47 | TextField("Send", text: $vm.inputMessage)
48 | .multilineTextAlignment(.center)
49 | .frame(width: 176)
50 | .focused($isTextFieldFocused)
51 | .disabled(vm.isInteracting)
52 | .onSubmit {
53 | Task { @MainActor in
54 | await vm.sendTapped()
55 | isTextFieldFocused = true
56 | }
57 | }
58 | .onChange(of: isTextFieldFocused) { _ in
59 | vm.inputMessage = ""
60 | }
61 |
62 | Button("Clear", role: .destructive) {
63 | vm.clearMessages()
64 | }
65 | .frame(width: 176)
66 | .disabled(vm.isInteracting || vm.messages.isEmpty)
67 |
68 | Button("Switch LLM", role: .destructive) {
69 | llmConfig = nil
70 | }
71 | .padding(.vertical)
72 |
73 | ProgressView()
74 | .progressViewStyle(.circular)
75 | .padding()
76 | .opacity(vm.isInteracting ? 1 : 0)
77 | }
78 | }
79 | }
80 |
81 | }
82 |
83 |
84 |
85 | }
86 | }
87 | }
88 |
89 |
--------------------------------------------------------------------------------
/Shared/ContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // XCAChatGPT
4 | //
5 | // Created by Alfian Losari on 01/02/23.
6 | //
7 |
8 | import SwiftUI
9 | import AVKit
10 |
11 | struct ContentView: View {
12 |
13 | @Environment(\.colorScheme) var colorScheme
14 | @ObservedObject var vm: ViewModel
15 | @FocusState var isTextFieldFocused: Bool
16 |
17 | var body: some View {
18 | chatListView
19 | .navigationTitle(vm.navigationTitle)
20 | }
21 |
22 | var chatListView: some View {
23 | ScrollViewReader { proxy in
24 | VStack(spacing: 0) {
25 | ScrollView {
26 | LazyVStack(spacing: 0) {
27 | ForEach(vm.messages) { message in
28 | MessageRowView(message: message) { message in
29 | Task { @MainActor in
30 | await vm.retry(message: message)
31 | }
32 | }
33 | }
34 | }
35 | .onTapGesture {
36 | isTextFieldFocused = false
37 | }
38 | }
39 | #if os(iOS) || os(macOS)
40 | Divider()
41 | bottomView(image: "profile", proxy: proxy)
42 | Spacer()
43 | #endif
44 | }
45 | .onChange(of: vm.messages.last?.responseText) { _ in scrollToBottom(proxy: proxy)
46 | }
47 | }
48 | .background(colorScheme == .light ? .white : Color(red: 52/255, green: 53/255, blue: 65/255, opacity: 0.5))
49 | }
50 |
51 | func bottomView(image: String, proxy: ScrollViewProxy) -> some View {
52 | HStack(alignment: .top, spacing: 8) {
53 | if image.hasPrefix("http"), let url = URL(string: image) {
54 | AsyncImage(url: url) { image in
55 | image
56 | .resizable()
57 | .frame(width: 30, height: 30)
58 | } placeholder: {
59 | ProgressView()
60 | }
61 |
62 | } else {
63 | Image(image)
64 | .resizable()
65 | .frame(width: 30, height: 30)
66 | }
67 |
68 | TextField("Send message", text: $vm.inputMessage, axis: .vertical)
69 | .autocorrectionDisabled()
70 | #if os(iOS) || os(macOS)
71 | .textFieldStyle(.roundedBorder)
72 | #endif
73 | .focused($isTextFieldFocused)
74 | .disabled(vm.isInteracting)
75 |
76 | if vm.isInteracting {
77 | #if os(iOS)
78 | Button {
79 | vm.cancelStreamingResponse()
80 | } label: {
81 | Image(systemName: "stop.circle.fill")
82 | .font(.system(size: 30))
83 | .symbolRenderingMode(.multicolor)
84 | .foregroundColor(.red)
85 | }
86 | #else
87 | DotLoadingView().frame(width: 60, height: 30)
88 | #endif
89 | } else {
90 | Button {
91 | Task { @MainActor in
92 | isTextFieldFocused = false
93 | scrollToBottom(proxy: proxy)
94 | await vm.sendTapped()
95 | }
96 | } label: {
97 | Image(systemName: "paperplane.circle.fill")
98 | .rotationEffect(.degrees(45))
99 | .font(.system(size: 30))
100 | }
101 | #if os(macOS)
102 | .buttonStyle(.borderless)
103 | .keyboardShortcut(.defaultAction)
104 | .foregroundColor(.accentColor)
105 | #endif
106 | .disabled(vm.inputMessage.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
107 | }
108 | }
109 | .padding(.horizontal, 16)
110 | .padding(.top, 12)
111 | }
112 |
113 | private func scrollToBottom(proxy: ScrollViewProxy) {
114 | guard let id = vm.messages.last?.id else { return }
115 | proxy.scrollTo(id, anchor: .bottomTrailing)
116 | }
117 | }
118 |
119 | struct ContentView_Previews: PreviewProvider {
120 | static var previews: some View {
121 | NavigationStack {
122 | ContentView(vm: ViewModel(api: ChatGPTAPI(apiKey: "PROVIDE_API_KEY")))
123 | }
124 | }
125 | }
126 |
127 |
--------------------------------------------------------------------------------
/XCAChatGPT/TokenizerView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TokenizerView.swift
3 | // XCAChatGPT
4 | //
5 | // Created by Alfian Losari on 28/03/23.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct TokenizerView: View {
11 |
12 | @StateObject var vm = TokenizerViewModel()
13 | @FocusState private var isFocused: Bool
14 |
15 | var body: some View {
16 | List {
17 | inputSection
18 | outputSection
19 | }
20 | .navigationTitle("GPT Tokenizer")
21 | }
22 |
23 | var inputSection: some View {
24 | Section {
25 | TextField("Enter text to tokenize", text: $vm.inputText, axis: .vertical)
26 | .focused($isFocused)
27 | .lineLimit(4...12)
28 | .toolbar {
29 | ToolbarItem(placement: .keyboard) {
30 | HStack {
31 | Spacer()
32 | Button("Done") {
33 | isFocused = false
34 | }
35 | }
36 | }
37 | }
38 |
39 | HStack {
40 | Button("Clear") {
41 | withAnimation {
42 | vm.inputText = ""
43 | }
44 | }
45 | .buttonStyle(.borderedProminent)
46 | .disabled(vm.inputText.isEmpty)
47 |
48 | Button("Show example") {
49 | withAnimation {
50 | vm.inputText = exampleText
51 | isFocused = false
52 | }
53 |
54 | }
55 | .buttonStyle(.borderedProminent)
56 | .disabled(vm.inputText == exampleText)
57 |
58 | Spacer()
59 |
60 | if vm.isTokenizing {
61 | ProgressView()
62 | }
63 |
64 | }
65 | .padding(.vertical, 2)
66 | } header: {
67 | Text("Input")
68 | }
69 |
70 | }
71 |
72 | var outputSection: some View {
73 | Section {
74 | if let output = vm.output {
75 | VStack(alignment: .leading) {
76 | HStack {
77 | VStack {
78 | Text("Tokens").font(.subheadline)
79 | Text("\(output.tokens.count)").font(.headline)
80 | }
81 |
82 | Divider()
83 | .frame(height: 32)
84 |
85 | VStack {
86 | Text("Characters").font(.subheadline)
87 | Text("\(output.text.count)").font(.headline)
88 | }
89 | }
90 | .frame(maxWidth: .infinity, alignment: .center)
91 |
92 | Picker("Output Type", selection: $vm.outputType) {
93 | Text("Text").tag(OutputType.text)
94 | Text("Token Ids").tag(OutputType.tokenIds)
95 | }
96 | .pickerStyle(.segmented)
97 |
98 | switch vm.outputType {
99 | case .text:
100 | TextView(output: output)
101 | .frame(height: 240)
102 |
103 | case .tokenIds:
104 | Text("\(output.tokens.description)")
105 | .textSelection(.enabled)
106 | .multilineTextAlignment(.leading)
107 | .padding(.vertical)
108 | }
109 | }
110 | }
111 | } header: {
112 | if vm.output != nil {
113 | Text("Output")
114 | }
115 | } footer: {
116 | Text(footerText).padding(.top, vm.output != nil ? 8 : 0)
117 | }
118 | }
119 | }
120 |
121 | struct TokenizerView_Previews: PreviewProvider {
122 | static var previews: some View {
123 | NavigationStack {
124 | TokenizerView()
125 | }
126 | }
127 | }
128 |
129 |
130 |
131 | let exampleText = """
132 | Many words map to one token, but some don't: indivisible.
133 |
134 | Unicode characters like emojis may be split into many tokens containing the underlying bytes: 🤚🏾
135 |
136 | Sequences of characters commonly found next to each other may be grouped together: 1234567890
137 | """
138 |
139 |
140 | let footerText = """
141 | The GPT family of models process text using tokens, which are common sequences of characters found in text. The models understand the statistical relationships between these tokens, and excel at producing the next token in a sequence of tokens.
142 |
143 | You can use this tool to understand how a piece of text would be tokenized by the API, and the total count of tokens in that piece of text.
144 |
145 | A helpful rule of thumb is that one token generally corresponds to ~4 characters of text for common English text. This translates to roughly ¾ of a word (so 100 tokens ~= 75 words).
146 |
147 | if your input contains one or more unicode characters that map to multiple tokens. The output visualization may display the bytes in each token in a non-standard way.
148 |
149 | If you need a programmatic interface for tokenizing text, check out the GPTEncoder SPM or Cocoapods lib for Swift.
150 | """
151 |
--------------------------------------------------------------------------------
/Shared/ChatGPTAPI.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChatGPTAPI.swift
3 | // XCAChatGPT
4 | //
5 | // Created by Alfian Losari on 01/02/23.
6 | //
7 |
8 | import Foundation
9 |
10 | class ChatGPTAPI: LLMClient, @unchecked Sendable {
11 |
12 | var provider: LLMProvider { .chatGPT }
13 |
14 | private let systemMessage: Message
15 | private let temperature: Double
16 | private let model: String
17 |
18 | private let apiKey: String
19 | private var historyList = [Message]()
20 | private let urlSession = URLSession.shared
21 | private var urlRequest: URLRequest {
22 | let url = URL(string: "https://api.openai.com/v1/chat/completions")!
23 | var urlRequest = URLRequest(url: url)
24 | urlRequest.httpMethod = "POST"
25 | headers.forEach { urlRequest.setValue($1, forHTTPHeaderField: $0) }
26 | return urlRequest
27 | }
28 |
29 | let dateFormatter: DateFormatter = {
30 | let df = DateFormatter()
31 | df.dateFormat = "YYYY-MM-dd"
32 | return df
33 | }()
34 |
35 | private let jsonDecoder: JSONDecoder = {
36 | let jsonDecoder = JSONDecoder()
37 | jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
38 | return jsonDecoder
39 | }()
40 |
41 | private var headers: [String: String] {
42 | [
43 | "Content-Type": "application/json",
44 | "Authorization": "Bearer \(apiKey)"
45 | ]
46 | }
47 |
48 |
49 | init(apiKey: String, model: String = "gpt-3.5-turbo", systemPrompt: String = "You are a helpful assistant", temperature: Double = 0.5) {
50 | self.apiKey = apiKey
51 | self.model = model
52 | self.systemMessage = .init(role: "system", content: systemPrompt)
53 | self.temperature = temperature
54 | }
55 |
56 | private func generateMessages(from text: String) -> [Message] {
57 | var messages = [systemMessage] + historyList + [Message(role: "user", content: text)]
58 |
59 | if messages.contentCount > (4000 * 4) {
60 | _ = historyList.removeFirst()
61 | messages = generateMessages(from: text)
62 | }
63 | return messages
64 | }
65 |
66 | private func jsonBody(text: String, stream: Bool = true) throws -> Data {
67 | let request = Request(model: model, temperature: temperature,
68 | messages: generateMessages(from: text), stream: stream)
69 | return try JSONEncoder().encode(request)
70 | }
71 |
72 | private func appendToHistoryList(userText: String, responseText: String) {
73 | self.historyList.append(.init(role: "user", content: userText))
74 | self.historyList.append(.init(role: "assistant", content: responseText))
75 | }
76 |
77 | func sendMessageStream(text: String) async throws -> AsyncThrowingStream {
78 | var urlRequest = self.urlRequest
79 | urlRequest.httpBody = try jsonBody(text: text)
80 |
81 | let (result, response) = try await urlSession.bytes(for: urlRequest)
82 | try Task.checkCancellation()
83 |
84 | guard let httpResponse = response as? HTTPURLResponse else {
85 | throw "Invalid response"
86 | }
87 |
88 | guard 200...299 ~= httpResponse.statusCode else {
89 | var errorText = ""
90 | for try await line in result.lines {
91 | try Task.checkCancellation()
92 | errorText += line
93 | }
94 |
95 | if let data = errorText.data(using: .utf8), let errorResponse = try? jsonDecoder.decode(ErrorRootResponse.self, from: data).error {
96 | errorText = "\n\(errorResponse.message)"
97 | }
98 |
99 | throw "Bad Response: \(httpResponse.statusCode), \(errorText)"
100 | }
101 |
102 | var responseText = ""
103 | let streams: AsyncThrowingStream = AsyncThrowingStream { continuation in
104 | Task {
105 | do {
106 | for try await line in result.lines {
107 | try Task.checkCancellation()
108 | continuation.yield(line)
109 | }
110 | continuation.finish()
111 | } catch {
112 | continuation.finish(throwing: error)
113 | }
114 | }
115 | }
116 |
117 | return AsyncThrowingStream { [weak self] in
118 | guard let self else { return nil }
119 | for try await line in streams {
120 | try Task.checkCancellation()
121 | if line.hasPrefix("data: "),
122 | let data = line.dropFirst(6).data(using: .utf8),
123 | let response = try? self.jsonDecoder.decode(StreamCompletionResponse.self, from: data),
124 | let text = response.choices.first?.delta.content {
125 | responseText += text
126 | return text
127 | }
128 | }
129 | self.appendToHistoryList(userText: text, responseText: responseText)
130 | return nil
131 | }
132 | }
133 |
134 | func sendMessage(_ text: String) async throws -> String {
135 | var urlRequest = self.urlRequest
136 | urlRequest.httpBody = try jsonBody(text: text, stream: false)
137 |
138 | let (data, response) = try await urlSession.data(for: urlRequest)
139 | try Task.checkCancellation()
140 | guard let httpResponse = response as? HTTPURLResponse else {
141 | throw "Invalid response"
142 | }
143 |
144 | guard 200...299 ~= httpResponse.statusCode else {
145 | var error = "Bad Response: \(httpResponse.statusCode)"
146 | if let errorResponse = try? jsonDecoder.decode(ErrorRootResponse.self, from: data).error {
147 | error.append("\n\(errorResponse.message)")
148 | }
149 | throw error
150 | }
151 |
152 | do {
153 | let completionResponse = try self.jsonDecoder.decode(CompletionResponse.self, from: data)
154 | let responseText = completionResponse.choices.first?.message.content ?? ""
155 | self.appendToHistoryList(userText: text, responseText: responseText)
156 | return responseText
157 | } catch {
158 | throw error
159 | }
160 | }
161 |
162 | func deleteHistoryList() {
163 | self.historyList.removeAll()
164 | }
165 | }
166 |
167 | extension String: CustomNSError {
168 |
169 | public var errorUserInfo: [String : Any] {
170 | [
171 | NSLocalizedDescriptionKey: self
172 | ]
173 | }
174 | }
175 |
176 |
177 |
178 |
--------------------------------------------------------------------------------
/Shared/MessageRowView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MessageRowView.swift
3 | // XCAChatGPT
4 | //
5 | // Created by Alfian Losari on 02/02/23.
6 | //
7 |
8 | import SwiftUI
9 | #if os(iOS)
10 | import Markdown
11 | #endif
12 |
13 | struct MessageRowView: View {
14 |
15 | @Environment(\.colorScheme) private var colorScheme
16 | let message: MessageRow
17 | let retryCallback: (MessageRow) -> Void
18 |
19 | var imageSize: CGSize {
20 | #if os(iOS) || os(macOS)
21 | CGSize(width: 25, height: 25)
22 | #elseif os(watchOS)
23 | CGSize(width: 20, height: 20)
24 | #else
25 | CGSize(width: 80, height: 80)
26 | #endif
27 | }
28 |
29 | var body: some View {
30 | VStack(spacing: 0) {
31 | messageRow(rowType: message.send, image: message.sendImage, bgColor: colorScheme == .light ? .white : Color(red: 52/255, green: 53/255, blue: 65/255, opacity: 0.5))
32 |
33 | if let response = message.response {
34 | Divider()
35 | messageRow(rowType: response, image: message.responseImage, bgColor: colorScheme == .light ? .gray.opacity(0.1) : Color(red: 52/255, green: 53/255, blue: 65/255, opacity: 1), responseError: message.responseError, showDotLoading: message.isInteracting)
36 | Divider()
37 | }
38 | }
39 | }
40 |
41 | func messageRow(rowType: MessageRowType, image: String, bgColor: Color, responseError: String? = nil, showDotLoading: Bool = false) -> some View {
42 | #if os(watchOS)
43 | VStack(alignment: .leading, spacing: 8) {
44 | messageRowContent(rowType: rowType, image: image, responseError: responseError, showDotLoading: showDotLoading)
45 | }
46 |
47 | .padding(16)
48 | .frame(maxWidth: .infinity, alignment: .leading)
49 | .background(bgColor)
50 | #else
51 | HStack(alignment: .top, spacing: 24) {
52 | messageRowContent(rowType: rowType, image: image, responseError: responseError, showDotLoading: showDotLoading)
53 | }
54 | #if os(tvOS)
55 | .padding(32)
56 | #else
57 | .padding(16)
58 | #endif
59 | .frame(maxWidth: .infinity, alignment: .leading)
60 | .background(bgColor)
61 | #endif
62 | }
63 |
64 | @ViewBuilder
65 | func messageRowContent(rowType: MessageRowType, image: String, responseError: String? = nil, showDotLoading: Bool = false) -> some View {
66 | if image.hasPrefix("http"), let url = URL(string: image) {
67 | AsyncImage(url: url) { image in
68 | image
69 | .resizable()
70 | .frame(width: imageSize.width, height: imageSize.height)
71 | } placeholder: {
72 | ProgressView()
73 | }
74 |
75 | } else {
76 | Image(image)
77 | .resizable()
78 | .frame(width: imageSize.width, height: imageSize.height)
79 | }
80 |
81 | VStack(alignment: .leading) {
82 | switch rowType {
83 | case .attributed(let attributedOutput):
84 | attributedView(results: attributedOutput.results)
85 |
86 | case .rawText(let text):
87 | if !text.isEmpty {
88 | #if os(tvOS)
89 | responseTextView(text: text)
90 | #else
91 | Text(text)
92 | .multilineTextAlignment(.leading)
93 | #if os(iOS) || os(macOS)
94 | .textSelection(.enabled)
95 | #endif
96 | #endif
97 | }
98 | }
99 |
100 | if let error = responseError {
101 | Text("Error: \(error)")
102 | .foregroundColor(.red)
103 | .multilineTextAlignment(.leading)
104 |
105 | Button("Regenerate response") {
106 | retryCallback(message)
107 | }
108 | .foregroundColor(.accentColor)
109 | .padding(.top)
110 | }
111 |
112 | if showDotLoading {
113 | #if os(tvOS)
114 | ProgressView()
115 | .progressViewStyle(.circular)
116 | .padding()
117 | #else
118 | DotLoadingView()
119 | .frame(width: 60, height: 30)
120 | #endif
121 |
122 | }
123 | }
124 | }
125 |
126 | func attributedView(results: [ParserResult]) -> some View {
127 | VStack(alignment: .leading, spacing: 0) {
128 | ForEach(results) { parsed in
129 | if parsed.isCodeBlock {
130 | #if os(iOS)
131 | CodeBlockView(parserResult: parsed)
132 | .padding(.bottom, 24)
133 | #else
134 | Text(parsed.attributedString)
135 | #if os(iOS) || os(macOS)
136 | .textSelection(.enabled)
137 | #endif
138 | #endif
139 | } else {
140 | Text(parsed.attributedString)
141 | #if os(iOS) || os(macOS)
142 | .textSelection(.enabled)
143 | #endif
144 | }
145 | }
146 | }
147 | }
148 |
149 | #if os(tvOS)
150 | private func rowsFor(text: String) -> [String] {
151 | var rows = [String]()
152 | let maxLinesPerRow = 8
153 | var currentRowText = ""
154 | var currentLineSum = 0
155 |
156 | for char in text {
157 | currentRowText += String(char)
158 | if char == "\n" {
159 | currentLineSum += 1
160 | }
161 |
162 | if currentLineSum >= maxLinesPerRow {
163 | rows.append(currentRowText)
164 | currentLineSum = 0
165 | currentRowText = ""
166 | }
167 | }
168 |
169 | rows.append(currentRowText)
170 | return rows
171 | }
172 |
173 |
174 | func responseTextView(text: String) -> some View {
175 | ForEach(rowsFor(text: text), id: \.self) { text in
176 | Text(text)
177 | .focusable()
178 | .multilineTextAlignment(.leading)
179 | }
180 | }
181 | #endif
182 |
183 | }
184 |
185 | struct MessageRowView_Previews: PreviewProvider {
186 |
187 | static let message = MessageRow(
188 | isInteracting: true, sendImage: "profile",
189 | send: .rawText("What is SwiftUI?"),
190 | responseImage: "openai",
191 | response: responseMessageRowType)
192 |
193 | static let message2 = MessageRow(
194 | isInteracting: false, sendImage: "profile",
195 | send: .rawText("What is SwiftUI?"),
196 | responseImage: "openai",
197 | response: .rawText(""),
198 | responseError: "ChatGPT is currently not available")
199 |
200 | static var previews: some View {
201 | NavigationStack {
202 | ScrollView {
203 | MessageRowView(message: message, retryCallback: { messageRow in
204 |
205 | })
206 |
207 | MessageRowView(message: message2, retryCallback: { messageRow in
208 |
209 | })
210 |
211 | }
212 | .previewLayout(.sizeThatFits)
213 | }
214 | }
215 |
216 | static var responseMessageRowType: MessageRowType {
217 | #if os(iOS)
218 | let document = Document(parsing: rawString)
219 | var parser = MarkdownAttributedStringParser()
220 | let results = parser.parserResults(from: document)
221 | return MessageRowType.attributed(.init(string: rawString, results: results))
222 | #else
223 | MessageRowType.rawText(rawString)
224 | #endif
225 | }
226 |
227 | static var rawString: String {
228 | #if os(iOS)
229 | """
230 | ## Supported Platforms
231 |
232 | - iOS/tvOS 15 and above
233 | - macOS 12 and above
234 | - watchOS 8 and above
235 | - Linux
236 |
237 | ## Installation
238 |
239 | ### Swift Package Manager
240 | - File > Swift Packages > Add Package Dependency
241 | - Add https://github.com/alfianlosari/ChatGPTSwift.git
242 |
243 | ### Cocoapods
244 | ```ruby
245 | platform :ios, '15.0'
246 | use_frameworks!
247 |
248 | target 'MyApp' do
249 | pod 'ChatGPTSwift', '~> 1.3.1'
250 | end
251 | ```
252 |
253 | ## Requirement
254 |
255 | Register for API key from [OpenAI](https://openai.com/api). Initialize with api key
256 |
257 | ```swift
258 | let api = ChatGPTAPI(apiKey: "API_KEY")
259 | ```
260 |
261 | ## Usage
262 |
263 | There are 2 APIs: stream and normal
264 |
265 | ### Stream
266 |
267 | The server will stream chunks of data until complete, the method `AsyncThrowingStream` which you can loop using For-Loop like so:
268 |
269 | ```swift
270 | Task {
271 | do {
272 | let stream = try await api.sendMessageStream(text: "What is ChatGPT?")
273 | for try await line in stream {
274 | print(line)
275 | }
276 | } catch {
277 | print(error.localizedDescription)
278 | }
279 | }
280 | ```
281 |
282 | ### Normal
283 | A normal HTTP request and response lifecycle. Server will send the complete text (it will take more time to response)
284 |
285 | ```swift
286 | Task {
287 | do {
288 | let response = try await api.sendMessage(text: "What is ChatGPT?")
289 | print(response)
290 | } catch {
291 | print(error.localizedDescription)
292 | }
293 | }
294 | ```
295 | """
296 | #else
297 | "SwiftUI is a user interface framework that allows developers to design and develop user interfaces for iOS, macOS, watchOS, and tvOS applications using Swift, a programming language developed by Apple Inc."
298 | #endif
299 | }
300 | }
301 |
302 |
303 |
--------------------------------------------------------------------------------
/Shared/ViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewModel.swift
3 | // XCAChatGPT
4 | //
5 | // Created by Alfian Losari on 02/02/23.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 | import AVKit
11 |
12 | class ViewModel: ObservableObject {
13 |
14 | @Published var isInteracting = false
15 | @Published var messages: [MessageRow] = []
16 | @Published var inputMessage: String = ""
17 | var task: Task?
18 |
19 | #if !os(watchOS)
20 | private var synthesizer: AVSpeechSynthesizer?
21 | #endif
22 |
23 | private var api: LLMClient
24 |
25 | var title: String {
26 | "XCA LLM Chatbot"
27 | }
28 |
29 | var navigationTitle: String {
30 | api.provider.navigationTitle
31 | }
32 |
33 | init(api: LLMClient, enableSpeech: Bool = false) {
34 | self.api = api
35 | #if !os(watchOS)
36 | if enableSpeech {
37 | synthesizer = .init()
38 | }
39 | #endif
40 | }
41 |
42 | func updateClient(_ client: LLMClient) {
43 | self.messages = []
44 | self.api = client
45 | }
46 |
47 | @MainActor
48 | func sendTapped() async {
49 | #if os(iOS)
50 | self.task = Task {
51 | let text = inputMessage
52 | inputMessage = ""
53 | if api.provider == .chatGPT {
54 | await sendAttributed(text: text)
55 | } else {
56 | await sendAttributedWithoutStream(text: text)
57 | }
58 | }
59 | #else
60 | let text = inputMessage
61 | inputMessage = ""
62 | if api.provider == .chatGPT {
63 | await send(text: text)
64 | } else {
65 | await sendWithoutStream(text: text)
66 | }
67 | #endif
68 | }
69 |
70 | @MainActor
71 | func clearMessages() {
72 | stopSpeaking()
73 | api.deleteHistoryList()
74 | withAnimation { [weak self] in
75 | self?.messages = []
76 | }
77 | }
78 |
79 | @MainActor
80 | func retry(message: MessageRow) async {
81 | #if os(iOS)
82 | self.task = Task {
83 | guard let index = messages.firstIndex(where: { $0.id == message.id }) else {
84 | return
85 | }
86 | self.messages.remove(at: index)
87 | if api.provider == .chatGPT {
88 | await sendAttributed(text: message.sendText)
89 | } else {
90 | await sendAttributedWithoutStream(text: message.sendText)
91 | }
92 | }
93 | #else
94 | guard let index = messages.firstIndex(where: { $0.id == message.id }) else {
95 | return
96 | }
97 | self.messages.remove(at: index)
98 | if api.provider == .chatGPT {
99 | await send(text: message.sendText)
100 | } else {
101 | await sendWithoutStream(text: message.sendText)
102 | }
103 | #endif
104 | }
105 |
106 | func cancelStreamingResponse() {
107 | self.task?.cancel()
108 | self.task = nil
109 | }
110 |
111 | #if os(iOS)
112 | @MainActor
113 | private func sendAttributed(text: String) async {
114 | isInteracting = true
115 | var streamText = ""
116 |
117 | var messageRow = MessageRow(
118 | isInteracting: true,
119 | sendImage: "profile",
120 | send: .rawText(text),
121 | responseImage: api.provider.imageName,
122 | response: .rawText(streamText),
123 | responseError: nil)
124 |
125 | do {
126 | let parsingTask = ResponseParsingTask()
127 | let attributedSend = await parsingTask.parse(text: text)
128 | try Task.checkCancellation()
129 | messageRow.send = .attributed(attributedSend)
130 |
131 | self.messages.append(messageRow)
132 |
133 | let parserThresholdTextCount = 64
134 | var currentTextCount = 0
135 | var currentOutput: AttributedOutput?
136 |
137 | let stream = try await api.sendMessageStream(text: text)
138 | for try await text in stream {
139 | streamText += text
140 | currentTextCount += text.count
141 |
142 | if currentTextCount >= parserThresholdTextCount || text.contains("```") {
143 | currentOutput = await parsingTask.parse(text: streamText)
144 | try Task.checkCancellation()
145 | currentTextCount = 0
146 | }
147 |
148 | if let currentOutput = currentOutput, !currentOutput.results.isEmpty {
149 | let suffixText = streamText.trimmingPrefix(currentOutput.string)
150 | var results = currentOutput.results
151 | let lastResult = results[results.count - 1]
152 | var lastAttrString = lastResult.attributedString
153 | if lastResult.isCodeBlock {
154 | lastAttrString.append(AttributedString(String(suffixText), attributes: .init([.font: UIFont.systemFont(ofSize: 12).apply(newTraits: .traitMonoSpace), .foregroundColor: UIColor.white])))
155 | } else {
156 | lastAttrString.append(AttributedString(String(suffixText)))
157 | }
158 | results[results.count - 1] = ParserResult(attributedString: lastAttrString, isCodeBlock: lastResult.isCodeBlock, codeBlockLanguage: lastResult.codeBlockLanguage)
159 | messageRow.response = .attributed(.init(string: streamText, results: results))
160 | } else {
161 | messageRow.response = .attributed(.init(string: streamText, results: [
162 | ParserResult(attributedString: AttributedString(stringLiteral: streamText), isCodeBlock: false, codeBlockLanguage: nil)
163 | ]))
164 | }
165 |
166 | self.messages[self.messages.count - 1] = messageRow
167 | if let currentString = currentOutput?.string, currentString != streamText {
168 | let output = await parsingTask.parse(text: streamText)
169 | try Task.checkCancellation()
170 | messageRow.response = .attributed(output)
171 | }
172 | }
173 | } catch is CancellationError {
174 | messageRow.responseError = "The response was cancelled"
175 | } catch {
176 | messageRow.responseError = error.localizedDescription
177 | }
178 |
179 | if messageRow.response == nil {
180 | messageRow.response = .rawText(streamText)
181 | }
182 |
183 | messageRow.isInteracting = false
184 | self.messages[self.messages.count - 1] = messageRow
185 | isInteracting = false
186 | speakLastResponse()
187 | }
188 |
189 | @MainActor
190 | private func sendAttributedWithoutStream(text: String) async {
191 | isInteracting = true
192 | var messageRow = MessageRow(
193 | isInteracting: true,
194 | sendImage: "profile",
195 | send: .rawText(text),
196 | responseImage: api.provider.imageName,
197 | response: .rawText(""),
198 | responseError: nil)
199 |
200 | self.messages.append(messageRow)
201 |
202 | do {
203 | let responseText = try await api.sendMessage(text)
204 | try Task.checkCancellation()
205 |
206 | let parsingTask = ResponseParsingTask()
207 | let output = await parsingTask.parse(text: responseText)
208 | try Task.checkCancellation()
209 |
210 | messageRow.response = .attributed(output)
211 |
212 | } catch {
213 | messageRow.responseError = error.localizedDescription
214 | }
215 |
216 | messageRow.isInteracting = false
217 | self.messages[self.messages.count - 1] = messageRow
218 | isInteracting = false
219 | speakLastResponse()
220 |
221 | }
222 | #endif
223 |
224 | @MainActor
225 | private func send(text: String) async {
226 | isInteracting = true
227 | var streamText = ""
228 | var messageRow = MessageRow(
229 | isInteracting: true,
230 | sendImage: "profile",
231 | send: .rawText(text),
232 | responseImage: api.provider.imageName,
233 | response: .rawText(streamText),
234 | responseError: nil)
235 |
236 | self.messages.append(messageRow)
237 |
238 | do {
239 | let stream = try await api.sendMessageStream(text: text)
240 | for try await text in stream {
241 | streamText += text
242 | messageRow.response = .rawText(streamText.trimmingCharacters(in: .whitespacesAndNewlines))
243 | self.messages[self.messages.count - 1] = messageRow
244 | }
245 | } catch {
246 | messageRow.responseError = error.localizedDescription
247 | }
248 |
249 | messageRow.isInteracting = false
250 | self.messages[self.messages.count - 1] = messageRow
251 | isInteracting = false
252 | speakLastResponse()
253 |
254 | }
255 |
256 | @MainActor
257 | private func sendWithoutStream(text: String) async {
258 | isInteracting = true
259 | var messageRow = MessageRow(
260 | isInteracting: true,
261 | sendImage: "profile",
262 | send: .rawText(text),
263 | responseImage: api.provider.imageName,
264 | response: .rawText(""),
265 | responseError: nil)
266 |
267 | self.messages.append(messageRow)
268 |
269 | do {
270 | let responseText = try await api.sendMessage(text)
271 | try Task.checkCancellation()
272 | messageRow.response = .rawText(responseText)
273 | } catch {
274 | messageRow.responseError = error.localizedDescription
275 | }
276 |
277 | messageRow.isInteracting = false
278 | self.messages[self.messages.count - 1] = messageRow
279 | isInteracting = false
280 | speakLastResponse()
281 | }
282 |
283 | func speakLastResponse() {
284 | #if !os(watchOS)
285 | guard let synthesizer, let responseText = self.messages.last?.responseText, !responseText.isEmpty else {
286 | return
287 | }
288 | stopSpeaking()
289 | let utterance = AVSpeechUtterance(string: responseText)
290 | utterance.voice = .init(language: "en-US")
291 | utterance.rate = 0.5
292 | utterance.pitchMultiplier = 0.8
293 | utterance.postUtteranceDelay = 0.2
294 | synthesizer.speak(utterance )
295 | #endif
296 | }
297 |
298 | func stopSpeaking() {
299 | #if !os(watchOS)
300 | synthesizer?.stopSpeaking(at: .immediate)
301 | #endif
302 | }
303 |
304 | }
305 |
306 |
307 |
308 |
--------------------------------------------------------------------------------
/Shared/MarkdownAttributedStringParser.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MarkdownAttributedStringParser.swift
3 | // XCAChatGPT
4 | //
5 | // Created by Alfian Losari on 19/04/23.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 | import Markdown
11 | import Highlighter
12 |
13 | /// Based on the source code from Christian Selig
14 | /// https://github.com/christianselig/Markdownosaur/blob/main/Sources/Markdownosaur/Markdownosaur.swift
15 |
16 | public struct MarkdownAttributedStringParser: MarkupVisitor {
17 | let baseFontSize: CGFloat = UIFont.preferredFont(forTextStyle: .body).pointSize
18 | let highlighter: Highlighter = {
19 | let highlighter = Highlighter()!
20 | highlighter.setTheme("stackoverflow-dark")
21 | return highlighter
22 | }()
23 |
24 | let newLineFontSize: CGFloat = 12
25 |
26 | public init() {}
27 |
28 | public mutating func attributedString(from document: Document) -> NSAttributedString {
29 | return visit(document)
30 | }
31 |
32 | mutating func parserResults(from document: Document) -> [ParserResult] {
33 | var results = [ParserResult]()
34 | var currentAttrString = NSMutableAttributedString()
35 |
36 | func appendCurrentAttrString() {
37 | if !currentAttrString.string.isEmpty {
38 | let currentAttrStringToAppend = (try? AttributedString(currentAttrString, including: \.uiKit)) ?? AttributedString(stringLiteral: currentAttrString.string)
39 | results.append(.init(attributedString: currentAttrStringToAppend, isCodeBlock: false, codeBlockLanguage: nil))
40 | }
41 | }
42 |
43 | document.children.forEach { markup in
44 | let attrString = visit(markup)
45 | if let codeBlock = markup as? CodeBlock {
46 | appendCurrentAttrString()
47 | let attrStringToAppend = (try? AttributedString(attrString, including: \.uiKit)) ?? AttributedString(stringLiteral: attrString.string)
48 | results.append(.init(attributedString: attrStringToAppend, isCodeBlock: true, codeBlockLanguage: codeBlock.language))
49 | currentAttrString = NSMutableAttributedString()
50 | } else {
51 | currentAttrString.append(attrString)
52 | }
53 | }
54 |
55 | appendCurrentAttrString()
56 | return results
57 | }
58 |
59 | mutating public func defaultVisit(_ markup: Markup) -> NSAttributedString {
60 | let result = NSMutableAttributedString()
61 |
62 | for child in markup.children {
63 | result.append(visit(child))
64 | }
65 |
66 | return result
67 | }
68 |
69 | mutating public func visitText(_ text: Text) -> NSAttributedString {
70 | return NSAttributedString(string: text.plainText, attributes: [.font: UIFont.systemFont(ofSize: baseFontSize, weight: .regular)])
71 | }
72 |
73 | mutating public func visitEmphasis(_ emphasis: Emphasis) -> NSAttributedString {
74 | let result = NSMutableAttributedString()
75 |
76 | for child in emphasis.children {
77 | result.append(visit(child))
78 | }
79 |
80 | result.applyEmphasis()
81 |
82 | return result
83 | }
84 |
85 | mutating public func visitStrong(_ strong: Strong) -> NSAttributedString {
86 | let result = NSMutableAttributedString()
87 |
88 | for child in strong.children {
89 | result.append(visit(child))
90 | }
91 |
92 | result.applyStrong()
93 |
94 | return result
95 | }
96 |
97 | mutating public func visitParagraph(_ paragraph: Paragraph) -> NSAttributedString {
98 | let result = NSMutableAttributedString()
99 |
100 | for child in paragraph.children {
101 | result.append(visit(child))
102 | }
103 |
104 | if paragraph.hasSuccessor {
105 | result.append(paragraph.isContainedInList ? .singleNewline(withFontSize: newLineFontSize) : .doubleNewline(withFontSize: newLineFontSize))
106 | }
107 |
108 | return result
109 | }
110 |
111 | mutating public func visitHeading(_ heading: Heading) -> NSAttributedString {
112 | let result = NSMutableAttributedString()
113 |
114 | for child in heading.children {
115 | result.append(visit(child))
116 | }
117 |
118 | result.applyHeading(withLevel: heading.level)
119 |
120 | if heading.hasSuccessor {
121 | result.append(.doubleNewline(withFontSize: newLineFontSize))
122 | }
123 |
124 | return result
125 | }
126 |
127 | mutating public func visitLink(_ link: Link) -> NSAttributedString {
128 | let result = NSMutableAttributedString()
129 |
130 | for child in link.children {
131 | result.append(visit(child))
132 | }
133 |
134 | let url = link.destination != nil ? URL(string: link.destination!) : nil
135 |
136 | result.applyLink(withURL: url)
137 |
138 | return result
139 | }
140 |
141 | mutating public func visitInlineCode(_ inlineCode: InlineCode) -> NSAttributedString {
142 | return NSAttributedString(string: inlineCode.code, attributes: [.font: UIFont.monospacedSystemFont(ofSize: baseFontSize - 1.0, weight: .regular), .foregroundColor: UIColor.systemPink])
143 | }
144 |
145 | public func visitCodeBlock(_ codeBlock: CodeBlock) -> NSAttributedString {
146 | let result = NSMutableAttributedString(attributedString: highlighter.highlight(codeBlock.code, as: codeBlock.language) ?? NSAttributedString(string: codeBlock.code))
147 |
148 | if codeBlock.hasSuccessor {
149 | result.append(.singleNewline(withFontSize: newLineFontSize))
150 | }
151 |
152 | return result
153 | }
154 |
155 | mutating public func visitStrikethrough(_ strikethrough: Strikethrough) -> NSAttributedString {
156 | let result = NSMutableAttributedString()
157 |
158 | for child in strikethrough.children {
159 | result.append(visit(child))
160 | }
161 |
162 | result.applyStrikethrough()
163 |
164 | return result
165 | }
166 |
167 | mutating public func visitUnorderedList(_ unorderedList: UnorderedList) -> NSAttributedString {
168 | let result = NSMutableAttributedString()
169 |
170 | let font = UIFont.systemFont(ofSize: baseFontSize, weight: .regular)
171 |
172 | for listItem in unorderedList.listItems {
173 | var listItemAttributes: [NSAttributedString.Key: Any] = [:]
174 |
175 | let listItemParagraphStyle = NSMutableParagraphStyle()
176 |
177 | let baseLeftMargin: CGFloat = 15.0
178 | let leftMarginOffset = baseLeftMargin + (20.0 * CGFloat(unorderedList.listDepth))
179 | let spacingFromIndex: CGFloat = 8.0
180 | let bulletWidth = ceil(NSAttributedString(string: "•", attributes: [.font: font]).size().width)
181 | let firstTabLocation = leftMarginOffset + bulletWidth
182 | let secondTabLocation = firstTabLocation + spacingFromIndex
183 |
184 | listItemParagraphStyle.tabStops = [
185 | NSTextTab(textAlignment: .right, location: firstTabLocation),
186 | NSTextTab(textAlignment: .left, location: secondTabLocation)
187 | ]
188 |
189 | listItemParagraphStyle.headIndent = secondTabLocation
190 |
191 | listItemAttributes[.paragraphStyle] = listItemParagraphStyle
192 | listItemAttributes[.font] = UIFont.systemFont(ofSize: baseFontSize, weight: .regular)
193 | listItemAttributes[.listDepth] = unorderedList.listDepth
194 |
195 | let listItemAttributedString = visit(listItem).mutableCopy() as! NSMutableAttributedString
196 | listItemAttributedString.insert(NSAttributedString(string: "\t•\t", attributes: listItemAttributes), at: 0)
197 |
198 | result.append(listItemAttributedString)
199 | }
200 |
201 | if unorderedList.hasSuccessor {
202 | result.append(.doubleNewline(withFontSize: newLineFontSize))
203 | }
204 |
205 | return result
206 | }
207 |
208 | mutating public func visitListItem(_ listItem: ListItem) -> NSAttributedString {
209 | let result = NSMutableAttributedString()
210 |
211 | for child in listItem.children {
212 | result.append(visit(child))
213 | }
214 |
215 | if listItem.hasSuccessor {
216 | result.append(.singleNewline(withFontSize: newLineFontSize))
217 | }
218 |
219 | return result
220 | }
221 |
222 | mutating public func visitOrderedList(_ orderedList: OrderedList) -> NSAttributedString {
223 | let result = NSMutableAttributedString()
224 |
225 | for (index, listItem) in orderedList.listItems.enumerated() {
226 | var listItemAttributes: [NSAttributedString.Key: Any] = [:]
227 |
228 | let font = UIFont.systemFont(ofSize: baseFontSize, weight: .regular)
229 | let numeralFont = UIFont.monospacedDigitSystemFont(ofSize: baseFontSize, weight: .regular)
230 |
231 | let listItemParagraphStyle = NSMutableParagraphStyle()
232 |
233 | // Implement a base amount to be spaced from the left side at all times to better visually differentiate it as a list
234 | let baseLeftMargin: CGFloat = 15.0
235 | let leftMarginOffset = baseLeftMargin + (20.0 * CGFloat(orderedList.listDepth))
236 |
237 | // Grab the highest number to be displayed and measure its width (yes normally some digits are wider than others but since we're using the numeral mono font all will be the same width in this case)
238 | let highestNumberInList = orderedList.childCount
239 | let numeralColumnWidth = ceil(NSAttributedString(string: "\(highestNumberInList).", attributes: [.font: numeralFont]).size().width)
240 |
241 | let spacingFromIndex: CGFloat = 8.0
242 | let firstTabLocation = leftMarginOffset + numeralColumnWidth
243 | let secondTabLocation = firstTabLocation + spacingFromIndex
244 |
245 | listItemParagraphStyle.tabStops = [
246 | NSTextTab(textAlignment: .right, location: firstTabLocation),
247 | NSTextTab(textAlignment: .left, location: secondTabLocation)
248 | ]
249 |
250 | listItemParagraphStyle.headIndent = secondTabLocation
251 |
252 | listItemAttributes[.paragraphStyle] = listItemParagraphStyle
253 | listItemAttributes[.font] = font
254 | listItemAttributes[.listDepth] = orderedList.listDepth
255 |
256 | let listItemAttributedString = visit(listItem).mutableCopy() as! NSMutableAttributedString
257 |
258 | // Same as the normal list attributes, but for prettiness in formatting we want to use the cool monospaced numeral font
259 | var numberAttributes = listItemAttributes
260 | numberAttributes[.font] = numeralFont
261 |
262 | let numberAttributedString = NSAttributedString(string: "\t\(index + 1).\t", attributes: numberAttributes)
263 | listItemAttributedString.insert(numberAttributedString, at: 0)
264 |
265 | result.append(listItemAttributedString)
266 | }
267 |
268 | if orderedList.hasSuccessor {
269 | result.append(orderedList.isContainedInList ? .singleNewline(withFontSize: newLineFontSize) : .doubleNewline(withFontSize: newLineFontSize))
270 | }
271 |
272 | return result
273 | }
274 |
275 | mutating public func visitBlockQuote(_ blockQuote: BlockQuote) -> NSAttributedString {
276 | let result = NSMutableAttributedString()
277 |
278 | for child in blockQuote.children {
279 | var quoteAttributes: [NSAttributedString.Key: Any] = [:]
280 |
281 | let quoteParagraphStyle = NSMutableParagraphStyle()
282 |
283 | let baseLeftMargin: CGFloat = 15.0
284 | let leftMarginOffset = baseLeftMargin + (20.0 * CGFloat(blockQuote.quoteDepth))
285 |
286 | quoteParagraphStyle.tabStops = [NSTextTab(textAlignment: .left, location: leftMarginOffset)]
287 |
288 | quoteParagraphStyle.headIndent = leftMarginOffset
289 |
290 | quoteAttributes[.paragraphStyle] = quoteParagraphStyle
291 | quoteAttributes[.font] = UIFont.systemFont(ofSize: baseFontSize, weight: .regular)
292 | quoteAttributes[.listDepth] = blockQuote.quoteDepth
293 |
294 | let quoteAttributedString = visit(child).mutableCopy() as! NSMutableAttributedString
295 | quoteAttributedString.insert(NSAttributedString(string: "\t", attributes: quoteAttributes), at: 0)
296 |
297 | quoteAttributedString.addAttribute(.foregroundColor, value: UIColor.systemGray)
298 |
299 | result.append(quoteAttributedString)
300 | }
301 |
302 | if blockQuote.hasSuccessor {
303 | result.append(.doubleNewline(withFontSize: newLineFontSize))
304 | }
305 |
306 | return result
307 | }
308 | }
309 |
310 | // MARK: - Extensions Land
311 |
312 | extension NSMutableAttributedString {
313 | func applyEmphasis() {
314 | enumerateAttribute(.font, in: NSRange(location: 0, length: length), options: []) { value, range, stop in
315 | guard let font = value as? UIFont else { return }
316 |
317 | let newFont = font.apply(newTraits: .traitItalic)
318 | addAttribute(.font, value: newFont, range: range)
319 | }
320 | }
321 |
322 | func applyStrong() {
323 | enumerateAttribute(.font, in: NSRange(location: 0, length: length), options: []) { value, range, stop in
324 | guard let font = value as? UIFont else { return }
325 |
326 | let newFont = font.apply(newTraits: .traitBold)
327 | addAttribute(.font, value: newFont, range: range)
328 | }
329 | }
330 |
331 | func applyLink(withURL url: URL?) {
332 | addAttribute(.foregroundColor, value: UIColor.systemBlue)
333 |
334 | if let url = url {
335 | addAttribute(.link, value: url)
336 | }
337 | }
338 |
339 | func applyBlockquote() {
340 | addAttribute(.foregroundColor, value: UIColor.systemGray)
341 | }
342 |
343 | func applyHeading(withLevel headingLevel: Int) {
344 | enumerateAttribute(.font, in: NSRange(location: 0, length: length), options: []) { value, range, stop in
345 | guard let font = value as? UIFont else { return }
346 |
347 | let newFont = font.apply(newTraits: .traitBold, newPointSize: 28.0 - CGFloat(headingLevel * 2))
348 | addAttribute(.font, value: newFont, range: range)
349 | }
350 | }
351 |
352 | func applyStrikethrough() {
353 | addAttribute(.strikethroughStyle, value: NSUnderlineStyle.single.rawValue)
354 | }
355 | }
356 |
357 | extension UIFont {
358 | func apply(newTraits: UIFontDescriptor.SymbolicTraits, newPointSize: CGFloat? = nil) -> UIFont {
359 | var existingTraits = fontDescriptor.symbolicTraits
360 | existingTraits.insert(newTraits)
361 |
362 | guard let newFontDescriptor = fontDescriptor.withSymbolicTraits(existingTraits) else { return self }
363 | return UIFont(descriptor: newFontDescriptor, size: newPointSize ?? pointSize)
364 | }
365 | }
366 |
367 | extension ListItemContainer {
368 | /// Depth of the list if nested within others. Index starts at 0.
369 | var listDepth: Int {
370 | var index = 0
371 |
372 | var currentElement = parent
373 |
374 | while currentElement != nil {
375 | if currentElement is ListItemContainer {
376 | index += 1
377 | }
378 |
379 | currentElement = currentElement?.parent
380 | }
381 |
382 | return index
383 | }
384 | }
385 |
386 | extension BlockQuote {
387 | /// Depth of the quote if nested within others. Index starts at 0.
388 | var quoteDepth: Int {
389 | var index = 0
390 |
391 | var currentElement = parent
392 |
393 | while currentElement != nil {
394 | if currentElement is BlockQuote {
395 | index += 1
396 | }
397 |
398 | currentElement = currentElement?.parent
399 | }
400 |
401 | return index
402 | }
403 | }
404 |
405 | extension NSAttributedString.Key {
406 | static let listDepth = NSAttributedString.Key("ListDepth")
407 | static let quoteDepth = NSAttributedString.Key("QuoteDepth")
408 | }
409 |
410 | extension NSMutableAttributedString {
411 | func addAttribute(_ name: NSAttributedString.Key, value: Any) {
412 | addAttribute(name, value: value, range: NSRange(location: 0, length: length))
413 | }
414 |
415 | func addAttributes(_ attrs: [NSAttributedString.Key : Any]) {
416 | addAttributes(attrs, range: NSRange(location: 0, length: length))
417 | }
418 | }
419 |
420 | extension Markup {
421 | /// Returns true if this element has sibling elements after it.
422 | var hasSuccessor: Bool {
423 | guard let childCount = parent?.childCount else { return false }
424 | return indexInParent < childCount - 1
425 | }
426 |
427 | var isContainedInList: Bool {
428 | var currentElement = parent
429 |
430 | while currentElement != nil {
431 | if currentElement is ListItemContainer {
432 | return true
433 | }
434 |
435 | currentElement = currentElement?.parent
436 | }
437 |
438 | return false
439 | }
440 | }
441 |
442 | extension NSAttributedString {
443 | static func singleNewline(withFontSize fontSize: CGFloat) -> NSAttributedString {
444 | return NSAttributedString(string: "\n", attributes: [.font: UIFont.systemFont(ofSize: fontSize, weight: .regular)])
445 | }
446 |
447 | static func doubleNewline(withFontSize fontSize: CGFloat) -> NSAttributedString {
448 | return NSAttributedString(string: "\n\n", attributes: [.font: UIFont.systemFont(ofSize: fontSize, weight: .regular)])
449 | }
450 | }
451 |
452 |
--------------------------------------------------------------------------------
/XCAChatGPT.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 56;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 8B057585298E52C000A56C9A /* XCAChatGPTMacApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B057584298E52C000A56C9A /* XCAChatGPTMacApp.swift */; };
11 | 8B057589298E52C000A56C9A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8B057588298E52C000A56C9A /* Assets.xcassets */; };
12 | 8B05758C298E52C000A56C9A /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8B05758B298E52C000A56C9A /* Preview Assets.xcassets */; };
13 | 8B057592298E52D900A56C9A /* DotLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C017298ADF4E0079AF26 /* DotLoadingView.swift */; };
14 | 8B057593298E52D900A56C9A /* ChatGPTAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C011298AD0CE0079AF26 /* ChatGPTAPI.swift */; };
15 | 8B057594298E52D900A56C9A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C005298AD09E0079AF26 /* ContentView.swift */; };
16 | 8B057595298E52D900A56C9A /* MessageRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C019298ADF7F0079AF26 /* MessageRowView.swift */; };
17 | 8B057596298E52DD00A56C9A /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C013298ADC560079AF26 /* ViewModel.swift */; };
18 | 8B057597298E52DD00A56C9A /* MessageRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C015298ADC9D0079AF26 /* MessageRow.swift */; };
19 | 8B0575F1298FA9D800A56C9A /* XCAChatGPTWatch Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 8B0575F0298FA9D800A56C9A /* XCAChatGPTWatch Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
20 | 8B0575F6298FA9D800A56C9A /* XCAChatGPTWatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B0575F5298FA9D800A56C9A /* XCAChatGPTWatchApp.swift */; };
21 | 8B0575FA298FA9DA00A56C9A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8B0575F9298FA9DA00A56C9A /* Assets.xcassets */; };
22 | 8B0575FD298FA9DA00A56C9A /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8B0575FC298FA9DA00A56C9A /* Preview Assets.xcassets */; };
23 | 8B057605298FA9E900A56C9A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C005298AD09E0079AF26 /* ContentView.swift */; };
24 | 8B057606298FA9E900A56C9A /* MessageRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C019298ADF7F0079AF26 /* MessageRowView.swift */; };
25 | 8B057607298FA9E900A56C9A /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C013298ADC560079AF26 /* ViewModel.swift */; };
26 | 8B057608298FA9E900A56C9A /* DotLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C017298ADF4E0079AF26 /* DotLoadingView.swift */; };
27 | 8B057609298FA9E900A56C9A /* MessageRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C015298ADC9D0079AF26 /* MessageRow.swift */; };
28 | 8B05760A298FA9E900A56C9A /* ChatGPTAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C011298AD0CE0079AF26 /* ChatGPTAPI.swift */; };
29 | 8B057614298FBDB600A56C9A /* XCAChatGPTTVApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B057613298FBDB600A56C9A /* XCAChatGPTTVApp.swift */; };
30 | 8B057618298FBDB700A56C9A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8B057617298FBDB700A56C9A /* Assets.xcassets */; };
31 | 8B05761B298FBDB700A56C9A /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8B05761A298FBDB700A56C9A /* Preview Assets.xcassets */; };
32 | 8B05761F298FBE0400A56C9A /* ChatGPTAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C011298AD0CE0079AF26 /* ChatGPTAPI.swift */; };
33 | 8B057620298FBE0400A56C9A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C005298AD09E0079AF26 /* ContentView.swift */; };
34 | 8B057621298FBE0400A56C9A /* MessageRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C015298ADC9D0079AF26 /* MessageRow.swift */; };
35 | 8B057622298FBE0400A56C9A /* MessageRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C019298ADF7F0079AF26 /* MessageRowView.swift */; };
36 | 8B057623298FBE0400A56C9A /* DotLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C017298ADF4E0079AF26 /* DotLoadingView.swift */; };
37 | 8B057624298FBE0400A56C9A /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C013298ADC560079AF26 /* ViewModel.swift */; };
38 | 8B05764829909A9200A56C9A /* ScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B05764729909A9200A56C9A /* ScrollView.swift */; };
39 | 8B612E2529D68CC9008DF5AF /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B612E2229D68CC9008DF5AF /* TextView.swift */; };
40 | 8B612E2629D68CC9008DF5AF /* TokenizerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B612E2329D68CC9008DF5AF /* TokenizerView.swift */; };
41 | 8B612E2729D68CC9008DF5AF /* TokenizerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B612E2429D68CC9008DF5AF /* TokenizerViewModel.swift */; };
42 | 8B612E2A29D68CE3008DF5AF /* GPTEncoder in Frameworks */ = {isa = PBXBuildFile; productRef = 8B612E2929D68CE3008DF5AF /* GPTEncoder */; };
43 | 8B75A1B12A2CB7C600E8810E /* GoogleGenerativeAI in Frameworks */ = {isa = PBXBuildFile; productRef = 8B75A1B02A2CB7C600E8810E /* GoogleGenerativeAI */; };
44 | 8B75A1B42A2CB7D200E8810E /* GoogleGenerativeAI in Frameworks */ = {isa = PBXBuildFile; productRef = 8B75A1B32A2CB7D200E8810E /* GoogleGenerativeAI */; };
45 | 8B75A1B62A2CB7D500E8810E /* GoogleGenerativeAI in Frameworks */ = {isa = PBXBuildFile; productRef = 8B75A1B52A2CB7D500E8810E /* GoogleGenerativeAI */; };
46 | 8B75A1B82A2CB7DA00E8810E /* GoogleGenerativeAI in Frameworks */ = {isa = PBXBuildFile; productRef = 8B75A1B72A2CB7DA00E8810E /* GoogleGenerativeAI */; };
47 | 8B75A1BE2A2CB83900E8810E /* LLMClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B75A1BA2A2CB83900E8810E /* LLMClient.swift */; };
48 | 8B75A1BF2A2CB83900E8810E /* LLMClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B75A1BA2A2CB83900E8810E /* LLMClient.swift */; };
49 | 8B75A1C02A2CB83900E8810E /* LLMClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B75A1BA2A2CB83900E8810E /* LLMClient.swift */; };
50 | 8B75A1C12A2CB83900E8810E /* LLMClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B75A1BA2A2CB83900E8810E /* LLMClient.swift */; };
51 | 8B75A1C22A2CB83900E8810E /* PaLMChatAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B75A1BB2A2CB83900E8810E /* PaLMChatAPI.swift */; };
52 | 8B75A1C32A2CB83900E8810E /* PaLMChatAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B75A1BB2A2CB83900E8810E /* PaLMChatAPI.swift */; };
53 | 8B75A1C42A2CB83900E8810E /* PaLMChatAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B75A1BB2A2CB83900E8810E /* PaLMChatAPI.swift */; };
54 | 8B75A1C52A2CB83900E8810E /* PaLMChatAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B75A1BB2A2CB83900E8810E /* PaLMChatAPI.swift */; };
55 | 8B75A1C62A2CB83900E8810E /* LLMConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B75A1BC2A2CB83900E8810E /* LLMConfig.swift */; };
56 | 8B75A1C72A2CB83900E8810E /* LLMConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B75A1BC2A2CB83900E8810E /* LLMConfig.swift */; };
57 | 8B75A1C82A2CB83900E8810E /* LLMConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B75A1BC2A2CB83900E8810E /* LLMConfig.swift */; };
58 | 8B75A1C92A2CB83900E8810E /* LLMConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B75A1BC2A2CB83900E8810E /* LLMConfig.swift */; };
59 | 8B75A1CA2A2CB83900E8810E /* LLMProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B75A1BD2A2CB83900E8810E /* LLMProvider.swift */; };
60 | 8B75A1CB2A2CB83900E8810E /* LLMProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B75A1BD2A2CB83900E8810E /* LLMProvider.swift */; };
61 | 8B75A1CC2A2CB83900E8810E /* LLMProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B75A1BD2A2CB83900E8810E /* LLMProvider.swift */; };
62 | 8B75A1CD2A2CB83900E8810E /* LLMProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B75A1BD2A2CB83900E8810E /* LLMProvider.swift */; };
63 | 8B75A1CF2A2CB8DC00E8810E /* LLMConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B75A1CE2A2CB8DC00E8810E /* LLMConfigView.swift */; };
64 | 8B75A1D02A2CB8DC00E8810E /* LLMConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B75A1CE2A2CB8DC00E8810E /* LLMConfigView.swift */; };
65 | 8B75A1D12A2CB8DC00E8810E /* LLMConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B75A1CE2A2CB8DC00E8810E /* LLMConfigView.swift */; };
66 | 8B75A1D22A2CB8DC00E8810E /* LLMConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B75A1CE2A2CB8DC00E8810E /* LLMConfigView.swift */; };
67 | 8B82463429B1F49F0069B8F7 /* ChatGPTAPIModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B82463329B1F49F0069B8F7 /* ChatGPTAPIModels.swift */; };
68 | 8B82463529B1F49F0069B8F7 /* ChatGPTAPIModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B82463329B1F49F0069B8F7 /* ChatGPTAPIModels.swift */; };
69 | 8B82463629B1F49F0069B8F7 /* ChatGPTAPIModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B82463329B1F49F0069B8F7 /* ChatGPTAPIModels.swift */; };
70 | 8B82463729B1F49F0069B8F7 /* ChatGPTAPIModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B82463329B1F49F0069B8F7 /* ChatGPTAPIModels.swift */; };
71 | 8B91C004298AD09E0079AF26 /* XCAChatGPTApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C003298AD09E0079AF26 /* XCAChatGPTApp.swift */; };
72 | 8B91C006298AD09E0079AF26 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C005298AD09E0079AF26 /* ContentView.swift */; };
73 | 8B91C008298AD09F0079AF26 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8B91C007298AD09F0079AF26 /* Assets.xcassets */; };
74 | 8B91C00B298AD09F0079AF26 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8B91C00A298AD09F0079AF26 /* Preview Assets.xcassets */; };
75 | 8B91C012298AD0CE0079AF26 /* ChatGPTAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C011298AD0CE0079AF26 /* ChatGPTAPI.swift */; };
76 | 8B91C014298ADC560079AF26 /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C013298ADC560079AF26 /* ViewModel.swift */; };
77 | 8B91C016298ADC9D0079AF26 /* MessageRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C015298ADC9D0079AF26 /* MessageRow.swift */; };
78 | 8B91C018298ADF4E0079AF26 /* DotLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C017298ADF4E0079AF26 /* DotLoadingView.swift */; };
79 | 8B91C01A298ADF7F0079AF26 /* MessageRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B91C019298ADF7F0079AF26 /* MessageRowView.swift */; };
80 | 8BC0F8D029F157C800D6B492 /* Markdown in Frameworks */ = {isa = PBXBuildFile; productRef = 8BC0F8CF29F157C800D6B492 /* Markdown */; };
81 | 8BC0F8D329F157E600D6B492 /* Highlighter in Frameworks */ = {isa = PBXBuildFile; productRef = 8BC0F8D229F157E600D6B492 /* Highlighter */; };
82 | 8BC0F8D529F1581800D6B492 /* MarkdownAttributedStringParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BC0F8D429F1581800D6B492 /* MarkdownAttributedStringParser.swift */; };
83 | 8BC0F8D729F1583300D6B492 /* ParserResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BC0F8D629F1583300D6B492 /* ParserResult.swift */; };
84 | 8BC0F8D829F1583300D6B492 /* ParserResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BC0F8D629F1583300D6B492 /* ParserResult.swift */; };
85 | 8BC0F8D929F1583300D6B492 /* ParserResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BC0F8D629F1583300D6B492 /* ParserResult.swift */; };
86 | 8BC0F8DA29F1583300D6B492 /* ParserResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BC0F8D629F1583300D6B492 /* ParserResult.swift */; };
87 | 8BC0F8DC29F1587600D6B492 /* ResponseParsingTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BC0F8DB29F1587600D6B492 /* ResponseParsingTask.swift */; };
88 | 8BC0F8DE29F1589600D6B492 /* CodeBlockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8BC0F8DD29F1589600D6B492 /* CodeBlockView.swift */; };
89 | /* End PBXBuildFile section */
90 |
91 | /* Begin PBXContainerItemProxy section */
92 | 8B0575F2298FA9D800A56C9A /* PBXContainerItemProxy */ = {
93 | isa = PBXContainerItemProxy;
94 | containerPortal = 8B91BFF8298AD09E0079AF26 /* Project object */;
95 | proxyType = 1;
96 | remoteGlobalIDString = 8B0575EF298FA9D800A56C9A;
97 | remoteInfo = "XCAChatGPTWatch Watch App";
98 | };
99 | /* End PBXContainerItemProxy section */
100 |
101 | /* Begin PBXCopyFilesBuildPhase section */
102 | 8B057600298FA9DA00A56C9A /* Embed Watch Content */ = {
103 | isa = PBXCopyFilesBuildPhase;
104 | buildActionMask = 2147483647;
105 | dstPath = "$(CONTENTS_FOLDER_PATH)/Watch";
106 | dstSubfolderSpec = 16;
107 | files = (
108 | 8B0575F1298FA9D800A56C9A /* XCAChatGPTWatch Watch App.app in Embed Watch Content */,
109 | );
110 | name = "Embed Watch Content";
111 | runOnlyForDeploymentPostprocessing = 0;
112 | };
113 | /* End PBXCopyFilesBuildPhase section */
114 |
115 | /* Begin PBXFileReference section */
116 | 8B057582298E52C000A56C9A /* XCAChatGPTMac.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XCAChatGPTMac.app; sourceTree = BUILT_PRODUCTS_DIR; };
117 | 8B057584298E52C000A56C9A /* XCAChatGPTMacApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCAChatGPTMacApp.swift; sourceTree = ""; };
118 | 8B057588298E52C000A56C9A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
119 | 8B05758B298E52C000A56C9A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
120 | 8B05758D298E52C000A56C9A /* XCAChatGPTMac.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = XCAChatGPTMac.entitlements; sourceTree = ""; };
121 | 8B0575EB298FA9D800A56C9A /* XCAChatGPTWatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XCAChatGPTWatch.app; sourceTree = BUILT_PRODUCTS_DIR; };
122 | 8B0575F0298FA9D800A56C9A /* XCAChatGPTWatch Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "XCAChatGPTWatch Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; };
123 | 8B0575F5298FA9D800A56C9A /* XCAChatGPTWatchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCAChatGPTWatchApp.swift; sourceTree = ""; };
124 | 8B0575F9298FA9DA00A56C9A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
125 | 8B0575FC298FA9DA00A56C9A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
126 | 8B057611298FBDB600A56C9A /* XCAChatGPTTV.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XCAChatGPTTV.app; sourceTree = BUILT_PRODUCTS_DIR; };
127 | 8B057613298FBDB600A56C9A /* XCAChatGPTTVApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCAChatGPTTVApp.swift; sourceTree = ""; };
128 | 8B057617298FBDB700A56C9A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
129 | 8B05761A298FBDB700A56C9A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
130 | 8B05764729909A9200A56C9A /* ScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollView.swift; sourceTree = ""; };
131 | 8B612E2229D68CC9008DF5AF /* TextView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = ""; };
132 | 8B612E2329D68CC9008DF5AF /* TokenizerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenizerView.swift; sourceTree = ""; };
133 | 8B612E2429D68CC9008DF5AF /* TokenizerViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenizerViewModel.swift; sourceTree = ""; };
134 | 8B75A1BA2A2CB83900E8810E /* LLMClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LLMClient.swift; sourceTree = ""; };
135 | 8B75A1BB2A2CB83900E8810E /* PaLMChatAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaLMChatAPI.swift; sourceTree = ""; };
136 | 8B75A1BC2A2CB83900E8810E /* LLMConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LLMConfig.swift; sourceTree = ""; };
137 | 8B75A1BD2A2CB83900E8810E /* LLMProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LLMProvider.swift; sourceTree = ""; };
138 | 8B75A1CE2A2CB8DC00E8810E /* LLMConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LLMConfigView.swift; sourceTree = ""; };
139 | 8B82463329B1F49F0069B8F7 /* ChatGPTAPIModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatGPTAPIModels.swift; sourceTree = ""; };
140 | 8B91C000298AD09E0079AF26 /* XCAChatGPT.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XCAChatGPT.app; sourceTree = BUILT_PRODUCTS_DIR; };
141 | 8B91C003298AD09E0079AF26 /* XCAChatGPTApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCAChatGPTApp.swift; sourceTree = ""; };
142 | 8B91C005298AD09E0079AF26 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
143 | 8B91C007298AD09F0079AF26 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
144 | 8B91C00A298AD09F0079AF26 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
145 | 8B91C011298AD0CE0079AF26 /* ChatGPTAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatGPTAPI.swift; sourceTree = ""; };
146 | 8B91C013298ADC560079AF26 /* ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = ""; };
147 | 8B91C015298ADC9D0079AF26 /* MessageRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRow.swift; sourceTree = ""; };
148 | 8B91C017298ADF4E0079AF26 /* DotLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DotLoadingView.swift; sourceTree = ""; };
149 | 8B91C019298ADF7F0079AF26 /* MessageRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRowView.swift; sourceTree = ""; };
150 | 8BC0F8D429F1581800D6B492 /* MarkdownAttributedStringParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownAttributedStringParser.swift; sourceTree = ""; };
151 | 8BC0F8D629F1583300D6B492 /* ParserResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParserResult.swift; sourceTree = ""; };
152 | 8BC0F8DB29F1587600D6B492 /* ResponseParsingTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponseParsingTask.swift; sourceTree = ""; };
153 | 8BC0F8DD29F1589600D6B492 /* CodeBlockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeBlockView.swift; sourceTree = ""; };
154 | /* End PBXFileReference section */
155 |
156 | /* Begin PBXFrameworksBuildPhase section */
157 | 8B05757F298E52C000A56C9A /* Frameworks */ = {
158 | isa = PBXFrameworksBuildPhase;
159 | buildActionMask = 2147483647;
160 | files = (
161 | 8B75A1B42A2CB7D200E8810E /* GoogleGenerativeAI in Frameworks */,
162 | );
163 | runOnlyForDeploymentPostprocessing = 0;
164 | };
165 | 8B0575ED298FA9D800A56C9A /* Frameworks */ = {
166 | isa = PBXFrameworksBuildPhase;
167 | buildActionMask = 2147483647;
168 | files = (
169 | 8B75A1B62A2CB7D500E8810E /* GoogleGenerativeAI in Frameworks */,
170 | );
171 | runOnlyForDeploymentPostprocessing = 0;
172 | };
173 | 8B05760E298FBDB600A56C9A /* Frameworks */ = {
174 | isa = PBXFrameworksBuildPhase;
175 | buildActionMask = 2147483647;
176 | files = (
177 | 8B75A1B82A2CB7DA00E8810E /* GoogleGenerativeAI in Frameworks */,
178 | );
179 | runOnlyForDeploymentPostprocessing = 0;
180 | };
181 | 8B91BFFD298AD09E0079AF26 /* Frameworks */ = {
182 | isa = PBXFrameworksBuildPhase;
183 | buildActionMask = 2147483647;
184 | files = (
185 | 8B612E2A29D68CE3008DF5AF /* GPTEncoder in Frameworks */,
186 | 8BC0F8D329F157E600D6B492 /* Highlighter in Frameworks */,
187 | 8BC0F8D029F157C800D6B492 /* Markdown in Frameworks */,
188 | 8B75A1B12A2CB7C600E8810E /* GoogleGenerativeAI in Frameworks */,
189 | );
190 | runOnlyForDeploymentPostprocessing = 0;
191 | };
192 | /* End PBXFrameworksBuildPhase section */
193 |
194 | /* Begin PBXGroup section */
195 | 8B057583298E52C000A56C9A /* XCAChatGPTMac */ = {
196 | isa = PBXGroup;
197 | children = (
198 | 8B057584298E52C000A56C9A /* XCAChatGPTMacApp.swift */,
199 | 8B057588298E52C000A56C9A /* Assets.xcassets */,
200 | 8B05758D298E52C000A56C9A /* XCAChatGPTMac.entitlements */,
201 | 8B05758A298E52C000A56C9A /* Preview Content */,
202 | );
203 | path = XCAChatGPTMac;
204 | sourceTree = "";
205 | };
206 | 8B05758A298E52C000A56C9A /* Preview Content */ = {
207 | isa = PBXGroup;
208 | children = (
209 | 8B05758B298E52C000A56C9A /* Preview Assets.xcassets */,
210 | );
211 | path = "Preview Content";
212 | sourceTree = "";
213 | };
214 | 8B057591298E52CB00A56C9A /* Shared */ = {
215 | isa = PBXGroup;
216 | children = (
217 | 8B75A1B92A2CB81400E8810E /* LLM Provider */,
218 | 8B91C011298AD0CE0079AF26 /* ChatGPTAPI.swift */,
219 | 8B82463329B1F49F0069B8F7 /* ChatGPTAPIModels.swift */,
220 | 8B91C005298AD09E0079AF26 /* ContentView.swift */,
221 | 8B91C017298ADF4E0079AF26 /* DotLoadingView.swift */,
222 | 8B91C019298ADF7F0079AF26 /* MessageRowView.swift */,
223 | 8B91C013298ADC560079AF26 /* ViewModel.swift */,
224 | 8B91C015298ADC9D0079AF26 /* MessageRow.swift */,
225 | 8BC0F8D429F1581800D6B492 /* MarkdownAttributedStringParser.swift */,
226 | 8BC0F8D629F1583300D6B492 /* ParserResult.swift */,
227 | 8BC0F8DB29F1587600D6B492 /* ResponseParsingTask.swift */,
228 | 8BC0F8DD29F1589600D6B492 /* CodeBlockView.swift */,
229 | 8B75A1CE2A2CB8DC00E8810E /* LLMConfigView.swift */,
230 | );
231 | path = Shared;
232 | sourceTree = "";
233 | };
234 | 8B0575F4298FA9D800A56C9A /* XCAChatGPTWatch Watch App */ = {
235 | isa = PBXGroup;
236 | children = (
237 | 8B0575F5298FA9D800A56C9A /* XCAChatGPTWatchApp.swift */,
238 | 8B0575F9298FA9DA00A56C9A /* Assets.xcassets */,
239 | 8B0575FB298FA9DA00A56C9A /* Preview Content */,
240 | );
241 | path = "XCAChatGPTWatch Watch App";
242 | sourceTree = "";
243 | };
244 | 8B0575FB298FA9DA00A56C9A /* Preview Content */ = {
245 | isa = PBXGroup;
246 | children = (
247 | 8B0575FC298FA9DA00A56C9A /* Preview Assets.xcassets */,
248 | );
249 | path = "Preview Content";
250 | sourceTree = "";
251 | };
252 | 8B057612298FBDB600A56C9A /* XCAChatGPTTV */ = {
253 | isa = PBXGroup;
254 | children = (
255 | 8B057613298FBDB600A56C9A /* XCAChatGPTTVApp.swift */,
256 | 8B057617298FBDB700A56C9A /* Assets.xcassets */,
257 | 8B057619298FBDB700A56C9A /* Preview Content */,
258 | 8B05764729909A9200A56C9A /* ScrollView.swift */,
259 | );
260 | path = XCAChatGPTTV;
261 | sourceTree = "";
262 | };
263 | 8B057619298FBDB700A56C9A /* Preview Content */ = {
264 | isa = PBXGroup;
265 | children = (
266 | 8B05761A298FBDB700A56C9A /* Preview Assets.xcassets */,
267 | );
268 | path = "Preview Content";
269 | sourceTree = "";
270 | };
271 | 8B75A1B22A2CB7CE00E8810E /* Frameworks */ = {
272 | isa = PBXGroup;
273 | children = (
274 | );
275 | name = Frameworks;
276 | sourceTree = "";
277 | };
278 | 8B75A1B92A2CB81400E8810E /* LLM Provider */ = {
279 | isa = PBXGroup;
280 | children = (
281 | 8B75A1BA2A2CB83900E8810E /* LLMClient.swift */,
282 | 8B75A1BC2A2CB83900E8810E /* LLMConfig.swift */,
283 | 8B75A1BD2A2CB83900E8810E /* LLMProvider.swift */,
284 | 8B75A1BB2A2CB83900E8810E /* PaLMChatAPI.swift */,
285 | );
286 | path = "LLM Provider";
287 | sourceTree = "";
288 | };
289 | 8B91BFF7298AD09E0079AF26 = {
290 | isa = PBXGroup;
291 | children = (
292 | 8B057591298E52CB00A56C9A /* Shared */,
293 | 8B91C002298AD09E0079AF26 /* XCAChatGPT */,
294 | 8B057583298E52C000A56C9A /* XCAChatGPTMac */,
295 | 8B0575F4298FA9D800A56C9A /* XCAChatGPTWatch Watch App */,
296 | 8B057612298FBDB600A56C9A /* XCAChatGPTTV */,
297 | 8B91C001298AD09E0079AF26 /* Products */,
298 | 8B75A1B22A2CB7CE00E8810E /* Frameworks */,
299 | );
300 | sourceTree = "";
301 | };
302 | 8B91C001298AD09E0079AF26 /* Products */ = {
303 | isa = PBXGroup;
304 | children = (
305 | 8B91C000298AD09E0079AF26 /* XCAChatGPT.app */,
306 | 8B057582298E52C000A56C9A /* XCAChatGPTMac.app */,
307 | 8B0575EB298FA9D800A56C9A /* XCAChatGPTWatch.app */,
308 | 8B0575F0298FA9D800A56C9A /* XCAChatGPTWatch Watch App.app */,
309 | 8B057611298FBDB600A56C9A /* XCAChatGPTTV.app */,
310 | );
311 | name = Products;
312 | sourceTree = "";
313 | };
314 | 8B91C002298AD09E0079AF26 /* XCAChatGPT */ = {
315 | isa = PBXGroup;
316 | children = (
317 | 8B91C003298AD09E0079AF26 /* XCAChatGPTApp.swift */,
318 | 8B612E2229D68CC9008DF5AF /* TextView.swift */,
319 | 8B612E2329D68CC9008DF5AF /* TokenizerView.swift */,
320 | 8B612E2429D68CC9008DF5AF /* TokenizerViewModel.swift */,
321 | 8B91C007298AD09F0079AF26 /* Assets.xcassets */,
322 | 8B91C009298AD09F0079AF26 /* Preview Content */,
323 | );
324 | path = XCAChatGPT;
325 | sourceTree = "";
326 | };
327 | 8B91C009298AD09F0079AF26 /* Preview Content */ = {
328 | isa = PBXGroup;
329 | children = (
330 | 8B91C00A298AD09F0079AF26 /* Preview Assets.xcassets */,
331 | );
332 | path = "Preview Content";
333 | sourceTree = "";
334 | };
335 | /* End PBXGroup section */
336 |
337 | /* Begin PBXNativeTarget section */
338 | 8B057581298E52C000A56C9A /* XCAChatGPTMac */ = {
339 | isa = PBXNativeTarget;
340 | buildConfigurationList = 8B057590298E52C000A56C9A /* Build configuration list for PBXNativeTarget "XCAChatGPTMac" */;
341 | buildPhases = (
342 | 8B05757E298E52C000A56C9A /* Sources */,
343 | 8B05757F298E52C000A56C9A /* Frameworks */,
344 | 8B057580298E52C000A56C9A /* Resources */,
345 | );
346 | buildRules = (
347 | );
348 | dependencies = (
349 | );
350 | name = XCAChatGPTMac;
351 | packageProductDependencies = (
352 | 8B75A1B32A2CB7D200E8810E /* GoogleGenerativeAI */,
353 | );
354 | productName = XCAChatGPTMac;
355 | productReference = 8B057582298E52C000A56C9A /* XCAChatGPTMac.app */;
356 | productType = "com.apple.product-type.application";
357 | };
358 | 8B0575EA298FA9D800A56C9A /* XCAChatGPTWatch */ = {
359 | isa = PBXNativeTarget;
360 | buildConfigurationList = 8B057604298FA9DA00A56C9A /* Build configuration list for PBXNativeTarget "XCAChatGPTWatch" */;
361 | buildPhases = (
362 | 8B0575E9298FA9D800A56C9A /* Resources */,
363 | 8B057600298FA9DA00A56C9A /* Embed Watch Content */,
364 | );
365 | buildRules = (
366 | );
367 | dependencies = (
368 | 8B0575F3298FA9D800A56C9A /* PBXTargetDependency */,
369 | );
370 | name = XCAChatGPTWatch;
371 | productName = XCAChatGPTWatch;
372 | productReference = 8B0575EB298FA9D800A56C9A /* XCAChatGPTWatch.app */;
373 | productType = "com.apple.product-type.application.watchapp2-container";
374 | };
375 | 8B0575EF298FA9D800A56C9A /* XCAChatGPTWatch Watch App */ = {
376 | isa = PBXNativeTarget;
377 | buildConfigurationList = 8B057603298FA9DA00A56C9A /* Build configuration list for PBXNativeTarget "XCAChatGPTWatch Watch App" */;
378 | buildPhases = (
379 | 8B0575EC298FA9D800A56C9A /* Sources */,
380 | 8B0575ED298FA9D800A56C9A /* Frameworks */,
381 | 8B0575EE298FA9D800A56C9A /* Resources */,
382 | );
383 | buildRules = (
384 | );
385 | dependencies = (
386 | );
387 | name = "XCAChatGPTWatch Watch App";
388 | packageProductDependencies = (
389 | 8B75A1B52A2CB7D500E8810E /* GoogleGenerativeAI */,
390 | );
391 | productName = "XCAChatGPTWatch Watch App";
392 | productReference = 8B0575F0298FA9D800A56C9A /* XCAChatGPTWatch Watch App.app */;
393 | productType = "com.apple.product-type.application";
394 | };
395 | 8B057610298FBDB600A56C9A /* XCAChatGPTTV */ = {
396 | isa = PBXNativeTarget;
397 | buildConfigurationList = 8B05761C298FBDB700A56C9A /* Build configuration list for PBXNativeTarget "XCAChatGPTTV" */;
398 | buildPhases = (
399 | 8B05760D298FBDB600A56C9A /* Sources */,
400 | 8B05760E298FBDB600A56C9A /* Frameworks */,
401 | 8B05760F298FBDB600A56C9A /* Resources */,
402 | );
403 | buildRules = (
404 | );
405 | dependencies = (
406 | );
407 | name = XCAChatGPTTV;
408 | packageProductDependencies = (
409 | 8B75A1B72A2CB7DA00E8810E /* GoogleGenerativeAI */,
410 | );
411 | productName = XCAChatGPTTV;
412 | productReference = 8B057611298FBDB600A56C9A /* XCAChatGPTTV.app */;
413 | productType = "com.apple.product-type.application";
414 | };
415 | 8B91BFFF298AD09E0079AF26 /* XCAChatGPT */ = {
416 | isa = PBXNativeTarget;
417 | buildConfigurationList = 8B91C00E298AD09F0079AF26 /* Build configuration list for PBXNativeTarget "XCAChatGPT" */;
418 | buildPhases = (
419 | 8B91BFFC298AD09E0079AF26 /* Sources */,
420 | 8B91BFFD298AD09E0079AF26 /* Frameworks */,
421 | 8B91BFFE298AD09E0079AF26 /* Resources */,
422 | );
423 | buildRules = (
424 | );
425 | dependencies = (
426 | );
427 | name = XCAChatGPT;
428 | packageProductDependencies = (
429 | 8B612E2929D68CE3008DF5AF /* GPTEncoder */,
430 | 8BC0F8CF29F157C800D6B492 /* Markdown */,
431 | 8BC0F8D229F157E600D6B492 /* Highlighter */,
432 | 8B75A1B02A2CB7C600E8810E /* GoogleGenerativeAI */,
433 | );
434 | productName = XCAChatGPT;
435 | productReference = 8B91C000298AD09E0079AF26 /* XCAChatGPT.app */;
436 | productType = "com.apple.product-type.application";
437 | };
438 | /* End PBXNativeTarget section */
439 |
440 | /* Begin PBXProject section */
441 | 8B91BFF8298AD09E0079AF26 /* Project object */ = {
442 | isa = PBXProject;
443 | attributes = {
444 | BuildIndependentTargetsInParallel = 1;
445 | LastSwiftUpdateCheck = 1420;
446 | LastUpgradeCheck = 1420;
447 | TargetAttributes = {
448 | 8B057581298E52C000A56C9A = {
449 | CreatedOnToolsVersion = 14.2;
450 | };
451 | 8B0575EA298FA9D800A56C9A = {
452 | CreatedOnToolsVersion = 14.2;
453 | };
454 | 8B0575EF298FA9D800A56C9A = {
455 | CreatedOnToolsVersion = 14.2;
456 | };
457 | 8B057610298FBDB600A56C9A = {
458 | CreatedOnToolsVersion = 14.2;
459 | };
460 | 8B91BFFF298AD09E0079AF26 = {
461 | CreatedOnToolsVersion = 14.2;
462 | };
463 | };
464 | };
465 | buildConfigurationList = 8B91BFFB298AD09E0079AF26 /* Build configuration list for PBXProject "XCAChatGPT" */;
466 | compatibilityVersion = "Xcode 14.0";
467 | developmentRegion = en;
468 | hasScannedForEncodings = 0;
469 | knownRegions = (
470 | en,
471 | Base,
472 | );
473 | mainGroup = 8B91BFF7298AD09E0079AF26;
474 | packageReferences = (
475 | 8B612E2829D68CE3008DF5AF /* XCRemoteSwiftPackageReference "GPTEncoder" */,
476 | 8BC0F8CE29F157C800D6B492 /* XCRemoteSwiftPackageReference "swift-markdown" */,
477 | 8BC0F8D129F157E600D6B492 /* XCRemoteSwiftPackageReference "HighlighterSwift" */,
478 | 8B75A1AF2A2CB7C600E8810E /* XCRemoteSwiftPackageReference "generative-ai-swift" */,
479 | );
480 | productRefGroup = 8B91C001298AD09E0079AF26 /* Products */;
481 | projectDirPath = "";
482 | projectRoot = "";
483 | targets = (
484 | 8B91BFFF298AD09E0079AF26 /* XCAChatGPT */,
485 | 8B057581298E52C000A56C9A /* XCAChatGPTMac */,
486 | 8B0575EA298FA9D800A56C9A /* XCAChatGPTWatch */,
487 | 8B0575EF298FA9D800A56C9A /* XCAChatGPTWatch Watch App */,
488 | 8B057610298FBDB600A56C9A /* XCAChatGPTTV */,
489 | );
490 | };
491 | /* End PBXProject section */
492 |
493 | /* Begin PBXResourcesBuildPhase section */
494 | 8B057580298E52C000A56C9A /* Resources */ = {
495 | isa = PBXResourcesBuildPhase;
496 | buildActionMask = 2147483647;
497 | files = (
498 | 8B05758C298E52C000A56C9A /* Preview Assets.xcassets in Resources */,
499 | 8B057589298E52C000A56C9A /* Assets.xcassets in Resources */,
500 | );
501 | runOnlyForDeploymentPostprocessing = 0;
502 | };
503 | 8B0575E9298FA9D800A56C9A /* Resources */ = {
504 | isa = PBXResourcesBuildPhase;
505 | buildActionMask = 2147483647;
506 | files = (
507 | );
508 | runOnlyForDeploymentPostprocessing = 0;
509 | };
510 | 8B0575EE298FA9D800A56C9A /* Resources */ = {
511 | isa = PBXResourcesBuildPhase;
512 | buildActionMask = 2147483647;
513 | files = (
514 | 8B0575FD298FA9DA00A56C9A /* Preview Assets.xcassets in Resources */,
515 | 8B0575FA298FA9DA00A56C9A /* Assets.xcassets in Resources */,
516 | );
517 | runOnlyForDeploymentPostprocessing = 0;
518 | };
519 | 8B05760F298FBDB600A56C9A /* Resources */ = {
520 | isa = PBXResourcesBuildPhase;
521 | buildActionMask = 2147483647;
522 | files = (
523 | 8B05761B298FBDB700A56C9A /* Preview Assets.xcassets in Resources */,
524 | 8B057618298FBDB700A56C9A /* Assets.xcassets in Resources */,
525 | );
526 | runOnlyForDeploymentPostprocessing = 0;
527 | };
528 | 8B91BFFE298AD09E0079AF26 /* Resources */ = {
529 | isa = PBXResourcesBuildPhase;
530 | buildActionMask = 2147483647;
531 | files = (
532 | 8B91C00B298AD09F0079AF26 /* Preview Assets.xcassets in Resources */,
533 | 8B91C008298AD09F0079AF26 /* Assets.xcassets in Resources */,
534 | );
535 | runOnlyForDeploymentPostprocessing = 0;
536 | };
537 | /* End PBXResourcesBuildPhase section */
538 |
539 | /* Begin PBXSourcesBuildPhase section */
540 | 8B05757E298E52C000A56C9A /* Sources */ = {
541 | isa = PBXSourcesBuildPhase;
542 | buildActionMask = 2147483647;
543 | files = (
544 | 8B75A1D02A2CB8DC00E8810E /* LLMConfigView.swift in Sources */,
545 | 8B057597298E52DD00A56C9A /* MessageRow.swift in Sources */,
546 | 8B75A1CB2A2CB83900E8810E /* LLMProvider.swift in Sources */,
547 | 8B057593298E52D900A56C9A /* ChatGPTAPI.swift in Sources */,
548 | 8B75A1C32A2CB83900E8810E /* PaLMChatAPI.swift in Sources */,
549 | 8B82463529B1F49F0069B8F7 /* ChatGPTAPIModels.swift in Sources */,
550 | 8BC0F8D829F1583300D6B492 /* ParserResult.swift in Sources */,
551 | 8B75A1BF2A2CB83900E8810E /* LLMClient.swift in Sources */,
552 | 8B057594298E52D900A56C9A /* ContentView.swift in Sources */,
553 | 8B057592298E52D900A56C9A /* DotLoadingView.swift in Sources */,
554 | 8B057595298E52D900A56C9A /* MessageRowView.swift in Sources */,
555 | 8B75A1C72A2CB83900E8810E /* LLMConfig.swift in Sources */,
556 | 8B057585298E52C000A56C9A /* XCAChatGPTMacApp.swift in Sources */,
557 | 8B057596298E52DD00A56C9A /* ViewModel.swift in Sources */,
558 | );
559 | runOnlyForDeploymentPostprocessing = 0;
560 | };
561 | 8B0575EC298FA9D800A56C9A /* Sources */ = {
562 | isa = PBXSourcesBuildPhase;
563 | buildActionMask = 2147483647;
564 | files = (
565 | 8B75A1D12A2CB8DC00E8810E /* LLMConfigView.swift in Sources */,
566 | 8B057608298FA9E900A56C9A /* DotLoadingView.swift in Sources */,
567 | 8B75A1CC2A2CB83900E8810E /* LLMProvider.swift in Sources */,
568 | 8B057606298FA9E900A56C9A /* MessageRowView.swift in Sources */,
569 | 8B75A1C42A2CB83900E8810E /* PaLMChatAPI.swift in Sources */,
570 | 8B82463629B1F49F0069B8F7 /* ChatGPTAPIModels.swift in Sources */,
571 | 8BC0F8D929F1583300D6B492 /* ParserResult.swift in Sources */,
572 | 8B75A1C02A2CB83900E8810E /* LLMClient.swift in Sources */,
573 | 8B057609298FA9E900A56C9A /* MessageRow.swift in Sources */,
574 | 8B057605298FA9E900A56C9A /* ContentView.swift in Sources */,
575 | 8B05760A298FA9E900A56C9A /* ChatGPTAPI.swift in Sources */,
576 | 8B75A1C82A2CB83900E8810E /* LLMConfig.swift in Sources */,
577 | 8B057607298FA9E900A56C9A /* ViewModel.swift in Sources */,
578 | 8B0575F6298FA9D800A56C9A /* XCAChatGPTWatchApp.swift in Sources */,
579 | );
580 | runOnlyForDeploymentPostprocessing = 0;
581 | };
582 | 8B05760D298FBDB600A56C9A /* Sources */ = {
583 | isa = PBXSourcesBuildPhase;
584 | buildActionMask = 2147483647;
585 | files = (
586 | 8B057621298FBE0400A56C9A /* MessageRow.swift in Sources */,
587 | 8B057622298FBE0400A56C9A /* MessageRowView.swift in Sources */,
588 | 8B82463729B1F49F0069B8F7 /* ChatGPTAPIModels.swift in Sources */,
589 | 8BC0F8DA29F1583300D6B492 /* ParserResult.swift in Sources */,
590 | 8B75A1CD2A2CB83900E8810E /* LLMProvider.swift in Sources */,
591 | 8B75A1C92A2CB83900E8810E /* LLMConfig.swift in Sources */,
592 | 8B75A1C52A2CB83900E8810E /* PaLMChatAPI.swift in Sources */,
593 | 8B05761F298FBE0400A56C9A /* ChatGPTAPI.swift in Sources */,
594 | 8B057623298FBE0400A56C9A /* DotLoadingView.swift in Sources */,
595 | 8B057614298FBDB600A56C9A /* XCAChatGPTTVApp.swift in Sources */,
596 | 8B05764829909A9200A56C9A /* ScrollView.swift in Sources */,
597 | 8B057620298FBE0400A56C9A /* ContentView.swift in Sources */,
598 | 8B75A1C12A2CB83900E8810E /* LLMClient.swift in Sources */,
599 | 8B057624298FBE0400A56C9A /* ViewModel.swift in Sources */,
600 | 8B75A1D22A2CB8DC00E8810E /* LLMConfigView.swift in Sources */,
601 | );
602 | runOnlyForDeploymentPostprocessing = 0;
603 | };
604 | 8B91BFFC298AD09E0079AF26 /* Sources */ = {
605 | isa = PBXSourcesBuildPhase;
606 | buildActionMask = 2147483647;
607 | files = (
608 | 8B91C012298AD0CE0079AF26 /* ChatGPTAPI.swift in Sources */,
609 | 8B91C006298AD09E0079AF26 /* ContentView.swift in Sources */,
610 | 8BC0F8DC29F1587600D6B492 /* ResponseParsingTask.swift in Sources */,
611 | 8B75A1C62A2CB83900E8810E /* LLMConfig.swift in Sources */,
612 | 8B75A1CF2A2CB8DC00E8810E /* LLMConfigView.swift in Sources */,
613 | 8B612E2629D68CC9008DF5AF /* TokenizerView.swift in Sources */,
614 | 8B612E2729D68CC9008DF5AF /* TokenizerViewModel.swift in Sources */,
615 | 8B82463429B1F49F0069B8F7 /* ChatGPTAPIModels.swift in Sources */,
616 | 8BC0F8DE29F1589600D6B492 /* CodeBlockView.swift in Sources */,
617 | 8B91C014298ADC560079AF26 /* ViewModel.swift in Sources */,
618 | 8BC0F8D729F1583300D6B492 /* ParserResult.swift in Sources */,
619 | 8B91C018298ADF4E0079AF26 /* DotLoadingView.swift in Sources */,
620 | 8B91C004298AD09E0079AF26 /* XCAChatGPTApp.swift in Sources */,
621 | 8B75A1C22A2CB83900E8810E /* PaLMChatAPI.swift in Sources */,
622 | 8B612E2529D68CC9008DF5AF /* TextView.swift in Sources */,
623 | 8B91C01A298ADF7F0079AF26 /* MessageRowView.swift in Sources */,
624 | 8BC0F8D529F1581800D6B492 /* MarkdownAttributedStringParser.swift in Sources */,
625 | 8B75A1BE2A2CB83900E8810E /* LLMClient.swift in Sources */,
626 | 8B75A1CA2A2CB83900E8810E /* LLMProvider.swift in Sources */,
627 | 8B91C016298ADC9D0079AF26 /* MessageRow.swift in Sources */,
628 | );
629 | runOnlyForDeploymentPostprocessing = 0;
630 | };
631 | /* End PBXSourcesBuildPhase section */
632 |
633 | /* Begin PBXTargetDependency section */
634 | 8B0575F3298FA9D800A56C9A /* PBXTargetDependency */ = {
635 | isa = PBXTargetDependency;
636 | target = 8B0575EF298FA9D800A56C9A /* XCAChatGPTWatch Watch App */;
637 | targetProxy = 8B0575F2298FA9D800A56C9A /* PBXContainerItemProxy */;
638 | };
639 | /* End PBXTargetDependency section */
640 |
641 | /* Begin XCBuildConfiguration section */
642 | 8B05758E298E52C000A56C9A /* Debug */ = {
643 | isa = XCBuildConfiguration;
644 | buildSettings = {
645 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
646 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
647 | CODE_SIGN_ENTITLEMENTS = XCAChatGPTMac/XCAChatGPTMac.entitlements;
648 | CODE_SIGN_STYLE = Automatic;
649 | COMBINE_HIDPI_IMAGES = YES;
650 | CURRENT_PROJECT_VERSION = 1;
651 | DEVELOPMENT_ASSET_PATHS = "\"XCAChatGPTMac/Preview Content\"";
652 | DEVELOPMENT_TEAM = 5C2XD9H2JS;
653 | ENABLE_HARDENED_RUNTIME = YES;
654 | ENABLE_PREVIEWS = YES;
655 | GENERATE_INFOPLIST_FILE = YES;
656 | INFOPLIST_KEY_NSHumanReadableCopyright = "";
657 | LD_RUNPATH_SEARCH_PATHS = (
658 | "$(inherited)",
659 | "@executable_path/../Frameworks",
660 | );
661 | MACOSX_DEPLOYMENT_TARGET = 13.1;
662 | MARKETING_VERSION = 1.0;
663 | PRODUCT_BUNDLE_IDENTIFIER = com.alfianlosari.XCAChatGPTMac;
664 | PRODUCT_NAME = "$(TARGET_NAME)";
665 | SDKROOT = macosx;
666 | SWIFT_EMIT_LOC_STRINGS = YES;
667 | SWIFT_VERSION = 5.0;
668 | };
669 | name = Debug;
670 | };
671 | 8B05758F298E52C000A56C9A /* Release */ = {
672 | isa = XCBuildConfiguration;
673 | buildSettings = {
674 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
675 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
676 | CODE_SIGN_ENTITLEMENTS = XCAChatGPTMac/XCAChatGPTMac.entitlements;
677 | CODE_SIGN_STYLE = Automatic;
678 | COMBINE_HIDPI_IMAGES = YES;
679 | CURRENT_PROJECT_VERSION = 1;
680 | DEVELOPMENT_ASSET_PATHS = "\"XCAChatGPTMac/Preview Content\"";
681 | DEVELOPMENT_TEAM = 5C2XD9H2JS;
682 | ENABLE_HARDENED_RUNTIME = YES;
683 | ENABLE_PREVIEWS = YES;
684 | GENERATE_INFOPLIST_FILE = YES;
685 | INFOPLIST_KEY_NSHumanReadableCopyright = "";
686 | LD_RUNPATH_SEARCH_PATHS = (
687 | "$(inherited)",
688 | "@executable_path/../Frameworks",
689 | );
690 | MACOSX_DEPLOYMENT_TARGET = 13.1;
691 | MARKETING_VERSION = 1.0;
692 | PRODUCT_BUNDLE_IDENTIFIER = com.alfianlosari.XCAChatGPTMac;
693 | PRODUCT_NAME = "$(TARGET_NAME)";
694 | SDKROOT = macosx;
695 | SWIFT_EMIT_LOC_STRINGS = YES;
696 | SWIFT_VERSION = 5.0;
697 | };
698 | name = Release;
699 | };
700 | 8B0575FE298FA9DA00A56C9A /* Debug */ = {
701 | isa = XCBuildConfiguration;
702 | buildSettings = {
703 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
704 | CODE_SIGN_STYLE = Automatic;
705 | CURRENT_PROJECT_VERSION = 1;
706 | DEVELOPMENT_TEAM = 5C2XD9H2JS;
707 | INFOPLIST_KEY_CFBundleDisplayName = XCAChatGPTWatch;
708 | MARKETING_VERSION = 1.0;
709 | PRODUCT_BUNDLE_IDENTIFIER = com.alfianlosari.XCAChatGPTWatch;
710 | PRODUCT_NAME = "$(TARGET_NAME)";
711 | SWIFT_VERSION = 5.0;
712 | };
713 | name = Debug;
714 | };
715 | 8B0575FF298FA9DA00A56C9A /* Release */ = {
716 | isa = XCBuildConfiguration;
717 | buildSettings = {
718 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
719 | CODE_SIGN_STYLE = Automatic;
720 | CURRENT_PROJECT_VERSION = 1;
721 | DEVELOPMENT_TEAM = 5C2XD9H2JS;
722 | INFOPLIST_KEY_CFBundleDisplayName = XCAChatGPTWatch;
723 | MARKETING_VERSION = 1.0;
724 | PRODUCT_BUNDLE_IDENTIFIER = com.alfianlosari.XCAChatGPTWatch;
725 | PRODUCT_NAME = "$(TARGET_NAME)";
726 | SWIFT_VERSION = 5.0;
727 | };
728 | name = Release;
729 | };
730 | 8B057601298FA9DA00A56C9A /* Debug */ = {
731 | isa = XCBuildConfiguration;
732 | buildSettings = {
733 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
734 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
735 | CODE_SIGN_STYLE = Automatic;
736 | CURRENT_PROJECT_VERSION = 1;
737 | DEVELOPMENT_ASSET_PATHS = "\"XCAChatGPTWatch Watch App/Preview Content\"";
738 | DEVELOPMENT_TEAM = 5C2XD9H2JS;
739 | ENABLE_PREVIEWS = YES;
740 | GENERATE_INFOPLIST_FILE = YES;
741 | INFOPLIST_KEY_CFBundleDisplayName = XCAChatGPTWatch;
742 | INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
743 | INFOPLIST_KEY_WKWatchOnly = YES;
744 | LD_RUNPATH_SEARCH_PATHS = (
745 | "$(inherited)",
746 | "@executable_path/Frameworks",
747 | );
748 | MARKETING_VERSION = 1.0;
749 | PRODUCT_BUNDLE_IDENTIFIER = com.alfianlosari.XCAChatGPTWatch.watchkitapp;
750 | PRODUCT_NAME = "$(TARGET_NAME)";
751 | SDKROOT = watchos;
752 | SKIP_INSTALL = YES;
753 | SWIFT_EMIT_LOC_STRINGS = YES;
754 | SWIFT_VERSION = 5.0;
755 | TARGETED_DEVICE_FAMILY = 4;
756 | WATCHOS_DEPLOYMENT_TARGET = 9.1;
757 | };
758 | name = Debug;
759 | };
760 | 8B057602298FA9DA00A56C9A /* Release */ = {
761 | isa = XCBuildConfiguration;
762 | buildSettings = {
763 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
764 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
765 | CODE_SIGN_STYLE = Automatic;
766 | CURRENT_PROJECT_VERSION = 1;
767 | DEVELOPMENT_ASSET_PATHS = "\"XCAChatGPTWatch Watch App/Preview Content\"";
768 | DEVELOPMENT_TEAM = 5C2XD9H2JS;
769 | ENABLE_PREVIEWS = YES;
770 | GENERATE_INFOPLIST_FILE = YES;
771 | INFOPLIST_KEY_CFBundleDisplayName = XCAChatGPTWatch;
772 | INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
773 | INFOPLIST_KEY_WKWatchOnly = YES;
774 | LD_RUNPATH_SEARCH_PATHS = (
775 | "$(inherited)",
776 | "@executable_path/Frameworks",
777 | );
778 | MARKETING_VERSION = 1.0;
779 | PRODUCT_BUNDLE_IDENTIFIER = com.alfianlosari.XCAChatGPTWatch.watchkitapp;
780 | PRODUCT_NAME = "$(TARGET_NAME)";
781 | SDKROOT = watchos;
782 | SKIP_INSTALL = YES;
783 | SWIFT_EMIT_LOC_STRINGS = YES;
784 | SWIFT_VERSION = 5.0;
785 | TARGETED_DEVICE_FAMILY = 4;
786 | WATCHOS_DEPLOYMENT_TARGET = 9.1;
787 | };
788 | name = Release;
789 | };
790 | 8B05761D298FBDB700A56C9A /* Debug */ = {
791 | isa = XCBuildConfiguration;
792 | buildSettings = {
793 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
794 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
795 | CODE_SIGN_STYLE = Automatic;
796 | CURRENT_PROJECT_VERSION = 1;
797 | DEVELOPMENT_ASSET_PATHS = "\"XCAChatGPTTV/Preview Content\"";
798 | DEVELOPMENT_TEAM = 5C2XD9H2JS;
799 | ENABLE_PREVIEWS = YES;
800 | GENERATE_INFOPLIST_FILE = YES;
801 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
802 | INFOPLIST_KEY_UIUserInterfaceStyle = Automatic;
803 | LD_RUNPATH_SEARCH_PATHS = (
804 | "$(inherited)",
805 | "@executable_path/Frameworks",
806 | );
807 | MARKETING_VERSION = 1.0;
808 | PRODUCT_BUNDLE_IDENTIFIER = com.alfianlosari.XCAChatGPTTV;
809 | PRODUCT_NAME = "$(TARGET_NAME)";
810 | SDKROOT = appletvos;
811 | SWIFT_EMIT_LOC_STRINGS = YES;
812 | SWIFT_VERSION = 5.0;
813 | TARGETED_DEVICE_FAMILY = 3;
814 | TVOS_DEPLOYMENT_TARGET = 16.1;
815 | };
816 | name = Debug;
817 | };
818 | 8B05761E298FBDB700A56C9A /* Release */ = {
819 | isa = XCBuildConfiguration;
820 | buildSettings = {
821 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
822 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
823 | CODE_SIGN_STYLE = Automatic;
824 | CURRENT_PROJECT_VERSION = 1;
825 | DEVELOPMENT_ASSET_PATHS = "\"XCAChatGPTTV/Preview Content\"";
826 | DEVELOPMENT_TEAM = 5C2XD9H2JS;
827 | ENABLE_PREVIEWS = YES;
828 | GENERATE_INFOPLIST_FILE = YES;
829 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
830 | INFOPLIST_KEY_UIUserInterfaceStyle = Automatic;
831 | LD_RUNPATH_SEARCH_PATHS = (
832 | "$(inherited)",
833 | "@executable_path/Frameworks",
834 | );
835 | MARKETING_VERSION = 1.0;
836 | PRODUCT_BUNDLE_IDENTIFIER = com.alfianlosari.XCAChatGPTTV;
837 | PRODUCT_NAME = "$(TARGET_NAME)";
838 | SDKROOT = appletvos;
839 | SWIFT_EMIT_LOC_STRINGS = YES;
840 | SWIFT_VERSION = 5.0;
841 | TARGETED_DEVICE_FAMILY = 3;
842 | TVOS_DEPLOYMENT_TARGET = 16.1;
843 | };
844 | name = Release;
845 | };
846 | 8B91C00C298AD09F0079AF26 /* Debug */ = {
847 | isa = XCBuildConfiguration;
848 | buildSettings = {
849 | ALWAYS_SEARCH_USER_PATHS = NO;
850 | CLANG_ANALYZER_NONNULL = YES;
851 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
852 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
853 | CLANG_ENABLE_MODULES = YES;
854 | CLANG_ENABLE_OBJC_ARC = YES;
855 | CLANG_ENABLE_OBJC_WEAK = YES;
856 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
857 | CLANG_WARN_BOOL_CONVERSION = YES;
858 | CLANG_WARN_COMMA = YES;
859 | CLANG_WARN_CONSTANT_CONVERSION = YES;
860 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
861 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
862 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
863 | CLANG_WARN_EMPTY_BODY = YES;
864 | CLANG_WARN_ENUM_CONVERSION = YES;
865 | CLANG_WARN_INFINITE_RECURSION = YES;
866 | CLANG_WARN_INT_CONVERSION = YES;
867 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
868 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
869 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
870 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
871 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
872 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
873 | CLANG_WARN_STRICT_PROTOTYPES = YES;
874 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
875 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
876 | CLANG_WARN_UNREACHABLE_CODE = YES;
877 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
878 | COPY_PHASE_STRIP = NO;
879 | DEBUG_INFORMATION_FORMAT = dwarf;
880 | ENABLE_STRICT_OBJC_MSGSEND = YES;
881 | ENABLE_TESTABILITY = YES;
882 | GCC_C_LANGUAGE_STANDARD = gnu11;
883 | GCC_DYNAMIC_NO_PIC = NO;
884 | GCC_NO_COMMON_BLOCKS = YES;
885 | GCC_OPTIMIZATION_LEVEL = 0;
886 | GCC_PREPROCESSOR_DEFINITIONS = (
887 | "DEBUG=1",
888 | "$(inherited)",
889 | );
890 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
891 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
892 | GCC_WARN_UNDECLARED_SELECTOR = YES;
893 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
894 | GCC_WARN_UNUSED_FUNCTION = YES;
895 | GCC_WARN_UNUSED_VARIABLE = YES;
896 | IPHONEOS_DEPLOYMENT_TARGET = 16.2;
897 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
898 | MTL_FAST_MATH = YES;
899 | ONLY_ACTIVE_ARCH = YES;
900 | SDKROOT = iphoneos;
901 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
902 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
903 | };
904 | name = Debug;
905 | };
906 | 8B91C00D298AD09F0079AF26 /* Release */ = {
907 | isa = XCBuildConfiguration;
908 | buildSettings = {
909 | ALWAYS_SEARCH_USER_PATHS = NO;
910 | CLANG_ANALYZER_NONNULL = YES;
911 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
912 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
913 | CLANG_ENABLE_MODULES = YES;
914 | CLANG_ENABLE_OBJC_ARC = YES;
915 | CLANG_ENABLE_OBJC_WEAK = YES;
916 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
917 | CLANG_WARN_BOOL_CONVERSION = YES;
918 | CLANG_WARN_COMMA = YES;
919 | CLANG_WARN_CONSTANT_CONVERSION = YES;
920 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
921 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
922 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
923 | CLANG_WARN_EMPTY_BODY = YES;
924 | CLANG_WARN_ENUM_CONVERSION = YES;
925 | CLANG_WARN_INFINITE_RECURSION = YES;
926 | CLANG_WARN_INT_CONVERSION = YES;
927 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
928 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
929 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
930 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
931 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
932 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
933 | CLANG_WARN_STRICT_PROTOTYPES = YES;
934 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
935 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
936 | CLANG_WARN_UNREACHABLE_CODE = YES;
937 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
938 | COPY_PHASE_STRIP = NO;
939 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
940 | ENABLE_NS_ASSERTIONS = NO;
941 | ENABLE_STRICT_OBJC_MSGSEND = YES;
942 | GCC_C_LANGUAGE_STANDARD = gnu11;
943 | GCC_NO_COMMON_BLOCKS = YES;
944 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
945 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
946 | GCC_WARN_UNDECLARED_SELECTOR = YES;
947 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
948 | GCC_WARN_UNUSED_FUNCTION = YES;
949 | GCC_WARN_UNUSED_VARIABLE = YES;
950 | IPHONEOS_DEPLOYMENT_TARGET = 16.2;
951 | MTL_ENABLE_DEBUG_INFO = NO;
952 | MTL_FAST_MATH = YES;
953 | SDKROOT = iphoneos;
954 | SWIFT_COMPILATION_MODE = wholemodule;
955 | SWIFT_OPTIMIZATION_LEVEL = "-O";
956 | VALIDATE_PRODUCT = YES;
957 | };
958 | name = Release;
959 | };
960 | 8B91C00F298AD09F0079AF26 /* Debug */ = {
961 | isa = XCBuildConfiguration;
962 | buildSettings = {
963 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
964 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
965 | CODE_SIGN_STYLE = Automatic;
966 | CURRENT_PROJECT_VERSION = 1;
967 | DEVELOPMENT_ASSET_PATHS = "\"XCAChatGPT/Preview Content\"";
968 | DEVELOPMENT_TEAM = 5C2XD9H2JS;
969 | ENABLE_PREVIEWS = YES;
970 | GENERATE_INFOPLIST_FILE = YES;
971 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
972 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
973 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
974 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
975 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
976 | LD_RUNPATH_SEARCH_PATHS = (
977 | "$(inherited)",
978 | "@executable_path/Frameworks",
979 | );
980 | MARKETING_VERSION = 1.0;
981 | PRODUCT_BUNDLE_IDENTIFIER = com.alfianlosari.XCAChatGPT;
982 | PRODUCT_NAME = "$(TARGET_NAME)";
983 | SWIFT_EMIT_LOC_STRINGS = YES;
984 | SWIFT_VERSION = 5.0;
985 | TARGETED_DEVICE_FAMILY = "1,2";
986 | };
987 | name = Debug;
988 | };
989 | 8B91C010298AD09F0079AF26 /* Release */ = {
990 | isa = XCBuildConfiguration;
991 | buildSettings = {
992 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
993 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
994 | CODE_SIGN_STYLE = Automatic;
995 | CURRENT_PROJECT_VERSION = 1;
996 | DEVELOPMENT_ASSET_PATHS = "\"XCAChatGPT/Preview Content\"";
997 | DEVELOPMENT_TEAM = 5C2XD9H2JS;
998 | ENABLE_PREVIEWS = YES;
999 | GENERATE_INFOPLIST_FILE = YES;
1000 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
1001 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
1002 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
1003 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
1004 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
1005 | LD_RUNPATH_SEARCH_PATHS = (
1006 | "$(inherited)",
1007 | "@executable_path/Frameworks",
1008 | );
1009 | MARKETING_VERSION = 1.0;
1010 | PRODUCT_BUNDLE_IDENTIFIER = com.alfianlosari.XCAChatGPT;
1011 | PRODUCT_NAME = "$(TARGET_NAME)";
1012 | SWIFT_EMIT_LOC_STRINGS = YES;
1013 | SWIFT_VERSION = 5.0;
1014 | TARGETED_DEVICE_FAMILY = "1,2";
1015 | };
1016 | name = Release;
1017 | };
1018 | /* End XCBuildConfiguration section */
1019 |
1020 | /* Begin XCConfigurationList section */
1021 | 8B057590298E52C000A56C9A /* Build configuration list for PBXNativeTarget "XCAChatGPTMac" */ = {
1022 | isa = XCConfigurationList;
1023 | buildConfigurations = (
1024 | 8B05758E298E52C000A56C9A /* Debug */,
1025 | 8B05758F298E52C000A56C9A /* Release */,
1026 | );
1027 | defaultConfigurationIsVisible = 0;
1028 | defaultConfigurationName = Release;
1029 | };
1030 | 8B057603298FA9DA00A56C9A /* Build configuration list for PBXNativeTarget "XCAChatGPTWatch Watch App" */ = {
1031 | isa = XCConfigurationList;
1032 | buildConfigurations = (
1033 | 8B057601298FA9DA00A56C9A /* Debug */,
1034 | 8B057602298FA9DA00A56C9A /* Release */,
1035 | );
1036 | defaultConfigurationIsVisible = 0;
1037 | defaultConfigurationName = Release;
1038 | };
1039 | 8B057604298FA9DA00A56C9A /* Build configuration list for PBXNativeTarget "XCAChatGPTWatch" */ = {
1040 | isa = XCConfigurationList;
1041 | buildConfigurations = (
1042 | 8B0575FE298FA9DA00A56C9A /* Debug */,
1043 | 8B0575FF298FA9DA00A56C9A /* Release */,
1044 | );
1045 | defaultConfigurationIsVisible = 0;
1046 | defaultConfigurationName = Release;
1047 | };
1048 | 8B05761C298FBDB700A56C9A /* Build configuration list for PBXNativeTarget "XCAChatGPTTV" */ = {
1049 | isa = XCConfigurationList;
1050 | buildConfigurations = (
1051 | 8B05761D298FBDB700A56C9A /* Debug */,
1052 | 8B05761E298FBDB700A56C9A /* Release */,
1053 | );
1054 | defaultConfigurationIsVisible = 0;
1055 | defaultConfigurationName = Release;
1056 | };
1057 | 8B91BFFB298AD09E0079AF26 /* Build configuration list for PBXProject "XCAChatGPT" */ = {
1058 | isa = XCConfigurationList;
1059 | buildConfigurations = (
1060 | 8B91C00C298AD09F0079AF26 /* Debug */,
1061 | 8B91C00D298AD09F0079AF26 /* Release */,
1062 | );
1063 | defaultConfigurationIsVisible = 0;
1064 | defaultConfigurationName = Release;
1065 | };
1066 | 8B91C00E298AD09F0079AF26 /* Build configuration list for PBXNativeTarget "XCAChatGPT" */ = {
1067 | isa = XCConfigurationList;
1068 | buildConfigurations = (
1069 | 8B91C00F298AD09F0079AF26 /* Debug */,
1070 | 8B91C010298AD09F0079AF26 /* Release */,
1071 | );
1072 | defaultConfigurationIsVisible = 0;
1073 | defaultConfigurationName = Release;
1074 | };
1075 | /* End XCConfigurationList section */
1076 |
1077 | /* Begin XCRemoteSwiftPackageReference section */
1078 | 8B612E2829D68CE3008DF5AF /* XCRemoteSwiftPackageReference "GPTEncoder" */ = {
1079 | isa = XCRemoteSwiftPackageReference;
1080 | repositoryURL = "https://github.com/alfianlosari/GPTEncoder.git";
1081 | requirement = {
1082 | kind = upToNextMajorVersion;
1083 | minimumVersion = 1.0.0;
1084 | };
1085 | };
1086 | 8B75A1AF2A2CB7C600E8810E /* XCRemoteSwiftPackageReference "generative-ai-swift" */ = {
1087 | isa = XCRemoteSwiftPackageReference;
1088 | repositoryURL = "https://github.com/google/generative-ai-swift";
1089 | requirement = {
1090 | kind = upToNextMajorVersion;
1091 | minimumVersion = 0.2.0;
1092 | };
1093 | };
1094 | 8BC0F8CE29F157C800D6B492 /* XCRemoteSwiftPackageReference "swift-markdown" */ = {
1095 | isa = XCRemoteSwiftPackageReference;
1096 | repositoryURL = "https://github.com/apple/swift-markdown";
1097 | requirement = {
1098 | branch = main;
1099 | kind = branch;
1100 | };
1101 | };
1102 | 8BC0F8D129F157E600D6B492 /* XCRemoteSwiftPackageReference "HighlighterSwift" */ = {
1103 | isa = XCRemoteSwiftPackageReference;
1104 | repositoryURL = "https://github.com/alfianlosari/HighlighterSwift";
1105 | requirement = {
1106 | branch = main;
1107 | kind = branch;
1108 | };
1109 | };
1110 | /* End XCRemoteSwiftPackageReference section */
1111 |
1112 | /* Begin XCSwiftPackageProductDependency section */
1113 | 8B612E2929D68CE3008DF5AF /* GPTEncoder */ = {
1114 | isa = XCSwiftPackageProductDependency;
1115 | package = 8B612E2829D68CE3008DF5AF /* XCRemoteSwiftPackageReference "GPTEncoder" */;
1116 | productName = GPTEncoder;
1117 | };
1118 | 8B75A1B02A2CB7C600E8810E /* GoogleGenerativeAI */ = {
1119 | isa = XCSwiftPackageProductDependency;
1120 | package = 8B75A1AF2A2CB7C600E8810E /* XCRemoteSwiftPackageReference "generative-ai-swift" */;
1121 | productName = GoogleGenerativeAI;
1122 | };
1123 | 8B75A1B32A2CB7D200E8810E /* GoogleGenerativeAI */ = {
1124 | isa = XCSwiftPackageProductDependency;
1125 | package = 8B75A1AF2A2CB7C600E8810E /* XCRemoteSwiftPackageReference "generative-ai-swift" */;
1126 | productName = GoogleGenerativeAI;
1127 | };
1128 | 8B75A1B52A2CB7D500E8810E /* GoogleGenerativeAI */ = {
1129 | isa = XCSwiftPackageProductDependency;
1130 | package = 8B75A1AF2A2CB7C600E8810E /* XCRemoteSwiftPackageReference "generative-ai-swift" */;
1131 | productName = GoogleGenerativeAI;
1132 | };
1133 | 8B75A1B72A2CB7DA00E8810E /* GoogleGenerativeAI */ = {
1134 | isa = XCSwiftPackageProductDependency;
1135 | package = 8B75A1AF2A2CB7C600E8810E /* XCRemoteSwiftPackageReference "generative-ai-swift" */;
1136 | productName = GoogleGenerativeAI;
1137 | };
1138 | 8BC0F8CF29F157C800D6B492 /* Markdown */ = {
1139 | isa = XCSwiftPackageProductDependency;
1140 | package = 8BC0F8CE29F157C800D6B492 /* XCRemoteSwiftPackageReference "swift-markdown" */;
1141 | productName = Markdown;
1142 | };
1143 | 8BC0F8D229F157E600D6B492 /* Highlighter */ = {
1144 | isa = XCSwiftPackageProductDependency;
1145 | package = 8BC0F8D129F157E600D6B492 /* XCRemoteSwiftPackageReference "HighlighterSwift" */;
1146 | productName = Highlighter;
1147 | };
1148 | /* End XCSwiftPackageProductDependency section */
1149 | };
1150 | rootObject = 8B91BFF8298AD09E0079AF26 /* Project object */;
1151 | }
1152 |
--------------------------------------------------------------------------------