mirror of
https://github.com/ForFarmTeam/ForFarm.git
synced 2025-12-18 13:34:08 +01:00
Merge pull request #32 from ForFarmTeam/feature-caching
add rate limit and markdown chat
This commit is contained in:
commit
fc7e41aee2
@ -32,6 +32,7 @@ require (
|
||||
github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/go-chi/httprate v0.15.0 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
@ -45,6 +46,7 @@ require (
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
@ -58,6 +60,7 @@ require (
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/zeebo/xxh3 v1.0.2 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
|
||||
|
||||
@ -40,6 +40,8 @@ github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
|
||||
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-chi/httprate v0.15.0 h1:j54xcWV9KGmPf/X4H32/aTH+wBlrvxL7P+SdnRqxh5g=
|
||||
github.com/go-chi/httprate v0.15.0/go.mod h1:rzGHhVrsBn3IMLYDOZQsSU4fJNWcjui4fWKJcCId1R4=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
@ -103,6 +105,8 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@ -166,6 +170,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0=
|
||||
|
||||
@ -14,6 +14,7 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/go-chi/httprate"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"github.com/forfarm/backend/internal/cache"
|
||||
@ -130,18 +131,31 @@ func (a *api) Routes() *chi.Mux {
|
||||
router.Use(middleware.Logger)
|
||||
|
||||
router.Use(cors.Handler(cors.Options{
|
||||
// AllowedOrigins: []string{"https://foo.com"}, // Use this to allow specific origin hosts
|
||||
AllowedOrigins: []string{"https://*", "http://*", "http://localhost:3000"},
|
||||
// AllowOriginFunc: func(r *http.Request, origin string) bool { return true },
|
||||
AllowedOrigins: []string{"http://localhost:3000", "http://localhost:5173", "http://127.0.0.1:3000"},
|
||||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
||||
ExposedHeaders: []string{"Link"},
|
||||
AllowCredentials: true,
|
||||
MaxAge: 300, // Maximum value not ignored by any of major browsers
|
||||
MaxAge: 300,
|
||||
}))
|
||||
|
||||
config := huma.DefaultConfig("ForFarm Public API", "v1.0.0")
|
||||
api := humachi.New(router, config)
|
||||
// --- Add Rate Limiter Middleware ---
|
||||
if config.RATE_LIMIT_ENABLED {
|
||||
a.logger.Info("Rate limiting enabled",
|
||||
"rps", config.RATE_LIMIT_RPS,
|
||||
"ttl", config.RATE_LIMIT_TTL)
|
||||
|
||||
router.Use(httprate.Limit(
|
||||
config.RATE_LIMIT_RPS,
|
||||
config.RATE_LIMIT_TTL,
|
||||
httprate.WithKeyFuncs(httprate.KeyByIP),
|
||||
))
|
||||
} else {
|
||||
a.logger.Info("Rate limiting is disabled")
|
||||
} // --- End Rate Limiter Middleware ---
|
||||
|
||||
humaConfig := huma.DefaultConfig("ForFarm Public API", "v1.0.0")
|
||||
api := humachi.New(router, humaConfig)
|
||||
|
||||
router.Group(func(r chi.Router) {
|
||||
a.registerAuthRoutes(r, api)
|
||||
|
||||
@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
@ -21,6 +22,9 @@ var (
|
||||
OPENWEATHER_CACHE_TTL string
|
||||
WEATHER_FETCH_INTERVAL string
|
||||
GEMINI_API_KEY string
|
||||
RATE_LIMIT_ENABLED bool
|
||||
RATE_LIMIT_RPS int
|
||||
RATE_LIMIT_TTL time.Duration
|
||||
)
|
||||
|
||||
func Load() {
|
||||
@ -38,6 +42,9 @@ func Load() {
|
||||
viper.SetDefault("OPENWEATHER_CACHE_TTL", "15m")
|
||||
viper.SetDefault("WEATHER_FETCH_INTERVAL", "15m")
|
||||
viper.SetDefault("GEMINI_API_KEY", "gemini_api_key")
|
||||
viper.SetDefault("RATE_LIMIT_ENABLED", true)
|
||||
viper.SetDefault("RATE_LIMIT_RPS", 10)
|
||||
viper.SetDefault("RATE_LIMIT_TTL", 5*time.Minute)
|
||||
|
||||
viper.SetConfigFile(".env")
|
||||
viper.AddConfigPath("../../.")
|
||||
@ -62,4 +69,7 @@ func Load() {
|
||||
OPENWEATHER_CACHE_TTL = viper.GetString("OPENWEATHER_CACHE_TTL")
|
||||
WEATHER_FETCH_INTERVAL = viper.GetString("WEATHER_FETCH_INTERVAL")
|
||||
GEMINI_API_KEY = viper.GetString("GEMINI_API_KEY")
|
||||
RATE_LIMIT_ENABLED = viper.GetBool("RATE_LIMIT_ENABLED")
|
||||
RATE_LIMIT_RPS = viper.GetInt("RATE_LIMIT_RPS")
|
||||
RATE_LIMIT_TTL = viper.GetDuration("RATE_LIMIT_TTL")
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Send, MessageSquare, Sparkles, Loader2, User, Bot } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@ -9,6 +8,9 @@ import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar"; // Assuming Avatar is in ui folder
|
||||
import { sendChatMessage } from "@/api/chat"; // Import the API function
|
||||
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
|
||||
// Interface for chat messages
|
||||
interface ChatMessage {
|
||||
id: string;
|
||||
@ -157,9 +159,9 @@ export default function GeneralChatbotPage() {
|
||||
className={`max-w-[80%] rounded-lg p-3 shadow-sm ${
|
||||
message.role === "user"
|
||||
? "bg-green-600 text-white dark:bg-green-700"
|
||||
: "bg-white dark:bg-gray-800 border dark:border-gray-700"
|
||||
: "prose prose-sm dark:prose-invert bg-white dark:bg-gray-800 border dark:border-gray-700 max-w-none" // Use prose styles
|
||||
}`}>
|
||||
<p className="text-sm whitespace-pre-wrap">{message.content}</p>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>{message.content}</ReactMarkdown>
|
||||
<p
|
||||
className={`mt-1 text-xs opacity-70 ${
|
||||
message.role === "user" ? "text-green-100" : "text-gray-500 dark:text-gray-400"
|
||||
|
||||
@ -10,6 +10,8 @@ import { VisuallyHidden } from "@radix-ui/react-visually-hidden";
|
||||
import { useParams } from "next/navigation";
|
||||
import { sendChatMessage } from "@/api/chat";
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
|
||||
interface Message {
|
||||
id: string;
|
||||
@ -142,9 +144,9 @@ export function ChatbotDialog({ open, onOpenChange, cropName }: ChatbotDialogPro
|
||||
className={`rounded-lg px-4 py-2 max-w-[80%] shadow-sm ${
|
||||
message.role === "user"
|
||||
? "bg-primary text-primary-foreground dark:bg-primary dark:text-primary-foreground"
|
||||
: "bg-muted dark:bg-muted dark:text-muted-foreground"
|
||||
: "prose prose-sm dark:prose-invert bg-muted dark:bg-muted dark:text-muted-foreground max-w-none"
|
||||
}`}>
|
||||
<p className="text-sm whitespace-pre-wrap break-words">{message.content}</p>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>{message.content}</ReactMarkdown>
|
||||
<p
|
||||
className={`mt-1 text-xs opacity-70 ${
|
||||
message.role === "user" ? "text-green-100" : "text-gray-500 dark:text-gray-400"
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { BadgeCheck, Bell, ChevronsUpDown, CreditCard, LogOut, Sparkles } from "lucide-react";
|
||||
import { ChevronsUpDown, LogOut } from "lucide-react";
|
||||
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
@ -66,28 +65,6 @@ export function NavUser({
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<Sparkles />
|
||||
Upgrade to Pro
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem>
|
||||
<BadgeCheck />
|
||||
Account
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<CreditCard />
|
||||
Billing
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Bell />
|
||||
Notifications
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={logout}>
|
||||
<LogOut />
|
||||
Log out
|
||||
|
||||
@ -30,7 +30,6 @@
|
||||
"@radix-ui/react-visually-hidden": "^1.1.2",
|
||||
"@react-google-maps/api": "^2.20.6",
|
||||
"@react-oauth/google": "^0.12.1",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tanstack/react-query": "^5.66.0",
|
||||
"@tanstack/react-table": "^8.21.2",
|
||||
"@vis.gl/react-google-maps": "^1.5.2",
|
||||
@ -49,7 +48,9 @@
|
||||
"react-dom": "^19.0.0",
|
||||
"react-hook-form": "^7.54.2",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"recharts": "^2.15.1",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"sonner": "^2.0.1",
|
||||
"tailwind-merge": "^3.0.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
@ -57,6 +58,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,6 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
import animate from "tailwindcss-animate";
|
||||
import typography from "@tailwindcss/typography";
|
||||
|
||||
export default {
|
||||
darkMode: ["class"],
|
||||
@ -91,5 +93,5 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
|
||||
plugins: [animate, typography],
|
||||
} satisfies Config;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user