├── .dockerignore ├── .github └── workflows │ └── docker-publish.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── app.config.ts ├── bun.lock ├── docker-compose.yml ├── docs ├── DOCKER.md └── SELFHOST.md ├── instant.perms.ts ├── instant.schema.ts ├── package.json ├── patches └── nitropack@2.10.4.patch ├── postcss.config.cjs ├── public ├── banner.png ├── favicon.svg ├── gaussian_noise.png ├── icon.jpg ├── logo.svg ├── name.svg ├── og.png ├── profile.svg ├── robots.txt ├── screenshot.jpg ├── screenshot2.jpg ├── screenshot3.jpg ├── sitemap.xml └── whitehouse.jpg ├── src ├── app.css ├── app.tsx ├── client │ ├── accept.ts │ ├── database.tsx │ └── utils.tsx ├── components │ ├── AIComp.tsx │ ├── BlockComp.tsx │ ├── BuyComp.tsx │ ├── BuySellComp.tsx │ ├── CheckBox.tsx │ ├── CheckboxItem.tsx │ ├── IconComp.tsx │ ├── Image.tsx │ ├── InfiniteScroll.tsx │ ├── MarketCard.tsx │ ├── MarketImage.tsx │ ├── MarketSocialComp.tsx │ ├── Markets.tsx │ ├── MartketChart.tsx │ ├── Nav.tsx │ ├── NewsItem.tsx │ ├── OptionImage.tsx │ ├── OptionItem.tsx │ ├── ProfileImage.tsx │ ├── SellComp.tsx │ ├── Spinner.tsx │ ├── TranslatingGroup.tsx │ └── buysell │ │ └── Header.tsx ├── entry-client.tsx ├── entry-server.tsx ├── routes │ ├── [...404].tsx │ ├── api │ │ ├── alive │ │ │ └── index.tsx │ │ ├── complete │ │ │ └── index.ts │ │ ├── history__options │ │ │ └── [id] │ │ │ │ └── index.tsx │ │ ├── markets │ │ │ └── [id] │ │ │ │ └── vote │ │ │ │ └── index.ts │ │ ├── options │ │ │ └── [id] │ │ │ │ └── action │ │ │ │ └── index.tsx │ │ └── profiles │ │ │ └── jwt │ │ │ └── index.tsx │ ├── index.tsx │ ├── market │ │ └── [id].tsx │ └── profile │ │ └── [id].tsx ├── server │ ├── chat-utils.ts │ └── utils.ts ├── shared │ ├── BLOCKS.json │ ├── tools │ │ ├── createMarket.ts │ │ ├── index.ts │ │ ├── searchImages.ts │ │ ├── searchWeb.ts │ │ └── utils.ts │ └── utils.tsx └── types │ ├── brave_search_image.d.ts │ ├── brave_search_news.d.ts │ ├── brave_search_web.d.ts │ ├── global.d.ts │ └── window.d.ts ├── tailwind.config.cjs └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | *.env 4 | .DS_Store 5 | .vscode 6 | .git 7 | -------------------------------------------------------------------------------- /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Build and Push Docker Image 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | push_to_docker_hub: 10 | name: Build and Push to Docker Hub 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | packages: write 15 | attestations: write 16 | id-token: write 17 | 18 | steps: 19 | - name: Check out repository 20 | uses: actions/checkout@v4 21 | 22 | - name: Log in to Docker Hub 23 | uses: docker/login-action@v3 24 | with: 25 | username: ${{ secrets.DOCKER_USERNAME }} 26 | password: ${{ secrets.DOCKER_PASSWORD }} 27 | 28 | - name: Extract metadata (tags, labels) 29 | id: meta 30 | uses: docker/metadata-action@v5 31 | with: 32 | images: implyapp/imply 33 | 34 | - name: Build and push Docker image 35 | id: docker_build 36 | uses: docker/build-push-action@v5 37 | with: 38 | context: . 39 | file: ./Dockerfile 40 | push: true 41 | tags: | 42 | implyapp/imply:latest 43 | labels: ${{ steps.meta.outputs.labels }} 44 | 45 | - name: Generate artifact attestation 46 | uses: actions/attest-build-provenance@v2 47 | with: 48 | subject-name: implyapp/imply 49 | subject-digest: ${{ steps.docker_build.outputs.digest }} 50 | 51 | - name: Trigger Render Deploy 52 | run: | 53 | curl -X POST "https://api.render.com/deploy/srv-cugkir23esus73b1s9d0?key=${{ secrets.RENDER_DEPLOY_KEY }}&imgURL=docker.io%2Fimplyapp%2Fimply%3Alatest" 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | dist 3 | .solid 4 | .output 5 | .vercel 6 | .netlify 7 | .vinxi 8 | app.config.timestamp_*.js 9 | 10 | # Environment 11 | .env 12 | .env*.local 13 | 14 | # dependencies 15 | /node_modules 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | *.launch 22 | .settings/ 23 | 24 | # Temp 25 | gitignore 26 | 27 | # System Files 28 | .DS_Store 29 | Thumbs.db 30 | 31 | .wrangler 32 | wrangler.json 33 | wrangler.toml 34 | .*.vars -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # use the official Bun image 2 | # see all versions at https://hub.docker.com/r/oven/bun/tags 3 | FROM oven/bun:latest as base 4 | WORKDIR /usr/src/app 5 | 6 | # install production dependencies 7 | FROM base AS install 8 | RUN mkdir -p /temp/prod 9 | COPY package.json bun.lock /temp/prod/ 10 | COPY patches/ /temp/prod/patches/ 11 | RUN cd /temp/prod && bun install --frozen-lockfile 12 | 13 | # copy production dependencies and source code into final image 14 | FROM base AS release 15 | COPY --from=install /temp/prod/node_modules node_modules 16 | COPY . . 17 | 18 | # [optional] tests & build (if needed for production build) 19 | ENV NODE_ENV=production 20 | RUN bun run build 21 | 22 | # run the app 23 | USER bun 24 | EXPOSE 3000/tcp 25 | ENTRYPOINT [ "bun", "run", "start" ] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Tri Nguyen 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |
6 | 7 | Discord 8 | 9 |
10 | 11 | ## Imply 12 | 13 | A prediction market is like a stock market, but instead of buying and selling company shares, people trade guesses about future events. The price of each guess shows how likely people think that event is to happen. If you predict correctly, you make money; if you're wrong, you lose. This helps gather the opinions of many people to make better predictions. 14 | 15 | Imply make prediction markets easy to use & understand. The app is live here [https://imply.app](https://imply.app). 16 | 17 | ### Currently working on: 18 | - [x] 🤖 AI Agents 19 | - [x] 📚 can research & create prediction markets 20 | - [ ] 🔀 can merge similar markets 21 | - [ ] 🔍 can resolve markets (i.e. verifying sources) 22 | - [ ] 💼 can buy/sell shares on users' behalf 23 | - [x] 📈 Prediction markets 24 | - [x] ⚙️ Automated Market Markers (CPMM) 25 | - [x] ⏱️ Real-time price update 26 | - [ ] 🖥️ Simplified trading UI/UX 27 | 28 | ### Quick Demo (Youtube) 29 | 30 | [![Quick Demo](https://img.youtube.com/vi/x3VCd4FStJU/0.jpg)](https://www.youtube.com/watch?v=x3VCd4FStJU) 31 | 32 | ## Self-Hosting 33 | 34 | To self-host Imply, follow the [self-hosting guide](/docs/SELFHOST.md). 35 | 36 | ## License 37 | 38 | Imply is released under the **MIT License**. See [LICENSE](LICENSE) for details. 39 | 40 | ## Support 41 | 42 | For any questions or support, join our [Discord](https://discord.gg/XakeDSQSxc) or email **hi@imply.app**. 43 | -------------------------------------------------------------------------------- /app.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "@solidjs/start/config"; 2 | 3 | export default defineConfig({ 4 | server: { 5 | preset: 'bun', 6 | rollupConfig: { 7 | external: ["jose", "@instantdb/core", "@instantdb/admin"], 8 | } 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | app: 5 | image: imply 6 | ports: 7 | - "3000:3000" 8 | env_file: 9 | - .env 10 | restart: unless-stopped 11 | -------------------------------------------------------------------------------- /docs/DOCKER.md: -------------------------------------------------------------------------------- 1 | # Quick Guide to Running **Imply** with Docker 2 | 3 | Some useful Docker commands: 4 | 5 | 1. Build the Docker image for **Imply**. 6 | 7 | ```sh 8 | docker build -t docker.io/implyapp/imply . 9 | ``` 10 | 11 | 2. Use the following to start the application: 12 | 13 | ```sh 14 | docker-compose up 15 | ``` 16 | 17 | 3. To run **Imply** in a detached container: 18 | 19 | ```sh 20 | docker run -d --name imply --env-file .env -p 3000:3000 implyapp/imply 21 | ``` 22 | -------------------------------------------------------------------------------- /docs/SELFHOST.md: -------------------------------------------------------------------------------- 1 | # Self-Hosting Guide 2 | 3 | This document outlines the steps required to run **Imply**, either via Docker (recommended) or by building yourself. Both methods require setting up the `.env` file. 4 | 5 | ## Create an `.env` file with the following keys: 6 | 7 | ```sh 8 | BUN_VERSION= 9 | 10 | # AI 11 | OPENAI_API_KEY= 12 | OPENAI_BASE_URL= 13 | OPENAI_MODEL= 14 | REASONING_MODEL= 15 | 16 | # Internet 17 | BRAVE_SEARCH_API_KEY= 18 | 19 | # Database 20 | JWT_SECRET_KEY= 21 | INSTANTDB_APP_ID= 22 | INSTANT_APP_ADMIN_TOKEN= 23 | 24 | # Analytics 25 | POSTHOG_TOKEN= 26 | ``` 27 | 28 | ### 🔑 Key Details: 29 | 30 | 1. **BUN_VERSION**: Required for using Bun with Cloudflare Pages. Set it to `1.1.38` if unsure. 31 | 2. **InstantDB**: Imply uses **InstantDB**. Since InstantDB is open-source, you can self-host it or use their free & unlimited cloud version. To set it up: 32 | 33 | ```sh 34 | # In the root folder 35 | npx instant-cli@latest push schema 36 | # Choose "Create a new app" and push the schema 37 | ``` 38 | 39 | Afterward, visit the **InstantDB dashboard** to get `INSTANTDB_APP_ID` and `INSTANT_APP_ADMIN_TOKEN`. 40 | 41 | 3. **JWT_SECRET_KEY**: Generate this key with the following command: 42 | 43 | ```sh 44 | bun -e "console.log(require('crypto').randomBytes(32).toString('hex'))" 45 | ``` 46 | 47 | 4. **BRAVE_SEARCH_API_KEY**: Get a free key from [Brave](https://brave.com/search/api/). 48 | 49 | 5. **OPENAI_API_KEY**: API key to use with OpenAI SDK. **OPENAI_MODEL** and **OPENAI_BASE_URL** are optional. In production, we use Open Router to mix & match multiple models, so our `.env` looks like this: 50 | 51 | ```sh 52 | OPENAI_API_KEY= 53 | OPENAI_BASE_URL=https://openrouter.ai/api/v1 54 | OPENAI_MODEL=openai/gpt-4o-mini 55 | REASONING_MODEL=deepseek/deepseek-r1 56 | ``` 57 | 58 | 6. **POSTHOG_TOKEN**: This is optional & used for analytics purposes. 59 | 60 | ## Method 1: Via Docker 61 | 62 | The easiest way to get Imply running on your machine is via Docker. 63 | 64 | ```sh 65 | docker run -d --name imply --env-file .env -p 3000:3000 implyapp/imply 66 | ``` 67 | 68 | ## Method 2: Cloning and Building 69 | 70 | Imply is a web app built using the JavaScript framework **Solid Start**. 71 | If you don't have **Bun** installed, we recommend doing so. 72 | 73 | ### 1. Clone & Install Dependencies 74 | 75 | ```sh 76 | git clone https://github.com/tri2820/imply 77 | cd imply 78 | bun i 79 | ``` 80 | 81 | ### 2. Add the `.env` file 82 | 83 | Make sure you have the `.env` file created as described in the previous section & put it in the root folder. 84 | 85 | ### 3. Build & Run the App 86 | 87 | ```sh 88 | bun run build 89 | bun run start 90 | ``` 91 | -------------------------------------------------------------------------------- /instant.perms.ts: -------------------------------------------------------------------------------- 1 | // Docs: https://www.instantdb.com/docs/permissions 2 | 3 | import type { InstantRules } from "@instantdb/core"; 4 | 5 | const rules = { 6 | /** 7 | * Welcome to Instant's permission system! 8 | * Right now your rules are empty. To start filling them in, check out the docs: 9 | * https://www.instantdb.com/docs/permissions 10 | * 11 | * Here's an example to give you a feel: 12 | * posts: { 13 | * allow: { 14 | * view: "true", 15 | * create: "isOwner", 16 | * update: "isOwner", 17 | * delete: "isOwner", 18 | * }, 19 | * bind: ["isOwner", "auth.id != null && auth.id == data.ownerId"], 20 | * }, 21 | */ 22 | } satisfies InstantRules; 23 | 24 | export default rules; 25 | -------------------------------------------------------------------------------- /instant.schema.ts: -------------------------------------------------------------------------------- 1 | import { i } from "@instantdb/core"; 2 | 3 | const _schema = i.schema({ 4 | // This section lets you define entities: think `posts`, `comments`, etc 5 | // Take a look at the docs to learn more: 6 | // https://www.instantdb.com/docs/modeling-data#2-attributes 7 | entities: { 8 | $users: i.entity({ 9 | email: i.string().unique().indexed(), 10 | }), 11 | shares: i.entity({ 12 | type: i.string(), // Yes or No 13 | reserve: i.number(), 14 | }), 15 | options: i.entity({ 16 | name: i.string(), 17 | color: i.string(), 18 | image: i.string(), 19 | }), 20 | markets: i.entity({ 21 | name: i.string(), 22 | description: i.string(), 23 | image: i.string(), 24 | allow_multiple_correct: i.boolean(), 25 | created_at: i.date(), 26 | resolve_at: i.date(), 27 | stop_trading_at: i.date(), 28 | rule: i.string(), 29 | 30 | // social 31 | // faster (don't have to aggregate) 32 | // Only indexed and type-checked attrs can be used to order by. 33 | num_votes: i.number().indexed(), 34 | }), 35 | holdings: i.entity({ 36 | amount: i.number(), 37 | updated_at: i.date(), 38 | }), 39 | profiles: i.entity({ 40 | name: i.string(), 41 | avatar_src: i.string(), 42 | usd: i.number(), 43 | }), 44 | history__options: i.entity({ 45 | option_id: i.string(), 46 | created_at: i.date(), 47 | yesProb: i.number(), 48 | }), 49 | conversations: i.entity({ 50 | name: i.string(), 51 | }), 52 | blocks: i.entity({ 53 | created_at: i.date(), 54 | updated_at: i.date(), 55 | content: i.json(), 56 | role: i.string(), 57 | agent_step: i.string().optional(), 58 | }), 59 | 60 | votes: i.entity({ 61 | isUpvote: i.boolean(), 62 | }) 63 | }, 64 | // You can define links here. 65 | // For example, if `posts` should have many `comments`. 66 | // More in the docs: 67 | // https://www.instantdb.com/docs/modeling-data#3-links 68 | links: { 69 | profile_votes: { 70 | forward: { 71 | on: "profiles", 72 | has: "many", 73 | label: "votes", 74 | }, 75 | reverse: { 76 | on: "votes", 77 | has: "one", 78 | label: "profile", 79 | }, 80 | }, 81 | market_votes: { 82 | forward: { 83 | on: "markets", 84 | has: "many", 85 | label: "votes", 86 | }, 87 | reverse: { 88 | on: "votes", 89 | has: "one", 90 | label: "market", 91 | }, 92 | }, 93 | options_shares: { 94 | forward: { 95 | on: "options", 96 | has: "many", 97 | label: "shares", 98 | }, 99 | reverse: { 100 | on: "shares", 101 | has: "one", 102 | label: "option", 103 | }, 104 | }, 105 | markets_options: { 106 | forward: { 107 | on: "markets", 108 | has: "many", 109 | label: "options", 110 | }, 111 | reverse: { 112 | on: "options", 113 | has: "one", 114 | label: "market", 115 | }, 116 | }, 117 | profiles_holdings: { 118 | forward: { 119 | on: "profiles", 120 | has: "many", 121 | label: "holdings", 122 | }, 123 | reverse: { 124 | on: "holdings", 125 | has: "one", 126 | label: "profile", 127 | }, 128 | }, 129 | holdings_shares: { 130 | forward: { 131 | on: "holdings", 132 | has: "one", 133 | label: "share", 134 | }, 135 | reverse: { 136 | on: "shares", 137 | has: "many", 138 | label: "holdings", 139 | }, 140 | }, 141 | profile_blocks: { 142 | forward: { 143 | on: "profiles", 144 | has: "many", 145 | label: "blocks", 146 | }, 147 | reverse: { 148 | on: "blocks", 149 | has: "one", 150 | label: "profile", 151 | }, 152 | } 153 | }, 154 | // If you use presence, you can define a room schema here 155 | // https://www.instantdb.com/docs/presence-and-topics#typesafety 156 | rooms: {}, 157 | }); 158 | 159 | // This helps Typescript display nicer intellisense 160 | type _AppSchema = typeof _schema; 161 | interface AppSchema extends _AppSchema { } 162 | const schema: AppSchema = _schema; 163 | 164 | export type { AppSchema }; 165 | export default schema; 166 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "imply", 3 | "type": "module", 4 | "scripts": { 5 | "dev": "vinxi dev", 6 | "build": "vinxi build", 7 | "start": "bunx --bun vinxi start" 8 | }, 9 | "dependencies": { 10 | "@fontsource-variable/lexend": "^5.1.2", 11 | "@fontsource/poppins": "^5.1.1", 12 | "@instantdb/admin": "^0.17.9", 13 | "@instantdb/core": "^0.17.9", 14 | "@solidjs/router": "^0.15.0", 15 | "@solidjs/start": "^1.0.11", 16 | "date-fns": "^4.1.0", 17 | "jose": "^5.9.6", 18 | "json-stream-es": "^1.2.1", 19 | "lightweight-charts": "^4.2.2", 20 | "marked": "^15.0.6", 21 | "openai": "^4.80.0", 22 | "posthog-js": "^1.215.3", 23 | "solid-icons": "^1.1.0", 24 | "solid-js": "^1.9.2", 25 | "streaming-iterables": "^8.0.1", 26 | "vinxi": "^0.4.3", 27 | "zod": "^3.24.1", 28 | "zod-to-json-schema": "^3.24.1" 29 | }, 30 | "devDependencies": { 31 | "@tailwindcss/typography": "^0.5.16", 32 | "autoprefixer": "^10.4.19", 33 | "postcss": "^8.4.38", 34 | "tailwindcss": "^3.4.3" 35 | }, 36 | "overrides": { 37 | "vite": "5.4.10" 38 | }, 39 | "engines": { 40 | "node": ">=18" 41 | }, 42 | "patchedDependencies": { 43 | "nitropack@2.10.4": "patches/nitropack@2.10.4.patch" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /patches/nitropack@2.10.4.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/nitropack/.bun-tag-d7ccf2a00e004218 b/.bun-tag-d7ccf2a00e004218 2 | new file mode 100644 3 | index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 4 | diff --git a/dist/presets/bun/runtime/bun.mjs b/dist/presets/bun/runtime/bun.mjs 5 | index e46d1616dc6d6efa473e2d53a0884efc83fd7867..209bf44fbf2ef69763d13e055f0e6bee84e83857 100644 6 | --- a/dist/presets/bun/runtime/bun.mjs 7 | +++ b/dist/presets/bun/runtime/bun.mjs 8 | @@ -5,6 +5,7 @@ import wsAdapter from "crossws/adapters/bun"; 9 | const nitroApp = useNitroApp(); 10 | const ws = import.meta._websocket ? wsAdapter(nitroApp.h3App.websocket) : void 0; 11 | const server = Bun.serve({ 12 | + idleTimeout: 0, 13 | port: process.env.NITRO_PORT || process.env.PORT || 3e3, 14 | websocket: import.meta._websocket ? ws.websocket : void 0, 15 | async fetch(req, server2) { 16 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tri2820/imply/a4f7d68dd4e35af68010af36ad28faf4210eb439/public/banner.png -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/gaussian_noise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tri2820/imply/a4f7d68dd4e35af68010af36ad28faf4210eb439/public/gaussian_noise.png -------------------------------------------------------------------------------- /public/icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tri2820/imply/a4f7d68dd4e35af68010af36ad28faf4210eb439/public/icon.jpg -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/name.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tri2820/imply/a4f7d68dd4e35af68010af36ad28faf4210eb439/public/og.png -------------------------------------------------------------------------------- /public/profile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # robots.txt for imply.app 2 | 3 | User-agent: * 4 | Disallow: /admin/ 5 | Disallow: /api/ 6 | Disallow: /private/ 7 | Disallow: /tmp/ 8 | Allow: / 9 | 10 | # Sitemap 11 | Sitemap: https://imply.app/sitemap.xml -------------------------------------------------------------------------------- /public/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tri2820/imply/a4f7d68dd4e35af68010af36ad28faf4210eb439/public/screenshot.jpg -------------------------------------------------------------------------------- /public/screenshot2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tri2820/imply/a4f7d68dd4e35af68010af36ad28faf4210eb439/public/screenshot2.jpg -------------------------------------------------------------------------------- /public/screenshot3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tri2820/imply/a4f7d68dd4e35af68010af36ad28faf4210eb439/public/screenshot3.jpg -------------------------------------------------------------------------------- /public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | https://imply.app/ 6 | 2025-01-26 7 | weekly 8 | 1.0 9 | 10 | 11 | 39 | 40 | -------------------------------------------------------------------------------- /public/whitehouse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tri2820/imply/a4f7d68dd4e35af68010af36ad28faf4210eb439/public/whitehouse.jpg -------------------------------------------------------------------------------- /src/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --background-rgb: 19, 19, 19; 7 | --foreground-rgb: 255, 255, 255; 8 | } 9 | 10 | body { 11 | /* font-family: "Inter", sans-serif; */ 12 | font-family: 'Poppins', sans-serif; 13 | background: rgb(var(--background-rgb)); 14 | color: rgb(var(--foreground-rgb)); 15 | 16 | margin: 0; 17 | padding: 0; 18 | overflow-x: hidden; 19 | position: relative; 20 | } 21 | 22 | .grain { 23 | position: fixed; 24 | top: 0; 25 | left: 0; 26 | height: 100%; 27 | width: 100%; 28 | pointer-events: none; 29 | z-index: -10; 30 | transform: translateZ(0); 31 | } 32 | 33 | .grain::before { 34 | content: ""; 35 | top: -10rem; 36 | left: -10rem; 37 | width: calc(100% + 20rem); 38 | height: calc(100% + 20rem); 39 | z-index: 9999; 40 | position: fixed; 41 | background-image: url(/gaussian_noise.png); 42 | opacity: 0.05; 43 | pointer-events: none; 44 | } 45 | 46 | .dots-bg { 47 | width: 100%; 48 | height: 100%; 49 | background-image: radial-gradient(circle, #404040 2px, transparent 1px); 50 | background-size: 60px 40px; 51 | z-index: -1; 52 | } 53 | 54 | .no-scrollbar { 55 | scrollbar-width: none; 56 | -ms-overflow-style: none; 57 | } 58 | 59 | .checkbox-no-style { 60 | -webkit-appearance: none; 61 | -moz-appearance: none; 62 | appearance: none; 63 | outline: none; 64 | border: none; 65 | background: none; 66 | } 67 | 68 | /* Chrome, Safari, Edge, Opera */ 69 | input::-webkit-outer-spin-button, 70 | input::-webkit-inner-spin-button { 71 | -webkit-appearance: none; 72 | margin: 0; 73 | } 74 | 75 | /* Firefox */ 76 | input[type=number] { 77 | -moz-appearance: textfield; 78 | } 79 | 80 | .table-container { 81 | @apply bg-neutral-900 rounded-lg border border-neutral-800 overflow-hidden; 82 | } 83 | 84 | .table-container thead { 85 | @apply text-left p-4; 86 | } 87 | 88 | .table-container tr { 89 | @apply border-b border-neutral-800; 90 | } 91 | 92 | .table-container td, 93 | .table-container th { 94 | @apply p-4; 95 | } 96 | 97 | .table-container th { 98 | @apply bg-neutral-800; 99 | } 100 | 101 | 102 | @keyframes translating { 103 | 0% { 104 | transform: translateX(0); 105 | } 106 | 107 | 100% { 108 | transform: translateX(-100%); 109 | } 110 | } 111 | 112 | .font-lexend { 113 | font-family: 'Lexend Variable', sans-serif; 114 | } 115 | 116 | span.avoidwrap { 117 | display: inline-block; 118 | } -------------------------------------------------------------------------------- /src/app.tsx: -------------------------------------------------------------------------------- 1 | import { Router } from "@solidjs/router"; 2 | import { FileRoutes } from "@solidjs/start/router"; 3 | import { onCleanup, onMount, Suspense } from "solid-js"; 4 | 5 | import "./app.css"; 6 | 7 | // Supports weights 100-900 8 | import "@fontsource-variable/lexend"; 9 | import "@fontsource/poppins/100.css"; 10 | import "@fontsource/poppins/200.css"; 11 | import "@fontsource/poppins/300.css"; 12 | import "@fontsource/poppins/400.css"; 13 | import "@fontsource/poppins/500.css"; 14 | import "@fontsource/poppins/600.css"; 15 | import "@fontsource/poppins/700.css"; 16 | import "@fontsource/poppins/800.css"; 17 | import "@fontsource/poppins/900.css"; 18 | import Nav from "./components/Nav"; 19 | import { db } from "./client/database"; 20 | import { setProfileSubscription } from "./client/utils"; 21 | import posthog from "posthog-js"; 22 | 23 | export default function App() { 24 | onMount(() => { 25 | if (!window.env.POSTHOG_TOKEN) return; 26 | console.log("connecting to posthog", window.env.POSTHOG_TOKEN); 27 | posthog.init(window.env.POSTHOG_TOKEN, { 28 | api_host: "https://eu.i.posthog.com", 29 | person_profiles: "identified_only", // or 'always' to create profiles for anonymous users as well 30 | }); 31 | }); 32 | 33 | function subscribeProfile(profile_id: string) { 34 | console.log("subscribeProfile", profile_id); 35 | return db.subscribeQuery( 36 | { 37 | profiles: { 38 | $: { 39 | where: { 40 | id: profile_id, 41 | }, 42 | }, 43 | holdings: { 44 | share: { 45 | // get the type 46 | }, 47 | }, 48 | }, 49 | }, 50 | (resp) => { 51 | console.log("profile sub resp", resp); 52 | setProfileSubscription(resp); 53 | } 54 | ); 55 | } 56 | 57 | onMount(async () => { 58 | let unsub: Function; 59 | onCleanup(() => { 60 | unsub?.(); 61 | }); 62 | 63 | try { 64 | const resp = await fetch("/api/profiles/jwt", { 65 | method: "GET", 66 | }); 67 | console.log("profile_jwt resp", resp); 68 | if (!resp.ok) throw new Error("fetch profile_jwt failed"); 69 | const json: JWTResult = await resp.json(); 70 | console.log("profile", json); 71 | unsub = subscribeProfile(json.profile_id); 72 | } catch (e) { 73 | console.error("error fetch profiles key", e); 74 | } 75 | }); 76 | 77 | return ( 78 | ( 80 |
81 |
82 |
85 | )} 86 | > 87 | 88 | 89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /src/client/accept.ts: -------------------------------------------------------------------------------- 1 | 2 | import { ToolName } from "~/shared/tools/utils"; 3 | import { db } from "./database"; 4 | import { blocks, profile, setBlocks } from "./utils"; 5 | import { seededUUIDv4 } from "~/shared/utils"; 6 | 7 | async function accept_content(content: NonNullable, agent_step: AgentStep) { 8 | let assistantBlock: AssistantBlock | undefined = undefined; 9 | if (content.started) { 10 | assistantBlock = { 11 | agent_step, 12 | id: content.started.id, 13 | role: "assistant", 14 | content: content.started.text, 15 | // created_at: content.started.created_at, 16 | // updated_at: content.started.created_at, 17 | created_at: new Date().toISOString(), 18 | updated_at: new Date().toISOString(), 19 | } 20 | } 21 | 22 | if (content.delta) { 23 | assistantBlock = blocks()[content.delta.id] as AssistantBlock; 24 | assistantBlock.content += content.delta.text; 25 | // assistantBlock.updated_at = content.delta.updated_at; 26 | assistantBlock.updated_at = new Date().toISOString() 27 | } 28 | 29 | if (content.done) { 30 | assistantBlock = blocks()[content.done.id] as AssistantBlock; 31 | assistantBlock.updated_at = new Date().toISOString() 32 | const instantdb_id = seededUUIDv4(assistantBlock.id); 33 | db.transact([db.tx.blocks[instantdb_id].update(assistantBlock).link({ 34 | profile: profile()?.id 35 | })]); 36 | } 37 | 38 | if (assistantBlock) { 39 | setBlocks((blocks) => { 40 | return { 41 | ...blocks, 42 | [assistantBlock.id]: { 43 | ...assistantBlock 44 | }, 45 | }; 46 | }); 47 | } 48 | } 49 | 50 | 51 | async function accept_reasoning(reasoning: NonNullable, agent_step: AgentStep) { 52 | let reasoningBlock: ReasoningBlock | undefined = undefined; 53 | if (reasoning.started) { 54 | reasoningBlock = { 55 | agent_step, 56 | id: reasoning.started.id, 57 | role: "reasoning", 58 | content: reasoning.started.text, 59 | // created_at: content.started.created_at, 60 | // updated_at: content.started.created_at, 61 | created_at: new Date().toISOString(), 62 | updated_at: new Date().toISOString(), 63 | } 64 | } 65 | 66 | if (reasoning.delta) { 67 | reasoningBlock = blocks()[reasoning.delta.id] as ReasoningBlock; 68 | reasoningBlock.content += reasoning.delta.text; 69 | // assistantBlock.updated_at = content.delta.updated_at; 70 | reasoningBlock.updated_at = new Date().toISOString() 71 | } 72 | 73 | if (reasoning.done) { 74 | reasoningBlock = blocks()[reasoning.done.id] as ReasoningBlock; 75 | reasoningBlock.updated_at = new Date().toISOString() 76 | const instantdb_id = seededUUIDv4(reasoningBlock.id); 77 | db.transact([db.tx.blocks[instantdb_id].update(reasoningBlock).link({ 78 | profile: profile()?.id 79 | })]); 80 | } 81 | 82 | if (reasoningBlock) { 83 | setBlocks((blocks) => { 84 | return { 85 | ...blocks, 86 | [reasoningBlock.id]: { 87 | ...reasoningBlock 88 | }, 89 | }; 90 | }); 91 | } 92 | } 93 | 94 | 95 | 96 | export async function accept_tool(tool: NonNullable, agent_step: AgentStep) { 97 | let toolBlock: ToolBlock | undefined = undefined; 98 | 99 | if (tool.started) { 100 | toolBlock = { 101 | agent_step, 102 | role: 'tool', 103 | id: tool.started.id, 104 | content: { 105 | doings: [], 106 | arguments_partial_str: '', 107 | name: tool.started.name as ToolName, 108 | }, 109 | // created_at: tool.started.created_at, 110 | // updated_at: tool.started.created_at 111 | created_at: new Date().toISOString(), 112 | updated_at: new Date().toISOString() 113 | } 114 | } 115 | 116 | 117 | if (tool.delta) { 118 | toolBlock = blocks()[tool.delta.id] as ToolBlock; 119 | toolBlock.content.arguments_partial_str += tool.delta.arguments_delta; 120 | // toolBlock.updated_at = tool.delta.updated_at; 121 | toolBlock.updated_at = new Date().toISOString() 122 | } 123 | 124 | 125 | if (tool.done) { 126 | toolBlock = blocks()[tool.done.id] as ToolBlock 127 | toolBlock.content.arguments = tool.done.arguments; 128 | const instantdb_id = seededUUIDv4(toolBlock.id); 129 | db.transact([db.tx.blocks[instantdb_id].update(toolBlock).link({ 130 | profile: profile()?.id 131 | })]); 132 | } 133 | 134 | if (toolBlock) { 135 | setBlocks((blocks) => { 136 | return { 137 | ...blocks, 138 | [toolBlock.id]: { 139 | ...toolBlock 140 | }, 141 | }; 142 | }); 143 | } 144 | 145 | } 146 | 147 | export async function accept_tool_yield(tool: ToolYieldWithId, agent_step: AgentStep) { 148 | console.log('accept_tool_yield tool is toolYield', tool) 149 | let toolBlock: ToolBlock | undefined; 150 | if (tool.done) { 151 | toolBlock = blocks()[tool.id] as ToolBlock 152 | toolBlock.content.result = tool.done; 153 | const instantdb_id = seededUUIDv4(toolBlock.id); 154 | db.transact([db.tx.blocks[instantdb_id].update(toolBlock).link({ 155 | profile: profile()?.id 156 | })]); 157 | } else if (tool.doing) { 158 | console.log('tool.doing', tool.doing) 159 | toolBlock = blocks()[tool.id] as ToolBlock 160 | toolBlock.content.doings = [...toolBlock.content.doings, tool.doing]; 161 | } 162 | 163 | console.log('accept_tool_yield toolBlock', toolBlock) 164 | if (toolBlock) { 165 | setBlocks((blocks) => { 166 | return { 167 | ...blocks, 168 | [toolBlock.id]: { 169 | ...toolBlock 170 | }, 171 | }; 172 | }); 173 | } 174 | } 175 | 176 | export async function accept(y: ChatStreamYield) { 177 | console.log('y', y.agent_step) 178 | if (y.reasoning) { 179 | accept_reasoning(y.reasoning, y.agent_step) 180 | } 181 | 182 | if (y.content) { 183 | accept_content(y.content, y.agent_step) 184 | } 185 | 186 | if (y.tool) { 187 | accept_tool(y.tool, y.agent_step) 188 | } 189 | 190 | if (y.tool_yield) { 191 | accept_tool_yield(y.tool_yield, y.agent_step) 192 | } 193 | } -------------------------------------------------------------------------------- /src/client/database.tsx: -------------------------------------------------------------------------------- 1 | import { init } from "@instantdb/core"; 2 | import schema, { AppSchema } from "../../instant.schema"; 3 | 4 | // Initialize the database 5 | // --------- 6 | // @ts-ignore 7 | let db: ReturnType> = undefined; 8 | if (typeof window !== "undefined") { 9 | if (!window.env.INSTANTDB_APP_ID) throw new Error("INSTANTDB_APP_ID not set"); 10 | db = init({ 11 | appId: window.env.INSTANTDB_APP_ID, 12 | schema, 13 | devtool: false, 14 | }); 15 | } 16 | 17 | export { db }; 18 | -------------------------------------------------------------------------------- /src/client/utils.tsx: -------------------------------------------------------------------------------- 1 | import { id } from "@instantdb/core"; 2 | import { 3 | LastPriceAnimationMode, 4 | LineType, 5 | UTCTimestamp, 6 | } from "lightweight-charts"; 7 | import { createSignal } from "solid-js"; 8 | import { 9 | calcAttributes, 10 | createOption, 11 | triggerAddHistoryOption, 12 | } from "~/shared/utils"; 13 | import { db } from "./database"; 14 | 15 | export async function addMockMarket(num_option: number = 5) { 16 | const market_id = id(); 17 | 18 | const options = Array.from({ length: num_option }, (_, i) => 19 | createOption(db, `Option ${i + 1}`, Math.random()) 20 | ); 21 | const transactions: Parameters[0] = [ 22 | ...options.flatMap((o) => o.transactions), 23 | db.tx.markets[market_id] 24 | .update({ 25 | name: "Who will win the next election?", 26 | description: "Predict the winning candidate of the 2028 election. ", 27 | image: "", 28 | // The options are not independent 29 | // Only one option can win 30 | // Unlike "Who will attend the inauguration? (Barack Y/N, Trump Y/N, Biden Y/N)" 31 | allow_multiple_correct: false, 32 | created_at: new Date().toISOString(), 33 | resolve_at: new Date( 34 | Date.now() + 3 * 24 * 60 * 60 * 1000 35 | ).toISOString(), 36 | stop_trading_at: new Date( 37 | Date.now() + (3 - 1) * 24 * 60 * 60 * 1000 38 | ).toISOString(), 39 | rule: `The winner of the market is the candidate that wins the election according to Google News.\nIf a candidate drops out before the election, the option will be resolved as "No".\nIf the result of the election is disputed, the option for all candidatte will be resolved as "No".`, 40 | }) 41 | .link({ 42 | options: options.map((o) => o.option_id), 43 | }), 44 | ]; 45 | 46 | await db.transact(transactions); 47 | 48 | // trigger api history__options 49 | const ps = options.map((o) => triggerAddHistoryOption(o.option_id)); 50 | await Promise.all(ps); 51 | } 52 | 53 | export const [loadMarketsState, setLoadMarketsState] = createSignal< 54 | "idle" | "loading" 55 | >("idle"); 56 | export async function loadMarkets() { 57 | setLoadMarketsState("loading"); 58 | try { 59 | console.log("load markets"); 60 | const r = marketResponses().at(-1); 61 | const lastCursor = r?.pageInfo?.markets?.endCursor; 62 | const resp = await db.queryOnce({ 63 | markets: { 64 | options: { 65 | shares: {}, 66 | }, 67 | $: { 68 | first: 10, 69 | ...(lastCursor && { after: lastCursor }), 70 | order: { 71 | num_votes: "desc", 72 | }, 73 | }, 74 | }, 75 | }); 76 | 77 | console.log("loadMarkets resp", resp); 78 | setMarketResponses((prev) => [...prev, resp]); 79 | } catch (e) {} 80 | setLoadMarketsState("idle"); 81 | } 82 | 83 | export const [blocks, setBlocks] = createSignal({}); 84 | export const blocksToList = (blocks: Blocks) => 85 | Object.values(blocks).toSorted( 86 | (a, b) => 87 | new Date(a.created_at).getTime() - new Date(b.created_at).getTime() 88 | ); 89 | export const listBlocks = () => blocksToList(blocks()); 90 | 91 | if (typeof window !== "undefined") { 92 | // @ts-ignore 93 | window.dev = { 94 | addMockMarket, 95 | setBlocks, 96 | blocks: () => { 97 | setBlocks((prev) => { 98 | console.log("blocks", prev); 99 | return prev; 100 | }); 101 | }, 102 | }; 103 | } 104 | 105 | // Markets page 106 | export const [marketResponses, setMarketResponses] = createSignal< 107 | MarketResponse[] 108 | >([]); 109 | export const markets = () => { 110 | const ms = marketResponses().flatMap((m) => m.data.markets); 111 | return ms.map((m) => calcAttributes(m)); 112 | }; 113 | export const marketsHasNextPage = () => { 114 | const r = marketResponses().at(-1); 115 | const hasNextPage = r?.pageInfo?.markets?.hasNextPage; 116 | return hasNextPage; 117 | }; 118 | 119 | // Universial 120 | export const [profileSubscription, setProfileSubscription] = 121 | createSignal(); 122 | export const profile = () => { 123 | const s = profileSubscription(); 124 | const p = s?.data?.profiles.at(0); 125 | return p; 126 | }; 127 | 128 | // BuySelComp 129 | 130 | export const [optionId, setOptionId] = createSignal(); 131 | export const [type, setType] = createSignal("yes"); 132 | 133 | export const [marketSubscription, setMarketSubscription] = 134 | createSignal(); 135 | export const market = () => { 136 | const s = marketSubscription(); 137 | const ms = s?.data?.markets ?? []; 138 | const _ms = ms.map((m) => calcAttributes(m)); 139 | return _ms.at(0); 140 | }; 141 | export const [historyOptionSubscription, setHistoryOptionSubscription] = 142 | createSignal(); 143 | 144 | const historyOptions = () => 145 | historyOptionSubscription()?.data?.history__options ?? []; 146 | 147 | export const chartSeries = () => { 148 | const m = market(); 149 | if (!m) return []; 150 | 151 | const result: { 152 | [option_id: string]: Series; 153 | } = {}; 154 | 155 | m.options.forEach((o) => { 156 | result[o.id] = { 157 | data: [], 158 | id: o.id, 159 | options: { 160 | color: o.color, 161 | lineType: LineType.WithSteps, 162 | lineWidth: 2, 163 | lastPriceAnimation: LastPriceAnimationMode.Continuous, 164 | }, 165 | title: o.name, 166 | }; 167 | }); 168 | 169 | historyOptions().forEach((h) => { 170 | if (!result[h.option_id]) return; 171 | result[h.option_id].data.push({ 172 | time: Math.floor(new Date(h.created_at).getTime() / 1000) as UTCTimestamp, 173 | value: h.yesProb, 174 | }); 175 | }); 176 | 177 | return Object.values(result).map((s) => { 178 | const sorted = s.data.toSorted((a, b) => a.time - b.time); 179 | return { 180 | ...s, 181 | data: sorted, 182 | }; 183 | }); 184 | }; 185 | 186 | export const [scrolledToBottom, setScrolledToBottom] = createSignal(); 187 | export const [abortController, setAbortController] = createSignal< 188 | AbortController | undefined 189 | >(); 190 | 191 | // export const [blocksScrollView, setBlocksScrollView] = 192 | // createSignal(); 193 | 194 | export const userChatted = () => listBlocks().length > 0; 195 | export const [news, setNews] = createSignal<{ title: string }[]>([ 196 | { 197 | title: "Government announces new tax policies for 2023", 198 | }, 199 | { 200 | title: "Tesla announces new model 3", 201 | }, 202 | { 203 | title: "Scientists discover new species of cat that can play the piano", 204 | }, 205 | { 206 | title: "SpaceX launches new rocket to Mars", 207 | }, 208 | { 209 | title: "Apple unveils its latest iPhone model", 210 | }, 211 | { 212 | title: "Breakthrough in cancer research offers new hope", 213 | }, 214 | { 215 | title: "Global markets react to unexpected interest rate hike", 216 | }, 217 | { 218 | title: "Major tech company faces data breach allegations", 219 | }, 220 | 221 | { 222 | title: "Major tech company faces data breach allegations", 223 | }, 224 | 225 | { 226 | title: "Major tech company faces data breach allegations", 227 | }, 228 | 229 | { 230 | title: "Major tech company faces data breach allegations", 231 | }, 232 | ]); 233 | 234 | export const [bigLogoEl, setBigLogoEl] = createSignal(); 235 | 236 | export const [infiniteScrollHovered, setInfiniteScrollHovered] = 237 | createSignal(false); 238 | 239 | export const scrollToEnd = () => { 240 | // console.log("scrolling", document.body.scrollHeight); 241 | window.scrollTo({ 242 | // 80 is random for no reason 243 | top: document.body.scrollHeight + 80, 244 | behavior: "smooth", 245 | }); 246 | }; 247 | 248 | export const [toolTmpStorage, setToolTmpStorage] = createSignal({}); 249 | 250 | export async function api_vote(market_id: string, vote: UpvoteDownvote) { 251 | console.log("call vote", market_id); 252 | const resp = await fetch(`/api/markets/${market_id}/vote`, { 253 | method: "POST", 254 | headers: { 255 | "Content-Type": "application/json", 256 | }, 257 | body: JSON.stringify(vote), 258 | }); 259 | 260 | console.log("resp", resp, resp.ok); 261 | console.log("j", await resp.text()); 262 | 263 | return resp; 264 | } 265 | 266 | export const [blockShow, setBlockShow] = createSignal<{ 267 | [blockId: string]: boolean; 268 | }>({}); 269 | -------------------------------------------------------------------------------- /src/components/AIComp.tsx: -------------------------------------------------------------------------------- 1 | import { id } from "@instantdb/core"; 2 | 3 | import { BsArrowUpShort, BsStopFill } from "solid-icons/bs"; 4 | import { createSignal, For, onMount, Show } from "solid-js"; 5 | import { 6 | abortController, 7 | blocks, 8 | blocksToList, 9 | listBlocks, 10 | profile, 11 | scrollToEnd, 12 | setAbortController, 13 | setBigLogoEl, 14 | setBlocks, 15 | userChatted, 16 | } from "~/client/utils"; 17 | 18 | import { accept } from "~/client/accept"; 19 | import { db } from "~/client/database"; 20 | import BlockComp from "./BlockComp"; 21 | import IconComp from "./IconComp"; 22 | import Spinner from "./Spinner"; 23 | import { generateBlocks, readNDJSON } from "~/shared/utils"; 24 | 25 | export default function AIComp() { 26 | const [text, setText] = createSignal(""); 27 | 28 | async function submit() { 29 | const ac = abortController(); 30 | if (ac) { 31 | ac.abort(); 32 | } 33 | 34 | const t = text().trim(); 35 | if (!t) return; 36 | 37 | scrollToEnd(); 38 | setText(""); 39 | 40 | const userBlock: Block = { 41 | agent_step: undefined, 42 | id: id(), 43 | role: "user", 44 | content: t, 45 | created_at: new Date().toISOString(), 46 | updated_at: new Date().toISOString(), 47 | }; 48 | 49 | // Log the user's message 50 | db.transact([ 51 | db.tx.blocks[userBlock.id].update(userBlock).link({ 52 | profile: profile()?.id, 53 | }), 54 | ]); 55 | 56 | const blocks_1 = { 57 | ...blocks(), 58 | [userBlock.id]: userBlock, 59 | }; 60 | setBlocks(blocks_1); 61 | 62 | const onlyAssistantOrUser = blocksToList(blocks_1).filter( 63 | (b) => b.role == "user" || b.role == "assistant" 64 | ); 65 | 66 | let history = []; 67 | let remainingLength = 1000; 68 | 69 | for (let i = onlyAssistantOrUser.length - 1; i >= 0; i--) { 70 | const { content } = onlyAssistantOrUser[i]; 71 | 72 | if (content.length > remainingLength) { 73 | // Add only the part of the content that fits 74 | history.push({ 75 | ...onlyAssistantOrUser[i], 76 | content: content.slice(0, remainingLength), 77 | }); 78 | break; 79 | } 80 | 81 | // Add the full content if it fits 82 | history.push(onlyAssistantOrUser[i]); 83 | remainingLength -= content.length; 84 | } 85 | 86 | history = history.toReversed(); 87 | 88 | const controller = new AbortController(); 89 | const { signal } = controller; 90 | signal.addEventListener("abort", () => { 91 | console.log("aborted!"); 92 | }); 93 | setAbortController(controller); 94 | 95 | const resp = await fetch("/api/complete", { 96 | method: "POST", 97 | headers: { 98 | "Content-Type": "application/json", 99 | }, 100 | body: JSON.stringify({ blocks: history } as APICompleteBody), 101 | signal, 102 | }); 103 | 104 | if (!resp.body) return; 105 | const g = readNDJSON(resp.body); 106 | 107 | for await (const value of g) { 108 | const y = value as any as ChatStreamYield; 109 | accept(y); 110 | } 111 | 112 | setAbortController(undefined); 113 | } 114 | 115 | // onMount(() => { 116 | // setBlocks(generateBlocks()); 117 | // }); 118 | 119 | return ( 120 |
121 |
122 |
123 | 127 |
128 |
129 |
130 | 131 |

132 | Predict Anything 133 |

134 |
135 | 136 |
137 | 138 | We will tell how accurate your prediction is. 139 | {" "} 140 | 141 | If no data is available, a prediction market will be 142 | created to gather insights from the crowd. 143 | 144 |
145 |
146 |
147 |
148 | } 149 | > 150 |
151 | 152 | {(b) => } 153 | 154 | {/* 155 | 156 | */} 157 |
158 | 159 |
160 | 161 |
162 | 166 |