init: React frontend + PHP/Slim backend chat app
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "chat-app/server",
|
||||
"description": "Chat App PHP Backend",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"php": ">=7.4",
|
||||
"slim/slim": "^4.11",
|
||||
"slim/psr7": "^1.6"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "src/"
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+848
@@ -0,0 +1,848 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "24a272e0ab0d1653c60704fcf3eb7e72",
|
||||
"packages": [
|
||||
{
|
||||
"name": "fig/http-message-util",
|
||||
"version": "1.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-message-util.git",
|
||||
"reference": "9d94dc0154230ac39e5bf89398b324a86f63f765"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-message-util/zipball/9d94dc0154230ac39e5bf89398b324a86f63f765",
|
||||
"reference": "9d94dc0154230ac39e5bf89398b324a86f63f765",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": "^5.3 || ^7.0 || ^8.0"
|
||||
},
|
||||
"suggest": {
|
||||
"psr/http-message": "The package containing the PSR-7 interfaces"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Fig\\Http\\Message\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Utility classes and constants for use with PSR-7 (psr/http-message)",
|
||||
"keywords": [
|
||||
"http",
|
||||
"http-message",
|
||||
"psr",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-fig/http-message-util/issues",
|
||||
"source": "https://github.com/php-fig/http-message-util/tree/1.1.5"
|
||||
},
|
||||
"time": "2020-11-24T22:02:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nikic/fast-route",
|
||||
"version": "v1.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nikic/FastRoute.git",
|
||||
"reference": "181d480e08d9476e61381e04a71b34dc0432e812"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nikic/FastRoute/zipball/181d480e08d9476e61381e04a71b34dc0432e812",
|
||||
"reference": "181d480e08d9476e61381e04a71b34dc0432e812",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.8.35|~5.7"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"FastRoute\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nikita Popov",
|
||||
"email": "nikic@php.net"
|
||||
}
|
||||
],
|
||||
"description": "Fast request router for PHP",
|
||||
"keywords": [
|
||||
"router",
|
||||
"routing"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nikic/FastRoute/issues",
|
||||
"source": "https://github.com/nikic/FastRoute/tree/master"
|
||||
},
|
||||
"time": "2018-02-13T20:26:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
"version": "2.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/container.git",
|
||||
"reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
|
||||
"reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.4.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Container\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common Container Interface (PHP FIG PSR-11)",
|
||||
"homepage": "https://github.com/php-fig/container",
|
||||
"keywords": [
|
||||
"PSR-11",
|
||||
"container",
|
||||
"container-interface",
|
||||
"container-interop",
|
||||
"psr"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-fig/container/issues",
|
||||
"source": "https://github.com/php-fig/container/tree/2.0.2"
|
||||
},
|
||||
"time": "2021-11-05T16:47:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-factory",
|
||||
"version": "1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-factory.git",
|
||||
"reference": "e616d01114759c4c489f93b099585439f795fe35"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35",
|
||||
"reference": "e616d01114759c4c489f93b099585439f795fe35",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.0.0",
|
||||
"psr/http-message": "^1.0 || ^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Message\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interfaces for PSR-7 HTTP message factories",
|
||||
"keywords": [
|
||||
"factory",
|
||||
"http",
|
||||
"message",
|
||||
"psr",
|
||||
"psr-17",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-factory/tree/1.0.2"
|
||||
},
|
||||
"time": "2023-04-10T20:10:41+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-message",
|
||||
"version": "1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-message.git",
|
||||
"reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
|
||||
"reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Message\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "http://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP messages",
|
||||
"homepage": "https://github.com/php-fig/http-message",
|
||||
"keywords": [
|
||||
"http",
|
||||
"http-message",
|
||||
"psr",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-message/tree/1.1"
|
||||
},
|
||||
"time": "2023-04-04T09:50:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-server-handler",
|
||||
"version": "1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-server-handler.git",
|
||||
"reference": "84c4fb66179be4caaf8e97bd239203245302e7d4"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-server-handler/zipball/84c4fb66179be4caaf8e97bd239203245302e7d4",
|
||||
"reference": "84c4fb66179be4caaf8e97bd239203245302e7d4",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.0",
|
||||
"psr/http-message": "^1.0 || ^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Server\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP server-side request handler",
|
||||
"keywords": [
|
||||
"handler",
|
||||
"http",
|
||||
"http-interop",
|
||||
"psr",
|
||||
"psr-15",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response",
|
||||
"server"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/http-server-handler/tree/1.0.2"
|
||||
},
|
||||
"time": "2023-04-10T20:06:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-server-middleware",
|
||||
"version": "1.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-server-middleware.git",
|
||||
"reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-server-middleware/zipball/c1481f747daaa6a0782775cd6a8c26a1bf4a3829",
|
||||
"reference": "c1481f747daaa6a0782775cd6a8c26a1bf4a3829",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.0",
|
||||
"psr/http-message": "^1.0 || ^2.0",
|
||||
"psr/http-server-handler": "^1.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Http\\Server\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP server-side middleware",
|
||||
"keywords": [
|
||||
"http",
|
||||
"http-interop",
|
||||
"middleware",
|
||||
"psr",
|
||||
"psr-15",
|
||||
"psr-7",
|
||||
"request",
|
||||
"response"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/php-fig/http-server-middleware/issues",
|
||||
"source": "https://github.com/php-fig/http-server-middleware/tree/1.0.2"
|
||||
},
|
||||
"time": "2023-04-11T06:14:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/log",
|
||||
"version": "3.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/log.git",
|
||||
"reference": "79dff0b268932c640297f5208d6298f71855c03e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/log/zipball/79dff0b268932c640297f5208d6298f71855c03e",
|
||||
"reference": "79dff0b268932c640297f5208d6298f71855c03e",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\Log\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for logging libraries",
|
||||
"homepage": "https://github.com/php-fig/log",
|
||||
"keywords": [
|
||||
"log",
|
||||
"psr",
|
||||
"psr-3"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/php-fig/log/tree/3.0.1"
|
||||
},
|
||||
"time": "2024-08-21T13:31:24+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ralouphie/getallheaders",
|
||||
"version": "3.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ralouphie/getallheaders.git",
|
||||
"reference": "120b605dfeb996808c31b6477290a714d356e822"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
|
||||
"reference": "120b605dfeb996808c31b6477290a714d356e822",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-coveralls/php-coveralls": "^2.1",
|
||||
"phpunit/phpunit": "^5 || ^6.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/getallheaders.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ralph Khattar",
|
||||
"email": "ralph.khattar@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A polyfill for getallheaders.",
|
||||
"support": {
|
||||
"issues": "https://github.com/ralouphie/getallheaders/issues",
|
||||
"source": "https://github.com/ralouphie/getallheaders/tree/develop"
|
||||
},
|
||||
"time": "2019-03-08T08:55:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "slim/psr7",
|
||||
"version": "1.6.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/slimphp/Slim-Psr7.git",
|
||||
"reference": "5c64088c146673473f5667cbeb501edf5508dfaf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/slimphp/Slim-Psr7/zipball/5c64088c146673473f5667cbeb501edf5508dfaf",
|
||||
"reference": "5c64088c146673473f5667cbeb501edf5508dfaf",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"fig/http-message-util": "^1.1.5",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"psr/http-factory": "^1.0",
|
||||
"psr/http-message": "^1.0",
|
||||
"ralouphie/getallheaders": "^3.0",
|
||||
"symfony/polyfill-php80": "^1.26"
|
||||
},
|
||||
"provide": {
|
||||
"psr/http-factory-implementation": "1.0",
|
||||
"psr/http-message-implementation": "1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"adriansuter/php-autoload-override": "^1.3",
|
||||
"ext-json": "*",
|
||||
"http-interop/http-factory-tests": "^1.0 || ^2.0",
|
||||
"php-http/psr7-integration-tests": "^1.4",
|
||||
"phpspec/prophecy": "^1.15",
|
||||
"phpspec/prophecy-phpunit": "^2.0",
|
||||
"phpstan/phpstan": "^1.8",
|
||||
"phpunit/phpunit": "^9.5 || ^10",
|
||||
"squizlabs/php_codesniffer": "^3.7"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Slim\\Psr7\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Josh Lockhart",
|
||||
"email": "hello@joshlockhart.com",
|
||||
"homepage": "http://joshlockhart.com"
|
||||
},
|
||||
{
|
||||
"name": "Andrew Smith",
|
||||
"email": "a.smith@silentworks.co.uk",
|
||||
"homepage": "http://silentworks.co.uk"
|
||||
},
|
||||
{
|
||||
"name": "Rob Allen",
|
||||
"email": "rob@akrabat.com",
|
||||
"homepage": "http://akrabat.com"
|
||||
},
|
||||
{
|
||||
"name": "Pierre Berube",
|
||||
"email": "pierre@lgse.com",
|
||||
"homepage": "http://www.lgse.com"
|
||||
}
|
||||
],
|
||||
"description": "Strict PSR-7 implementation",
|
||||
"homepage": "https://www.slimframework.com",
|
||||
"keywords": [
|
||||
"http",
|
||||
"psr-7",
|
||||
"psr7"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/slimphp/Slim-Psr7/issues",
|
||||
"source": "https://github.com/slimphp/Slim-Psr7/tree/1.6.2"
|
||||
},
|
||||
"time": "2025-03-23T11:43:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "slim/slim",
|
||||
"version": "4.13.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/slimphp/Slim.git",
|
||||
"reference": "038fd5713d5a41636fdff0e8dcceedecdd17fc17"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/slimphp/Slim/zipball/038fd5713d5a41636fdff0e8dcceedecdd17fc17",
|
||||
"reference": "038fd5713d5a41636fdff0e8dcceedecdd17fc17",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"nikic/fast-route": "^1.3",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"psr/container": "^1.0 || ^2.0",
|
||||
"psr/http-factory": "^1.0",
|
||||
"psr/http-message": "^1.1 || ^2.0",
|
||||
"psr/http-server-handler": "^1.0",
|
||||
"psr/http-server-middleware": "^1.0",
|
||||
"psr/log": "^1.1 || ^2.0 || ^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"adriansuter/php-autoload-override": "^1.4",
|
||||
"ext-simplexml": "*",
|
||||
"guzzlehttp/psr7": "^2.6",
|
||||
"httpsoft/http-message": "^1.1",
|
||||
"httpsoft/http-server-request": "^1.1",
|
||||
"laminas/laminas-diactoros": "^2.17 || ^3",
|
||||
"nyholm/psr7": "^1.8",
|
||||
"nyholm/psr7-server": "^1.1",
|
||||
"phpspec/prophecy": "^1.19",
|
||||
"phpspec/prophecy-phpunit": "^2.1",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^9.6",
|
||||
"slim/http": "^1.3",
|
||||
"slim/psr7": "^1.6",
|
||||
"squizlabs/php_codesniffer": "^3.9"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-simplexml": "Needed to support XML format in BodyParsingMiddleware",
|
||||
"ext-xml": "Needed to support XML format in BodyParsingMiddleware",
|
||||
"php-di/php-di": "PHP-DI is the recommended container library to be used with Slim",
|
||||
"slim/psr7": "Slim PSR-7 implementation. See https://www.slimframework.com/docs/v4/start/installation.html for more information."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Slim\\": "Slim"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Josh Lockhart",
|
||||
"email": "hello@joshlockhart.com",
|
||||
"homepage": "https://joshlockhart.com"
|
||||
},
|
||||
{
|
||||
"name": "Andrew Smith",
|
||||
"email": "a.smith@silentworks.co.uk",
|
||||
"homepage": "http://silentworks.co.uk"
|
||||
},
|
||||
{
|
||||
"name": "Rob Allen",
|
||||
"email": "rob@akrabat.com",
|
||||
"homepage": "http://akrabat.com"
|
||||
},
|
||||
{
|
||||
"name": "Pierre Berube",
|
||||
"email": "pierre@lgse.com",
|
||||
"homepage": "http://www.lgse.com"
|
||||
},
|
||||
{
|
||||
"name": "Gabriel Manricks",
|
||||
"email": "gmanricks@me.com",
|
||||
"homepage": "http://gabrielmanricks.com"
|
||||
}
|
||||
],
|
||||
"description": "Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs",
|
||||
"homepage": "https://www.slimframework.com",
|
||||
"keywords": [
|
||||
"api",
|
||||
"framework",
|
||||
"micro",
|
||||
"router"
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://www.slimframework.com/docs/v4/",
|
||||
"forum": "https://discourse.slimframework.com/",
|
||||
"irc": "irc://irc.freenode.net:6667/slimphp",
|
||||
"issues": "https://github.com/slimphp/Slim/issues",
|
||||
"rss": "https://www.slimframework.com/blog/feed.rss",
|
||||
"slack": "https://slimphp.slack.com/",
|
||||
"source": "https://github.com/slimphp/Slim",
|
||||
"wiki": "https://github.com/slimphp/Slim/wiki"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://opencollective.com/slimphp",
|
||||
"type": "open_collective"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/slim/slim",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-03-03T21:25:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
|
||||
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Php80\\": ""
|
||||
},
|
||||
"classmap": [
|
||||
"Resources/stubs"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ion Bazan",
|
||||
"email": "ion.bazan@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-01-02T08:10:11+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"stability-flags": [],
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": {
|
||||
"php": ">=7.4"
|
||||
},
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Slim\Factory\AppFactory;
|
||||
|
||||
require __DIR__ . '/../src/config.php';
|
||||
require __DIR__ . '/../src/db.php';
|
||||
require __DIR__ . '/../src/jwt.php';
|
||||
|
||||
$app = AppFactory::create();
|
||||
|
||||
// CORS for dev
|
||||
$app->add(function ($request, $handler) {
|
||||
$response = $handler->handle($request);
|
||||
return $response
|
||||
->withHeader('Access-Control-Allow-Origin', '*')
|
||||
->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization')
|
||||
->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
|
||||
});
|
||||
|
||||
$app->addRoutingMiddleware();
|
||||
$app->addErrorMiddleware(true, true, true);
|
||||
|
||||
// Options handler
|
||||
$app->options('/{routes:.+}', function ($request, $response) {
|
||||
return $response;
|
||||
});
|
||||
|
||||
// Init DB
|
||||
getDb();
|
||||
|
||||
// Register routes
|
||||
require __DIR__ . '/../src/routes.php';
|
||||
|
||||
$app->run();
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
return [
|
||||
'db_path' => __DIR__ . '/../chat.db',
|
||||
'jwt_secret' => getenv('JWT_SECRET') ?: 'chat-terminal-secret-key-change-in-production',
|
||||
'password' => getenv('CHAT_PASSWORD') ?: 'admin',
|
||||
];
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
function getDb(): PDO {
|
||||
static $pdo = null;
|
||||
if ($pdo) return $pdo;
|
||||
|
||||
$config = require __DIR__ . '/config.php';
|
||||
$dbPath = $config['db_path'];
|
||||
$isNew = !file_exists($dbPath);
|
||||
|
||||
$pdo = new PDO('sqlite:' . $dbPath);
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
|
||||
$pdo->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false);
|
||||
|
||||
if ($isNew) {
|
||||
initDb($pdo);
|
||||
}
|
||||
|
||||
return $pdo;
|
||||
}
|
||||
|
||||
function initDb(PDO $pdo): void {
|
||||
$pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS conversations (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL DEFAULT '新对话',
|
||||
model_name TEXT,
|
||||
model_base_url TEXT,
|
||||
model_id TEXT,
|
||||
api_key TEXT,
|
||||
temperature REAL DEFAULT 0.7,
|
||||
max_tokens INTEGER DEFAULT 4096,
|
||||
system_prompt TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS messages (
|
||||
id TEXT PRIMARY KEY,
|
||||
conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
||||
role TEXT NOT NULL CHECK(role IN ('user','assistant','system')),
|
||||
content TEXT NOT NULL DEFAULT '',
|
||||
timestamp INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_messages_conversation ON messages(conversation_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_conversations_user ON conversations(user_id);
|
||||
");
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
function jwtBase64UrlEncode(string $data): string {
|
||||
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
|
||||
}
|
||||
|
||||
function jwtBase64UrlDecode(string $data): string {
|
||||
$padding = 4 - strlen($data) % 4;
|
||||
if ($padding !== 4) $data .= str_repeat('=', $padding);
|
||||
return base64_decode(strtr($data, '-_', '+/'));
|
||||
}
|
||||
|
||||
function jwtGenerate(string $userId): string {
|
||||
$config = require __DIR__ . '/config.php';
|
||||
$secret = $config['jwt_secret'];
|
||||
|
||||
$header = json_encode(['typ' => 'JWT', 'alg' => 'HS256']);
|
||||
$time = time();
|
||||
$payload = json_encode([
|
||||
'userId' => $userId,
|
||||
'iat' => $time,
|
||||
'exp' => $time + 604800,
|
||||
]);
|
||||
|
||||
$headerB64 = jwtBase64UrlEncode($header);
|
||||
$payloadB64 = jwtBase64UrlEncode($payload);
|
||||
$signature = hash_hmac('sha256', $headerB64 . '.' . $payloadB64, $secret, true);
|
||||
$sigB64 = jwtBase64UrlEncode($signature);
|
||||
|
||||
return $headerB64 . '.' . $payloadB64 . '.' . $sigB64;
|
||||
}
|
||||
|
||||
function jwtVerify(string $token): ?array {
|
||||
$config = require __DIR__ . '/config.php';
|
||||
$secret = $config['jwt_secret'];
|
||||
|
||||
$parts = explode('.', $token);
|
||||
if (count($parts) !== 3) return null;
|
||||
|
||||
$payload = json_decode(jwtBase64UrlDecode($parts[1]), true);
|
||||
if (!$payload || !isset($payload['exp']) || $payload['exp'] < time()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$signature = hash_hmac('sha256', $parts[0] . '.' . $parts[1], $secret, true);
|
||||
$expected = jwtBase64UrlEncode($signature);
|
||||
|
||||
if (!hash_equals($expected, $parts[2])) return null;
|
||||
|
||||
return $payload;
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
<?php
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
|
||||
// Auth middleware
|
||||
$authMiddleware = function (Request $request, $handler) {
|
||||
$authHeader = $request->getHeaderLine('Authorization');
|
||||
if (!$authHeader || !str_starts_with($authHeader, 'Bearer ')) {
|
||||
$response = new \Slim\Psr7\Response();
|
||||
$response->getBody()->write(json_encode(['error' => 'Unauthorized']));
|
||||
return $response->withStatus(401)->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
$token = substr($authHeader, 7);
|
||||
$decoded = jwtVerify($token);
|
||||
if (!$decoded) {
|
||||
$response = new \Slim\Psr7\Response();
|
||||
$response->getBody()->write(json_encode(['error' => 'Invalid token']));
|
||||
return $response->withStatus(401)->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
$request = $request->withAttribute('userId', $decoded['userId']);
|
||||
return $handler->handle($request);
|
||||
};
|
||||
|
||||
// Health check
|
||||
$app->get('/api/health', function (Request $request, Response $response) {
|
||||
$response->getBody()->write(json_encode(['status' => 'ok']));
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
});
|
||||
|
||||
// Auth routes
|
||||
$app->post('/api/auth/login', function (Request $request, Response $response) {
|
||||
$body = json_decode((string)$request->getBody(), true);
|
||||
$password = $body['password'] ?? '';
|
||||
$config = require __DIR__ . '/config.php';
|
||||
|
||||
if (!$password) {
|
||||
$response->getBody()->write(json_encode(['error' => 'Password required']));
|
||||
return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
if ($password !== $config['password']) {
|
||||
$response->getBody()->write(json_encode(['error' => 'Invalid password']));
|
||||
return $response->withStatus(401)->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
$userId = 'admin';
|
||||
$token = jwtGenerate($userId);
|
||||
$response->getBody()->write(json_encode([
|
||||
'token' => $token,
|
||||
'user' => ['id' => $userId, 'username' => 'admin']
|
||||
]));
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
});
|
||||
|
||||
$app->get('/api/auth/me', function (Request $request, Response $response) {
|
||||
$userId = $request->getAttribute('userId');
|
||||
$response->getBody()->write(json_encode([
|
||||
'id' => $userId,
|
||||
'username' => 'admin'
|
||||
]));
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
})->add($authMiddleware);
|
||||
|
||||
// Conversation routes
|
||||
$app->get('/api/conversations', function (Request $request, Response $response) {
|
||||
$userId = $request->getAttribute('userId');
|
||||
$db = getDb();
|
||||
$stmt = $db->prepare("SELECT id, title, model_name, model_base_url, model_id, temperature, max_tokens, created_at, updated_at FROM conversations WHERE user_id = ? ORDER BY updated_at DESC");
|
||||
$stmt->execute([$userId]);
|
||||
$rows = $stmt->fetchAll();
|
||||
$response->getBody()->write(json_encode($rows));
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
})->add($authMiddleware);
|
||||
|
||||
$app->get('/api/conversations/{id}', function (Request $request, Response $response, array $args) {
|
||||
$userId = $request->getAttribute('userId');
|
||||
$id = $args['id'];
|
||||
$db = getDb();
|
||||
|
||||
$stmt = $db->prepare('SELECT * FROM conversations WHERE id = ? AND user_id = ?');
|
||||
$stmt->execute([$id, $userId]);
|
||||
$conv = $stmt->fetch();
|
||||
if (!$conv) {
|
||||
$response->getBody()->write(json_encode(['error' => 'Conversation not found']));
|
||||
return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
$stmt = $db->prepare('SELECT id, role, content, timestamp FROM messages WHERE conversation_id = ? ORDER BY timestamp ASC');
|
||||
$stmt->execute([$id]);
|
||||
$messages = $stmt->fetchAll();
|
||||
|
||||
$response->getBody()->write(json_encode(array_merge($conv, ['messages' => $messages])));
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
})->add($authMiddleware);
|
||||
|
||||
$app->post('/api/conversations', function (Request $request, Response $response) {
|
||||
$userId = $request->getAttribute('userId');
|
||||
$body = json_decode((string)$request->getBody(), true);
|
||||
$title = $body['title'] ?? '新对话';
|
||||
$modelConfig = $body['modelConfig'] ?? null;
|
||||
|
||||
$db = getDb();
|
||||
$id = bin2hex(random_bytes(16));
|
||||
$now = time() * 1000;
|
||||
|
||||
$stmt = $db->prepare('INSERT INTO conversations (id, user_id, title, model_name, model_base_url, model_id, api_key, temperature, max_tokens, system_prompt, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
|
||||
$stmt->execute([
|
||||
$id, $userId, $title,
|
||||
$modelConfig['name'] ?? null,
|
||||
$modelConfig['baseUrl'] ?? null,
|
||||
$modelConfig['model'] ?? null,
|
||||
$modelConfig['apiKey'] ?? null,
|
||||
$modelConfig['temperature'] ?? 0.7,
|
||||
$modelConfig['maxTokens'] ?? 4096,
|
||||
$modelConfig['systemPrompt'] ?? null,
|
||||
$now, $now
|
||||
]);
|
||||
|
||||
$stmt = $db->prepare('SELECT * FROM conversations WHERE id = ?');
|
||||
$stmt->execute([$id]);
|
||||
$conv = $stmt->fetch();
|
||||
|
||||
$response->getBody()->write(json_encode(array_merge($conv, ['messages' => []])));
|
||||
return $response->withStatus(201)->withHeader('Content-Type', 'application/json');
|
||||
})->add($authMiddleware);
|
||||
|
||||
$app->put('/api/conversations/{id}', function (Request $request, Response $response, array $args) {
|
||||
$userId = $request->getAttribute('userId');
|
||||
$id = $args['id'];
|
||||
$body = json_decode((string)$request->getBody(), true);
|
||||
$title = $body['title'] ?? null;
|
||||
$modelConfig = $body['modelConfig'] ?? null;
|
||||
|
||||
$db = getDb();
|
||||
$stmt = $db->prepare('SELECT id FROM conversations WHERE id = ? AND user_id = ?');
|
||||
$stmt->execute([$id, $userId]);
|
||||
if (!$stmt->fetch()) {
|
||||
$response->getBody()->write(json_encode(['error' => 'Conversation not found']));
|
||||
return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
$now = time() * 1000;
|
||||
$stmt = $db->prepare('UPDATE conversations SET title = COALESCE(?, title), model_name = COALESCE(?, model_name), model_base_url = COALESCE(?, model_base_url), model_id = COALESCE(?, model_id), api_key = COALESCE(?, api_key), temperature = COALESCE(?, temperature), max_tokens = COALESCE(?, max_tokens), system_prompt = COALESCE(?, system_prompt), updated_at = ? WHERE id = ?');
|
||||
$stmt->execute([
|
||||
$title,
|
||||
$modelConfig['name'] ?? null,
|
||||
$modelConfig['baseUrl'] ?? null,
|
||||
$modelConfig['model'] ?? null,
|
||||
$modelConfig['apiKey'] ?? null,
|
||||
$modelConfig['temperature'] ?? null,
|
||||
$modelConfig['maxTokens'] ?? null,
|
||||
$modelConfig['systemPrompt'] ?? null,
|
||||
$now, $id
|
||||
]);
|
||||
|
||||
$stmt = $db->prepare('SELECT * FROM conversations WHERE id = ?');
|
||||
$stmt->execute([$id]);
|
||||
$conv = $stmt->fetch();
|
||||
|
||||
$stmt = $db->prepare('SELECT id, role, content, timestamp FROM messages WHERE conversation_id = ? ORDER BY timestamp ASC');
|
||||
$stmt->execute([$id]);
|
||||
$messages = $stmt->fetchAll();
|
||||
|
||||
$response->getBody()->write(json_encode(array_merge($conv, ['messages' => $messages])));
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
})->add($authMiddleware);
|
||||
|
||||
$app->delete('/api/conversations/{id}', function (Request $request, Response $response, array $args) {
|
||||
$userId = $request->getAttribute('userId');
|
||||
$id = $args['id'];
|
||||
$db = getDb();
|
||||
|
||||
$stmt = $db->prepare('DELETE FROM conversations WHERE id = ? AND user_id = ?');
|
||||
$stmt->execute([$id, $userId]);
|
||||
if ($stmt->rowCount() === 0) {
|
||||
$response->getBody()->write(json_encode(['error' => 'Conversation not found']));
|
||||
return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
$response->getBody()->write(json_encode(['success' => true]));
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
})->add($authMiddleware);
|
||||
|
||||
$app->delete('/api/conversations/{id}/messages', function (Request $request, Response $response, array $args) {
|
||||
$userId = $request->getAttribute('userId');
|
||||
$id = $args['id'];
|
||||
$db = getDb();
|
||||
|
||||
$stmt = $db->prepare('SELECT id FROM conversations WHERE id = ? AND user_id = ?');
|
||||
$stmt->execute([$id, $userId]);
|
||||
if (!$stmt->fetch()) {
|
||||
$response->getBody()->write(json_encode(['error' => 'Conversation not found']));
|
||||
return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
$stmt = $db->prepare('DELETE FROM messages WHERE conversation_id = ?');
|
||||
$stmt->execute([$id]);
|
||||
|
||||
$now = time() * 1000;
|
||||
$stmt = $db->prepare('UPDATE conversations SET updated_at = ? WHERE id = ?');
|
||||
$stmt->execute([$now, $id]);
|
||||
|
||||
$response->getBody()->write(json_encode(['success' => true]));
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
})->add($authMiddleware);
|
||||
|
||||
// Chat route (SSE)
|
||||
$app->post('/api/chat', function (Request $request, Response $response) {
|
||||
$userId = $request->getAttribute('userId');
|
||||
$body = json_decode((string)$request->getBody(), true);
|
||||
$conversationId = $body['conversationId'] ?? '';
|
||||
$content = trim($body['content'] ?? '');
|
||||
|
||||
if (!$conversationId || !$content) {
|
||||
$response->getBody()->write(json_encode(['error' => 'conversationId and content required']));
|
||||
return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
$db = getDb();
|
||||
|
||||
$stmt = $db->prepare('SELECT * FROM conversations WHERE id = ? AND user_id = ?');
|
||||
$stmt->execute([$conversationId, $userId]);
|
||||
$conv = $stmt->fetch();
|
||||
if (!$conv) {
|
||||
$response->getBody()->write(json_encode(['error' => 'Conversation not found']));
|
||||
return $response->withStatus(404)->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
// Load history
|
||||
$stmt = $db->prepare("SELECT role, content FROM messages WHERE conversation_id = ? ORDER BY timestamp ASC");
|
||||
$stmt->execute([$conversationId]);
|
||||
$history = $stmt->fetchAll();
|
||||
|
||||
// Build messages
|
||||
$messages = [];
|
||||
if ($conv['system_prompt']) {
|
||||
$messages[] = ['role' => 'system', 'content' => $conv['system_prompt']];
|
||||
}
|
||||
foreach ($history as $msg) {
|
||||
if ($msg['role'] !== 'system') {
|
||||
$messages[] = ['role' => $msg['role'], 'content' => $msg['content']];
|
||||
}
|
||||
}
|
||||
$messages[] = ['role' => 'user', 'content' => $content];
|
||||
|
||||
// Save user message
|
||||
$userMsgId = bin2hex(random_bytes(16));
|
||||
$now = time() * 1000;
|
||||
$stmt = $db->prepare('INSERT INTO messages (id, conversation_id, role, content, timestamp) VALUES (?, ?, ?, ?, ?)');
|
||||
$stmt->execute([$userMsgId, $conversationId, 'user', $content, $now]);
|
||||
|
||||
// Update title if first message
|
||||
$stmt = $db->prepare('SELECT COUNT(*) as cnt FROM messages WHERE conversation_id = ?');
|
||||
$stmt->execute([$conversationId]);
|
||||
$msgCount = $stmt->fetch()['cnt'] ?? 0;
|
||||
if ($msgCount == 1) {
|
||||
$newTitle = mb_substr($content, 0, 20) . (mb_strlen($content) > 20 ? '...' : '');
|
||||
$stmt = $db->prepare('UPDATE conversations SET title = ?, updated_at = ? WHERE id = ?');
|
||||
$stmt->execute([$newTitle, $now, $conversationId]);
|
||||
}
|
||||
|
||||
// Check model config
|
||||
if (!$conv['model_base_url'] || !$conv['api_key'] || !$conv['model_id']) {
|
||||
$errMsg = 'Error: 模型配置不完整,请先在「配置」中设置 API 地址、API Key 和模型 ID';
|
||||
$errMsgId = bin2hex(random_bytes(16));
|
||||
$stmt = $db->prepare('INSERT INTO messages (id, conversation_id, role, content, timestamp) VALUES (?, ?, ?, ?, ?)');
|
||||
$stmt->execute([$errMsgId, $conversationId, 'assistant', $errMsg, $now]);
|
||||
$response->getBody()->write(json_encode(['error' => 'Model config incomplete']));
|
||||
return $response->withStatus(400)->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
// SSE output
|
||||
header('Content-Type: text/event-stream');
|
||||
header('Cache-Control: no-cache');
|
||||
header('Connection: keep-alive');
|
||||
header('X-Accel-Buffering: no');
|
||||
|
||||
while (ob_get_level() > 0) ob_end_flush();
|
||||
ob_implicit_flush(true);
|
||||
|
||||
// Send user message confirmation
|
||||
echo "data: " . json_encode(['type' => 'user', 'id' => $userMsgId, 'content' => $content]) . "\n\n";
|
||||
flush();
|
||||
|
||||
// Call LLM API via curl
|
||||
$assistantMsgId = bin2hex(random_bytes(16));
|
||||
$fullContent = '';
|
||||
|
||||
$payload = json_encode([
|
||||
'model' => $conv['model_id'],
|
||||
'messages' => $messages,
|
||||
'temperature' => (float)$conv['temperature'],
|
||||
'max_tokens' => (int)$conv['max_tokens'],
|
||||
'stream' => true,
|
||||
]);
|
||||
|
||||
$ch = curl_init($conv['model_base_url'] . '/chat/completions');
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $payload,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Content-Type: application/json',
|
||||
'Authorization: Bearer ' . $conv['api_key'],
|
||||
],
|
||||
CURLOPT_RETURNTRANSFER => false,
|
||||
CURLOPT_WRITEFUNCTION => function ($ch, $data) use (&$fullContent) {
|
||||
$lines = explode("\n", $data);
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if (!$line || !str_starts_with($line, 'data: ')) continue;
|
||||
$jsonStr = substr($line, 6);
|
||||
if ($jsonStr === '[DONE]') continue;
|
||||
$parsed = json_decode($jsonStr, true);
|
||||
if (!$parsed) continue;
|
||||
$delta = $parsed['choices'][0]['delta']['content'] ?? '';
|
||||
if ($delta) {
|
||||
$fullContent .= $delta;
|
||||
echo "data: " . json_encode(['type' => 'delta', 'content' => $delta]) . "\n\n";
|
||||
flush();
|
||||
}
|
||||
}
|
||||
return strlen($data);
|
||||
},
|
||||
CURLOPT_TIMEOUT => 120,
|
||||
CURLOPT_CONNECTTIMEOUT => 30,
|
||||
]);
|
||||
|
||||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_exec($ch);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ($error) {
|
||||
$errMsg = 'Error: ' . $error;
|
||||
echo "data: " . json_encode(['type' => 'error', 'content' => $errMsg]) . "\n\n";
|
||||
}
|
||||
|
||||
// Save assistant message
|
||||
$stmt = $db->prepare('INSERT INTO messages (id, conversation_id, role, content, timestamp) VALUES (?, ?, ?, ?, ?)');
|
||||
$stmt->execute([$assistantMsgId, $conversationId, 'assistant', $fullContent, time() * 1000]);
|
||||
|
||||
$stmt = $db->prepare('UPDATE conversations SET updated_at = ? WHERE id = ?');
|
||||
$stmt->execute([time() * 1000, $conversationId]);
|
||||
|
||||
echo "data: " . json_encode(['type' => 'done', 'id' => $assistantMsgId, 'content' => $fullContent]) . "\n\n";
|
||||
echo "data: [DONE]\n\n";
|
||||
flush();
|
||||
|
||||
exit(0);
|
||||
})->add($authMiddleware);
|
||||
Reference in New Issue
Block a user