├── .gitignore
├── examples
├── demo.gif
└── chat.php
├── src
├── Prompt.php
├── Client.php
├── Tools.php
└── Conversation.php
├── composer.json
├── LICENSE
├── composer.lock
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor/
2 | .idea/
3 | .DS_Store
--------------------------------------------------------------------------------
/examples/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maximerenou/php-pi-chat/HEAD/examples/demo.gif
--------------------------------------------------------------------------------
/src/Prompt.php:
--------------------------------------------------------------------------------
1 | text = $text;
12 | }
13 | }
--------------------------------------------------------------------------------
/src/Client.php:
--------------------------------------------------------------------------------
1 | =7.1",
18 | "ext-curl": "*",
19 | "eislambey/eventsource": "^0.1.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/examples/chat.php:
--------------------------------------------------------------------------------
1 | createConversation();
9 |
10 | echo 'Type "q" to quit' . PHP_EOL;
11 |
12 | while (! $conversation->ended()) {
13 | echo PHP_EOL . "> ";
14 |
15 | $text = rtrim(fgets(STDIN));
16 |
17 | if ($text == 'q')
18 | break;
19 |
20 | $prompt = new \MaximeRenou\PiChat\Prompt($text);
21 |
22 | echo "- ";
23 |
24 | // Example: resume from current conversation identifiers
25 | //$identifiers = $conversation->getIdentifiers();
26 | //$conversation = $chatbot->resumeConversation($identifiers);
27 |
28 | try {
29 | $full_answer = $conversation->ask($prompt, function ($answer, $tokens) {
30 | echo $tokens;
31 | });
32 | }
33 | catch (\Exception $exception) {
34 | echo "Sorry, something went wrong: {$exception->getMessage()}.";
35 | }
36 | }
37 |
38 | exit(0);
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Maxime Renou
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 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "2c1dc77c49a101ce6e4daee8eb55a339",
8 | "packages": [
9 | {
10 | "name": "eislambey/eventsource",
11 | "version": "v0.1.0",
12 | "source": {
13 | "type": "git",
14 | "url": "https://github.com/eislambey/php-eventsource.git",
15 | "reference": "6cb41018eec429b3607fd47b3c42bd2f89a1209f"
16 | },
17 | "dist": {
18 | "type": "zip",
19 | "url": "https://api.github.com/repos/eislambey/php-eventsource/zipball/6cb41018eec429b3607fd47b3c42bd2f89a1209f",
20 | "reference": "6cb41018eec429b3607fd47b3c42bd2f89a1209f",
21 | "shasum": ""
22 | },
23 | "require": {
24 | "ext-curl": "*",
25 | "ext-json": "*"
26 | },
27 | "type": "library",
28 | "autoload": {
29 | "psr-4": {
30 | "EventSource\\": "src/"
31 | }
32 | },
33 | "notification-url": "https://packagist.org/downloads/",
34 | "license": [
35 | "MIT"
36 | ],
37 | "authors": [
38 | {
39 | "name": "emre can islambey",
40 | "email": "eislambey@gmail.com"
41 | }
42 | ],
43 | "description": "A simple EventSource / SSE (Server-Sent Events) client.",
44 | "keywords": [
45 | "Server-Sent Events",
46 | "client",
47 | "eventsource",
48 | "sse"
49 | ],
50 | "support": {
51 | "issues": "https://github.com/eislambey/php-eventsource/issues",
52 | "source": "https://github.com/eislambey/php-eventsource/tree/v0.1.0"
53 | },
54 | "time": "2019-08-07T16:41:36+00:00"
55 | }
56 | ],
57 | "packages-dev": [],
58 | "aliases": [],
59 | "minimum-stability": "stable",
60 | "stability-flags": [],
61 | "prefer-stable": false,
62 | "prefer-lowest": false,
63 | "platform": {
64 | "php": ">=7.1",
65 | "ext-curl": "*"
66 | },
67 | "platform-dev": [],
68 | "plugin-api-version": "2.3.0"
69 | }
70 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Pi chatbot client
2 |
3 | [](https://opensource.org/licenses/MIT)
4 | [](https://packagist.org/packages/maximerenou/pi-chat)
5 | [](https://packagist.org/packages/maximerenou/pi-chat)
6 | [](https://packagist.org/packages/maximerenou/pi-chat)
7 |
8 | This is an unofficial PHP client for **Pi** chatbot (from **Inflection AI**). It doesn't require authentication.
9 |
10 | > This package isn't actively maintained and may be unstable due to Inflection AI's Cloudflare configuration.
11 |
12 | > This package is stricly meant for educational purpose. Use at your own risks.
13 |
14 | ## Installation
15 |
16 | composer require maximerenou/pi-chat
17 |
18 | ## Demo
19 |
20 | Clone this repo, run `composer install` and run `examples/chat.php` to test it.
21 |
22 | 
23 |
24 | ## Usage
25 |
26 | ```php
27 | use MaximeRenou\PiChat\Client as PiChat;
28 | use MaximeRenou\PiChat\Prompt;
29 |
30 | $chatbot = new PiChat();
31 |
32 | $conversation = $chatbot->createConversation();
33 |
34 | // $answer - full answer
35 | $answer = $conversation->ask(new Prompt("Hello World"));
36 | ```
37 |
38 |
39 | Real-time / progressive answer
40 |
41 | You may pass a function as second argument to get real-time progression:
42 |
43 | ```php
44 | // $current_answer - incomplete answer
45 | // $tokens - last tokens received
46 | $final_answer = $conversation->ask($prompt, function ($current_answer, $tokens) {
47 | echo $tokens;
48 | });
49 | ```
50 |
51 |
52 |
53 |
54 | Resume a conversation
55 |
56 | If you want to resume a previous conversation, you can retrieve its identifiers:
57 |
58 | ```php
59 | // Get current identifiers
60 | $identifiers = $conversation->getIdentifiers();
61 |
62 | // ...
63 | // Resume conversation with $identifiers parameter
64 | $conversation = $chatbot->resumeConversation($identifiers);
65 | ```
66 |
67 |
68 |
69 |
70 | Error handling
71 |
72 | The code throws exceptions when it receives an error from Pi. You can therefore use a try/catch block to handle errors.
73 |
74 |
75 |
76 | ---------------------------------------
77 |
78 | #### Disclaimer
79 |
80 | This code is for educational purpose only. Using Pi outside pi.ai may violate Inflection AI terms.
81 |
--------------------------------------------------------------------------------
/src/Tools.php:
--------------------------------------------------------------------------------
1 | cookie = $identifiers['cookie'];
23 |
24 | if (is_array($identifiers) && ! empty($identifiers['conversation_id']))
25 | $this->conversation_id = $identifiers['conversation_id'];
26 |
27 | if (! is_array($identifiers)) {
28 | $identifiers = $this->initConversation();
29 | Tools::debug("initConversation identifiers", $identifiers);
30 | }
31 |
32 | $this->cookie = $identifiers['cookie'];
33 | $this->conversation_id = $identifiers['conversation_id'];
34 | }
35 |
36 | public function getIdentifiers()
37 | {
38 | return [
39 | 'cookie' => $this->cookie,
40 | 'conversation_id' => $this->conversation_id,
41 | ];
42 | }
43 |
44 | public function initConversation()
45 | {
46 | $headers = [
47 | 'method: POST',
48 | 'accept: application/json',
49 | 'X-Api-Version: 3',
50 | "referer: https://pi.ai/onboarding",
51 | 'content-type: application/json',
52 | ];
53 |
54 | if (! empty($this->cookie)) {
55 | $headers[] = "cookie: {$this->cookie}";
56 | }
57 |
58 | $data = json_encode([]);
59 |
60 | list($data, $request, $url, $cookies, $cookie_string) = Tools::request("https://pi.ai/api/chat/start", $headers, $data, true);
61 | $data = json_decode($data, true);
62 |
63 | Tools::debug("initConversation result", $data);
64 |
65 | if (! is_array($data) || empty($data['mainConversation']))
66 | throw new \Exception("Failed to init conversation");
67 |
68 | return [
69 | 'conversation_id' => $data['mainConversation']['sid'],
70 | 'cookie' => $cookie_string,
71 | ];
72 | }
73 |
74 | public function ask(Prompt $message, $callback = null)
75 | {
76 | $this->current_text = '';
77 |
78 | $es = new EventSource("https://pi.ai/api/chat");
79 |
80 | $data = [
81 | 'conversation' => $this->conversation_id,
82 | 'text' => $message->text
83 | ];
84 |
85 | Tools::debug("ask", $data);
86 |
87 | $es->setCurlOptions([
88 | CURLOPT_HTTPHEADER => [
89 | 'method: POST',
90 | 'Accept: text/event-stream',
91 | 'Accept-Encoding: gzip, deflate, br, zstd',
92 | 'Accept-Language: en-US;q=0.9,en;q=0.8',
93 | "Referer: https://pi.ai/discover",
94 | "Origin: https://pi.ai",
95 | 'Content-Type: application/json',
96 | 'X-Api-Version: 3',
97 | "Cookie: {$this->cookie}",
98 | "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
99 | ],
100 | CURLOPT_POST => 1,
101 | CURLOPT_POSTFIELDS => json_encode($data)
102 | ]);
103 |
104 | $packets = [];
105 |
106 | $es->onMessage(function (Event $event) use (&$callback, &$packets) {
107 | $packets[] = $event->data;
108 |
109 | $this->handlePacket($event->data, function ($message) use (&$callback) {
110 | if ($message === false || empty($message['text']))
111 | return;
112 |
113 | $this->current_text .= $message['text'];
114 |
115 | if (! is_null($callback)) {
116 | $callback($this->current_text, $message['text']);
117 | }
118 | });
119 | });
120 |
121 | try {
122 | $es->connect();
123 | }
124 | catch (\Exception $exception) {
125 | Tools::debug("Failed to connect", $exception->getMessage());
126 | }
127 |
128 | // Handle abort
129 | if (empty($this->current_text)) {
130 | $this->ended = true;
131 | $this->current_text = "I'm sorry, please start a new conversation!";
132 |
133 | if (! is_null($callback)) {
134 | $callback($this->current_text, $this->current_text);
135 | }
136 | }
137 |
138 | Tools::debug("Packets", $packets);
139 |
140 | return $this->current_text;
141 | }
142 |
143 | protected function handlePacket($raw, $callback)
144 | {
145 | $packets = Tools::splitJsonStrings($raw);
146 |
147 | foreach ($packets as $packet) {
148 | $data = json_decode($packet, true);
149 |
150 | if ($data) {
151 | $callback($data);
152 | }
153 | }
154 | }
155 |
156 | public function ended()
157 | {
158 | return $this->ended;
159 | }
160 | }
161 |
--------------------------------------------------------------------------------