From 103dbd3b34660853d4ed437bbb6a2d24456454e3 Mon Sep 17 00:00:00 2001 From: wlt Date: Wed, 17 Jun 2026 13:57:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20AI=E5=B2=97=E4=BD=8D=E4=B8=93=E5=8C=BA?= =?UTF-8?q?=20=E2=80=94=205=E4=B8=AAAI=E5=B2=97=E4=BD=8D=E7=BD=AE=E9=A1=B6?= =?UTF-8?q?=20+=20=E9=A6=96=E9=A1=B5=E5=88=86=E7=BB=84=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - schema: HotPosition 新增 category 字段 (ai/traditional) - positions: 5 AI岗位 (AI算法/大模型应用/Prompt/AI产品/AI运维) + 7传统岗位 - frontend: 首页拆分 "🔥 AI热门岗位" 置顶高亮 + "更多岗位" 折叠 - ai服务: 新增 primaryFallbackModel (sensenova-6.7-flash-lite) 降级链路 --- .../ses_130b21833ffe0epQvGBjpGtogZ.json | 11 +++ backend/src/modules/ai/ai.service.ts | 43 ++++++----- .../src/modules/positions/positions.schema.ts | 16 ++-- zhiyin-app/src/pages/index/index.vue | 76 +++++++++++++++++-- 4 files changed, 116 insertions(+), 30 deletions(-) create mode 100644 .omo/run-continuation/ses_130b21833ffe0epQvGBjpGtogZ.json diff --git a/.omo/run-continuation/ses_130b21833ffe0epQvGBjpGtogZ.json b/.omo/run-continuation/ses_130b21833ffe0epQvGBjpGtogZ.json new file mode 100644 index 0000000..14f16f0 --- /dev/null +++ b/.omo/run-continuation/ses_130b21833ffe0epQvGBjpGtogZ.json @@ -0,0 +1,11 @@ +{ + "sessionID": "ses_130b21833ffe0epQvGBjpGtogZ", + "updatedAt": "2026-06-17T05:54:06.381Z", + "sources": { + "background-task": { + "state": "active", + "reason": "2 background task(s) active", + "updatedAt": "2026-06-17T05:54:06.381Z" + } + } +} \ No newline at end of file diff --git a/backend/src/modules/ai/ai.service.ts b/backend/src/modules/ai/ai.service.ts index 243eff6..316b3d3 100644 --- a/backend/src/modules/ai/ai.service.ts +++ b/backend/src/modules/ai/ai.service.ts @@ -1,6 +1,6 @@ -import { Injectable, Logger } from '@nestjs/common' -import axios from 'axios' -import https from 'https' +import { Injectable, Logger } from "@nestjs/common" +import axios from "axios" +import https from "https" interface AiCallOptions { systemPrompt: string @@ -15,26 +15,35 @@ const httpAgent = new https.Agent({ rejectUnauthorized: true, keepAlive: true }) export class AiService { private readonly logger = new Logger(AiService.name) - private readonly primaryUrl = process.env.AI_PRIMARY_URL || 'https://token.sensenova.cn/v1' - private readonly primaryKey = process.env.AI_PRIMARY_KEY || '' - private readonly primaryModel = process.env.AI_PRIMARY_MODEL || 'deepseek-v4-flash' + private readonly primaryUrl = process.env.AI_PRIMARY_URL || "https://token.sensenova.cn/v1" + private readonly primaryKey = process.env.AI_PRIMARY_KEY || "" + private readonly primaryModel = process.env.AI_PRIMARY_MODEL || "deepseek-v4-flash" + private readonly primaryFallbackModel = process.env.AI_PRIMARY_FALLBACK_MODEL || "sensenova-6.7-flash-lite" - private readonly backupUrl = process.env.AI_BACKUP_URL || 'https://integrate.api.nvidia.com/v1' - private readonly backupKey = process.env.AI_BACKUP_KEY || '' - private readonly backupModel = process.env.AI_BACKUP_MODEL || 'stepfun-ai/step-3.5-flash' + private readonly backupUrl = process.env.AI_BACKUP_URL || "https://integrate.api.nvidia.com/v1" + private readonly backupKey = process.env.AI_BACKUP_KEY || "" + private readonly backupModel = process.env.AI_BACKUP_MODEL || "stepfun-ai/step-3.5-flash" async call(options: AiCallOptions): Promise { const { systemPrompt, userMessage, temperature = 0.7, maxTokens = 2048 } = options - // Try primary AI + // Try primary AI (deepseek-v4-flash on sensenova) try { const result = await this.callApi(this.primaryUrl, this.primaryKey, this.primaryModel, systemPrompt, userMessage, temperature, maxTokens) if (result) return result } catch (e) { - this.logger.warn(`Primary AI failed: ${(e as Error).message}, trying backup...`) + this.logger.warn(`Primary AI failed: ${(e as Error).message}, trying primary fallback...`) } - // Try backup AI + // Try primary fallback model (sensenova-6.7-flash-lite, same provider) + try { + const result = await this.callApi(this.primaryUrl, this.primaryKey, this.primaryFallbackModel, systemPrompt, userMessage, temperature, maxTokens) + if (result) return result + } catch (e) { + this.logger.warn(`Primary fallback AI also failed: ${(e as Error).message}, trying backup...`) + } + + // Try backup AI (NVIDIA) try { const result = await this.callApi(this.backupUrl, this.backupKey, this.backupModel, systemPrompt, userMessage, temperature, maxTokens) if (result) return result @@ -43,7 +52,7 @@ export class AiService { } // Final fallback - throw new Error('AI 服务暂时不可用,请稍后重试') + throw new Error("AI \u670d\u52a1\u6682\u65f6\u4e0d\u53ef\u7528\uff0c\u8bf7\u7a0d\u540e\u91cd\u8bd5") } private async callApi( @@ -56,16 +65,16 @@ export class AiService { { model, messages: [ - { role: 'system', content: systemPrompt }, - { role: 'user', content: userMessage }, + { role: "system", content: systemPrompt }, + { role: "user", content: userMessage }, ], temperature, max_tokens: maxTokens, }, { headers: { - 'Authorization': `Bearer ${apiKey}`, - 'Content-Type': 'application/json', + "Authorization": `Bearer ${apiKey}`, + "Content-Type": "application/json", }, timeout: 60000, httpsAgent: httpAgent, diff --git a/backend/src/modules/positions/positions.schema.ts b/backend/src/modules/positions/positions.schema.ts index 682f4f3..9e1eb57 100644 --- a/backend/src/modules/positions/positions.schema.ts +++ b/backend/src/modules/positions/positions.schema.ts @@ -1,20 +1,22 @@ -import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose' -import { Document } from 'mongoose' +import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose" +import { Document } from "mongoose" export type HotPositionDocument = HotPosition & Document +export type PositionCategory = "ai" | "traditional" + @Schema({ timestamps: true }) export class HotPosition { @Prop({ required: true }) name: string - @Prop({ default: '' }) + @Prop({ default: "" }) salary?: string - @Prop({ default: '' }) + @Prop({ default: "" }) company?: string - @Prop({ default: '' }) + @Prop({ default: "" }) icon?: string @Prop({ default: 0 }) @@ -22,7 +24,11 @@ export class HotPosition { @Prop({ default: true }) active: boolean + + @Prop({ type: String, enum: ["ai", "traditional"], default: "traditional" }) + category: PositionCategory } export const HotPositionSchema = SchemaFactory.createForClass(HotPosition) HotPositionSchema.index({ sort: 1 }) +HotPositionSchema.index({ category: 1, active: 1 }) diff --git a/zhiyin-app/src/pages/index/index.vue b/zhiyin-app/src/pages/index/index.vue index ffee892..ea688d5 100644 --- a/zhiyin-app/src/pages/index/index.vue +++ b/zhiyin-app/src/pages/index/index.vue @@ -107,18 +107,49 @@ - + - 热门岗位 + 🔥 AI 热门岗位 + NEW - 点击直接面试 + AI 时代最热方向,点击直接面试 - - + + 🚀 AI 正在重塑整个行业 + 大模型应用 / Agent 开发 / Prompt 工程 — 顶尖人才缺口巨大,现在上车正当时 + + + - {{ pos.icon || posIcons[idx % posIcons.length] || '💼' }} + {{ pos.icon || posIcons[idx % posIcons.length] || '🤖' }} + + {{ pos.name }} + + {{ pos.company }} + {{ pos.salary }} + + + + + 立即模拟 + + + + + + + + 🧑‍💻 + 更多岗位({{ traditionalPositions.length }}) + + {{ showMore ? '收起 ▲' : '展开 ▼' }} + + + + + {{ pos.icon || posIcons[(aiPositions.length + idx) % posIcons.length] || '💼' }} {{ pos.name }} @@ -140,7 +171,7 @@