-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat frontend api refactor #24
Changes from 26 commits
47bf8ff
bb7478a
7f7a682
2fd07ab
ff0011c
7afa251
ac9c5a2
a034248
666d159
ffbe0dd
9c1924e
d90bf48
72209ba
4902e36
2314719
f2575a3
42c7a88
652c9b5
73a810d
ce340f5
806894b
3729eb7
bb28f69
ff17f54
d07ef4c
fb4319f
a2310f7
eb8ccca
d89f771
abd7b81
723a6e4
6279339
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,4 +12,7 @@ dist/ | |
|
||
# Models | ||
models/ | ||
*/**/models/ | ||
*/**/models/ | ||
|
||
*/**/database.sqlite | ||
./backend/src/database.sqlite |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"typescript.tsdk": "node_modules/typescript/lib", | ||
"typescript.enablePromptUseWorkspaceTsdk": true | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"cSpell.words": ["upsert"] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { | ||
Injectable, | ||
NestInterceptor, | ||
ExecutionContext, | ||
CallHandler, | ||
Logger, | ||
} from '@nestjs/common'; | ||
import { Observable } from 'rxjs'; | ||
import { GqlExecutionContext } from '@nestjs/graphql'; | ||
|
||
@Injectable() | ||
export class LoggingInterceptor implements NestInterceptor { | ||
private readonly logger = new Logger('GraphQLRequest'); | ||
|
||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> { | ||
const ctx = GqlExecutionContext.create(context); | ||
const { operation, fieldName } = ctx.getInfo(); | ||
const variables = ctx.getContext().req.body.variables; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for malformed requests. The context extraction could fail if the request is malformed. Consider adding try-catch blocks to handle potential errors gracefully. intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
+ try {
const ctx = GqlExecutionContext.create(context);
const { operation, fieldName } = ctx.getInfo();
const variables = ctx.getContext().req.body.variables;
+ } catch (error) {
+ this.logger.error(`Failed to extract GraphQL context: ${error.message}`);
+ throw error;
+ }
|
||
this.logger.log( | ||
`${operation.operation.toUpperCase()} \x1B[33m${fieldName}\x1B[39m${ | ||
variables ? ` Variables: ${JSON.stringify(variables)}` : '' | ||
}`, | ||
); | ||
Comment on lines
+25
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Enhance logging implementation with security and maintainability improvements. Several suggestions to improve the logging implementation:
Here's an improved implementation: - this.logger.log(
- `${operation.operation.toUpperCase()} \x1B[33m${fieldName}\x1B[39m${
- variables ? ` Variables: ${JSON.stringify(variables)}` : ''
- }`,
- );
+ const sanitizedVariables = this.sanitizeVariables(variables);
+ const startTime = Date.now();
+
+ this.logger.log({
+ type: operation.operation.toUpperCase(),
+ field: fieldName,
+ variables: sanitizedVariables,
+ timestamp: new Date().toISOString()
+ });
+
+ return next.handle().pipe(
+ tap(() => {
+ const duration = Date.now() - startTime;
+ this.logger.log(`Operation completed in ${duration}ms`);
+ })
+ );
// Add this method to the class
private sanitizeVariables(variables: any): any {
if (!variables) return undefined;
const sensitiveFields = ['password', 'token', 'secret'];
return Object.entries(variables).reduce((acc, [key, value]) => {
acc[key] = sensitiveFields.includes(key.toLowerCase()) ? '[REDACTED]' : value;
return acc;
}, {});
}
|
||
|
||
return next.handle(); | ||
} | ||
Comment on lines
+31
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider adding request timing and error handling to the response pipeline. The current implementation doesn't track request completion or handle errors in the response pipeline. - return next.handle();
+ return next.handle().pipe(
+ tap({
+ error: (error) => {
+ this.logger.error(
+ `Operation failed: ${error.message}`,
+ error.stack
+ );
+ }
+ })
+ );
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,8 +10,8 @@ | |
import { TokenModule } from './token/token.module'; | ||
import { UserModule } from './user/user.module'; | ||
import { InitModule } from './init/init.module'; | ||
import { RolesGuard } from './guard/roles.guard'; | ||
import { MenuGuard } from './guard/menu.guard'; | ||
import { User } from './user/user.model'; | ||
import { AppResolver } from './app.resolver'; | ||
|
||
|
@@ -20,7 +20,7 @@ | |
ConfigModule.forRoot({ isGlobal: true }), | ||
GraphQLModule.forRoot<ApolloDriverConfig>({ | ||
driver: ApolloDriver, | ||
autoSchemaFile: join(process.cwd(), 'src/schema.gql'), | ||
autoSchemaFile: join(process.cwd(), '../frontend/src/graphql/schema.gql'), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Schema file location creates deployment risks and tight coupling The verification confirms the architectural concerns. The only cross-directory reference is the schema path in
Recommended solution: - autoSchemaFile: join(process.cwd(), '../frontend/src/graphql/schema.gql'),
+ autoSchemaFile: join(process.cwd(), 'src/schema.gql'), Then implement one of these approaches:
🔗 Analysis chainConsider architectural implications of schema location. Moving the GraphQL schema file to the frontend directory creates a tight coupling between backend and frontend, which could lead to deployment and build issues. This approach:
Consider these alternatives:
- autoSchemaFile: join(process.cwd(), '../frontend/src/graphql/schema.gql'),
+ autoSchemaFile: join(process.cwd(), 'src/schema.gql'), Then add a build step to copy the schema to frontend.
- autoSchemaFile: join(process.cwd(), '../frontend/src/graphql/schema.gql'),
+ autoSchemaFile: join(process.cwd(), '../shared/schema.gql'),
Let's verify the potential impact: 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Check if there are any direct imports or references between frontend and backend
rg -g '!node_modules' -l 'frontend|backend' backend/
Length of output: 130 Script: #!/bin/bash
# Let's check the content of these files to understand the nature of frontend/backend references
echo "=== app.module.ts ==="
cat backend/src/app.module.ts
echo -e "\n=== protocol.ts ==="
cat backend/src/chat/protocol.ts
echo -e "\n=== package.json ==="
cat backend/package.json
Length of output: 8602 |
||
sortSchema: true, | ||
playground: true, | ||
installSubscriptionHandlers: true, | ||
|
@@ -30,7 +30,6 @@ | |
type: 'sqlite', | ||
database: join(process.cwd(), 'src/database.sqlite'), | ||
synchronize: true, | ||
logging: true, | ||
entities: [__dirname + '/**/*.model{.ts,.js}'], | ||
}), | ||
InitModule, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,7 +55,7 @@ | |
|
||
async login( | ||
loginUserInput: LoginUserInput, | ||
): Promise<{ access_token: string }> { | ||
): Promise<{ accessToken: string }> { | ||
const { username, password } = loginUserInput; | ||
|
||
const user = await this.userRepository.findOne({ | ||
|
@@ -73,10 +73,10 @@ | |
} | ||
|
||
const payload = { userId: user.id, username: user.username }; | ||
const access_token = this.jwtService.sign(payload); | ||
this.jwtCacheService.storeToken(access_token); | ||
const accessToken = this.jwtService.sign(payload); | ||
this.jwtCacheService.storeToken(accessToken); | ||
Comment on lines
+76
to
+77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider enhancing token management configuration. While the current implementation is functional, consider these improvements:
Here's a suggested implementation: const payload = { userId: user.id, username: user.username };
-const accessToken = this.jwtService.sign(payload);
+const accessToken = this.jwtService.sign(payload, {
+ expiresIn: this.configService.get('JWT_EXPIRATION', '1h'),
+}); And in the validateToken method: try {
await this.jwtService.verifyAsync(params.token);
return this.jwtCacheService.isTokenStored(params.token);
} catch (error) {
- console.log(error);
+ Logger.error('Token validation failed', {
+ error: error.message,
+ token: params.token.substring(0, 10) + '...',
+ });
return false;
}
|
||
|
||
return { access_token }; | ||
return { accessToken }; | ||
} | ||
|
||
async validateToken(params: CheckTokenInput): Promise<boolean> { | ||
|
@@ -84,7 +84,7 @@ | |
await this.jwtService.verifyAsync(params.token); | ||
return this.jwtCacheService.isTokenStored(params.token); | ||
} catch (error) { | ||
console.log(error); | ||
return false; | ||
} | ||
} | ||
|
@@ -92,7 +92,7 @@ | |
Logger.log('logout token', token); | ||
try { | ||
await this.jwtService.verifyAsync(token); | ||
} catch (error) { | ||
return false; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -59,7 +59,7 @@ export class ChatCompletionChunk { | |
model: string; | ||
|
||
@Field({ nullable: true }) | ||
system_fingerprint: string | null; | ||
systemFingerprint: string | null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Codebase verification Update required: Inconsistent field naming across the codebase The field rename from
While the model and frontend use camelCase, protocol files still use snake_case, which needs to be synchronized. 🔗 Analysis chainVerify the impact of the systemFingerprint rename. The field rename from Also applies to: 88-90 🏁 Scripts executedThe following scripts were executed for the analysis: Script: #!/bin/bash
# Search for any remaining snake_case usage of system_fingerprint
rg -l "system_fingerprint" --type ts --type tsx
# Search for GraphQL queries/mutations using the old field name
rg -l "system_fingerprint" --type graphql
Length of output: 149 Script: #!/bin/bash
# Search for any remaining snake_case usage of system_fingerprint in all files
rg "system_fingerprint"
# Search for camelCase usage to understand the context
rg "systemFingerprint"
Length of output: 666 |
||
|
||
@Field(() => [ChatCompletionChoice]) | ||
choices: ChatCompletionChoice[]; | ||
|
@@ -74,5 +74,5 @@ class ChatCompletionChoice { | |
delta: ChatCompletionDelta; | ||
|
||
@Field({ nullable: true }) | ||
finish_reason: string | null; | ||
finishReason: string | null; | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -4,6 +4,18 @@ import 'reflect-metadata'; | |||||||||||||||||||||||||
|
||||||||||||||||||||||||||
async function bootstrap() { | ||||||||||||||||||||||||||
const app = await NestFactory.create(AppModule); | ||||||||||||||||||||||||||
app.enableCors({ | ||||||||||||||||||||||||||
origin: '*', | ||||||||||||||||||||||||||
credentials: true, | ||||||||||||||||||||||||||
Comment on lines
+8
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Fix invalid CORS configuration The combination of If this is for development, consider: - origin: '*',
+ origin: process.env.NODE_ENV === 'production'
+ ? 'https://your-production-domain.com'
+ : 'http://localhost:3001', // adjust port as needed If you need to support multiple origins: - origin: '*',
+ origin: process.env.NODE_ENV === 'production'
+ ? ['https://domain1.com', 'https://domain2.com']
+ : 'http://localhost:3001',
|
||||||||||||||||||||||||||
methods: ['GET', 'POST', 'OPTIONS'], | ||||||||||||||||||||||||||
allowedHeaders: [ | ||||||||||||||||||||||||||
'Content-Type', | ||||||||||||||||||||||||||
'Accept', | ||||||||||||||||||||||||||
'Authorization', | ||||||||||||||||||||||||||
'Access-Control-Allow-Origin', | ||||||||||||||||||||||||||
'Access-Control-Allow-Credentials', | ||||||||||||||||||||||||||
], | ||||||||||||||||||||||||||
Comment on lines
+11
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove unnecessary CORS headers from allowedHeaders The Apply this fix: allowedHeaders: [
'Content-Type',
'Accept',
'Authorization',
- 'Access-Control-Allow-Origin',
- 'Access-Control-Allow-Credentials',
], 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||
await app.listen(process.env.PORT ?? 3000); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
bootstrap(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
{ | ||
"extends": "next/core-web-vitals" | ||
"extends": "next/core-web-vitals", | ||
"ignorePatterns": ["src/graphql/type.ts", "src/graphql/**/*"] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
name: frontend-dev | ||
root: ./ | ||
|
||
windows: | ||
- editor: | ||
layout: even-horizontal | ||
panes: | ||
- next: | ||
- pnpm dev | ||
- codegen: | ||
- pnpm generate:watch |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,134 +1,43 @@ | ||
<div align="center"> | ||
<img src="ollama-nextjs-ui.gif"> | ||
</div> | ||
- we are using tmuxinator if you want to watch both codegen and dev server | ||
|
||
struct for now | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix unclear documentation text. The text "struct for now" appears to be a placeholder or incomplete documentation. Consider removing it or replacing it with a proper description. |
||
```plain | ||
|
||
src/ | ||
├── app/ | ||
│ ├── hooks/ # React Hooks | ||
│ │ ├── useAuth.ts # 认证相关 hook | ||
│ │ ├── useChatStore.ts # 聊天状态管理 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maintain consistent language in documentation. Some comments are in Chinese (e.g., "认证相关 hook") while others are in English. For better international collaboration and maintainability, consider using a single language consistently throughout the documentation. Apply this change to translate Chinese comments to English: -│ │ ├── useAuth.ts # 认证相关 hook
+│ │ ├── useAuth.ts # Authentication related hook Also applies to: 11-14 |
||
│ │ ├── useLocalStorageData.ts # 本地存储数据管理 | ||
│ │ ├── useModels.ts # AI模型选择和缓存管理 | ||
│ │ └── useSpeechRecognition.ts # 语音识别功能 | ||
│ ├── login/ # 登录页面 | ||
│ ├── register/ # 注册页面 | ||
│ └── components/ # 共享组件 | ||
│ | ||
├── graphql/ | ||
│ ├── request.ts # GraphQL 查询/变更定义 | ||
│ │ ├── auth.ts # 认证相关查询 | ||
│ │ ├── chat.ts # 聊天相关查询 | ||
│ │ └── models.ts # 模型相关查询 | ||
│ ├── schema.graphql # GraphQL Schema定义 | ||
│ └── type.tsx # 由 schema 生成的 TypeScript 类型 | ||
│ | ||
├── lib/ | ||
│ ├── client.ts # Apollo Client 配置 | ||
│ │ ├── apolloClient.ts # Apollo Client 实例设置 | ||
│ │ └── errorHandling.ts # 错误处理中间件 | ||
│ ├── storage.ts # 存储相关枚举和常量 | ||
│ │ ├── LocalStore.ts # localStorage keys | ||
│ │ └── SessionStore.ts # sessionStorage keys | ||
│ └── utils.ts # 通用工具函数 | ||
│ | ||
└── test/ | ||
├── utils/ # 测试工具函数 | ||
│ ├── client.ts # 测试用 Apollo Client | ||
│ └── requests.ts # 测试用请求方法 | ||
└── __tests__/ # 测试用例 | ||
└── hooks/ # Hooks 测试 | ||
|
||
Comment on lines
+1
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Restore essential README sections. Based on the AI summary, several crucial sections have been removed from the README:
Consider restoring these sections to maintain comprehensive documentation for new contributors and users. Would you like me to help generate a template for these missing sections? |
||
<h1 align="center"> | ||
Fully-featured & beautiful web interface for Ollama LLMs | ||
</h1> | ||
|
||
<div align="center"> | ||
|
||
 | ||
|
||
</div> | ||
|
||
Get up and running with Large Language Models **quickly**, **locally** and even **offline**. | ||
This project aims to be the easiest way for you to get started with LLMs. No tedious and annoying setup required! | ||
|
||
# Features ✨ | ||
|
||
- **Beautiful & intuitive UI:** Inspired by ChatGPT, to enhance similarity in the user experience. | ||
- **Fully local:** Stores chats in localstorage for convenience. No need to run a database. | ||
- **Fully responsive:** Use your phone to chat, with the same ease as on desktop. | ||
- **Easy setup:** No tedious and annoying setup required. Just clone the repo and you're good to go! | ||
- **Code syntax highligting:** Messages that include code, will be highlighted for easy access. | ||
- **Copy codeblocks easily:** Easily copy the highlighted code with one click. | ||
- **Download/Pull & Delete models:** Easily download and delete models directly from the interface. | ||
- **Switch between models:** Switch between models fast with a click. | ||
- **Chat history:** Chats are saved and easily accessed. | ||
- **Light & Dark mode:** Switch between light & dark mode. | ||
|
||
# Preview | ||
|
||
https://github.com/jakobhoeg/nextjs-ollama-llm-ui/assets/114422072/08eaed4f-9deb-4e1b-b87a-ba17d81b9a02 | ||
|
||
# Requisites ⚙️ | ||
|
||
To use the web interface, these requisites must be met: | ||
|
||
1. Download [Ollama](https://ollama.com/download) and have it running. Or run it in a Docker container. Check the [docs](https://github.com/ollama/ollama) for instructions. | ||
2. Node.js (18+) and npm is required. [Download](https://nodejs.org/en/download) | ||
|
||
# Deploy your own to Vercel or Netlify in one click ✨ | ||
|
||
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fjakobhoeg%2Fnextjs-ollama-llm-ui&env=NEXT_PUBLIC_OLLAMA_URL&envDescription=Your%20Ollama%20URL) [](https://app.netlify.com/start/deploy?repository=https://github.com/jakobhoeg/nextjs-ollama-llm-ui) | ||
|
||
You'll need to set your [OLLAMA_ORIGINS](https://github.com/ollama/ollama/blob/main/docs/faq.md) environment variable on your machine that is running Ollama: | ||
|
||
``` | ||
OLLAMA_ORIGINS="https://your-app.vercel.app/" | ||
``` | ||
|
||
# Installation 📖 | ||
|
||
[](https://repology.org/project/nextjs-ollama-llm-ui/versions) | ||
|
||
Use a pre-build package from one of the supported package managers to run a local environment of the web interface. | ||
Alternatively you can install from source with the instructions below. | ||
|
||
> [!NOTE] | ||
> If your frontend runs on something other than `http://localhost` or `http://127.0.0.1`, you'll need to set the OLLAMA_ORIGINS to your frontend url. | ||
> | ||
> This is also stated in the [documentation](https://github.com/ollama/ollama/blob/main/docs/faq.md#how-do-i-configure-ollama-server): | ||
> | ||
> `Ollama allows cross-origin requests from 127.0.0.1 and 0.0.0.0 by default. Additional origins can be configured with OLLAMA_ORIGINS` | ||
|
||
## Install from source | ||
|
||
**1. Clone the repository to a directory on your pc via command prompt:** | ||
|
||
``` | ||
git clone https://github.com/jakobhoeg/nextjs-ollama-llm-ui | ||
``` | ||
|
||
**2. Open the folder:** | ||
|
||
``` | ||
cd nextjs-ollama-llm-ui | ||
``` | ||
|
||
**3. Rename the `.example.env` to `.env`:** | ||
|
||
``` | ||
mv .example.env .env | ||
``` | ||
|
||
**4. If your instance of Ollama is NOT running on the default ip-address and port, change the variable in the .env file to fit your usecase:** | ||
|
||
``` | ||
NEXT_PUBLIC_OLLAMA_URL="http://localhost:11434" | ||
``` | ||
|
||
**5. Install dependencies:** | ||
|
||
``` | ||
npm install | ||
``` | ||
|
||
**6. Start the development server:** | ||
|
||
``` | ||
npm run dev | ||
``` | ||
|
||
**5. Go to [localhost:3000](http://localhost:3000) and start chatting with your favourite model!** | ||
|
||
# Upcoming features | ||
|
||
This is a to-do list consisting of upcoming features. | ||
|
||
- ✅ Voice input support | ||
- ✅ Code syntax highlighting | ||
- ✅ Ability to send an image in the prompt to utilize vision language models. | ||
- ✅ Ability to regenerate responses | ||
- ⬜️ Import and export chats | ||
|
||
# Tech stack | ||
|
||
[NextJS](https://nextjs.org/) - React Framework for the Web | ||
|
||
[TailwindCSS](https://tailwindcss.com/) - Utility-first CSS framework | ||
|
||
[shadcn-ui](https://ui.shadcn.com/) - UI component built using Radix UI and Tailwind CSS | ||
|
||
[shadcn-chat](https://github.com/jakobhoeg/shadcn-chat) - Chat components for NextJS/React projects | ||
|
||
[Framer Motion](https://www.framer.com/motion/) - Motion/animation library for React | ||
|
||
[Lucide Icons](https://lucide.dev/) - Icon library | ||
|
||
# Helpful links | ||
|
||
[Medium Article](https://medium.com/@bartek.lewicz/launch-your-own-chatgpt-clone-for-free-on-colab-shareable-and-online-in-less-than-10-minutes-da19e44be5eb) - How to launch your own ChatGPT clone for free on Google Colab. By Bartek Lewicz. | ||
|
||
[Lobehub mention](https://lobehub.com/blog/5-ollama-web-ui-recommendation#5-next-js-ollama-llm-ui) - Five Excellent Free Ollama WebUI Client Recommendations |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider adding explicit return types rule.
Since you're enhancing TypeScript support, consider also adding the
@typescript-eslint/explicit-function-return-type
rule to ensure all functions have explicit return types.📝 Committable suggestion