使用Kimi开发自己的问答应用

概述

Kimi是大家常用的一个人工智能助手,本文使用Kimi开发文档,以node作为后端,开发与一个问答系统

实现效果

动画.gif

Kimi简介

Kimi是由Moonshot AI开发的人工智能助手,擅长中文和英文对话。目标是帮助用户解决问题、提供信息和执行任务。无论是回答问题、处理文件还是进行网络搜索,都能提供支持。

如下图,点击用户中心,在API Key管理可以添加key。

image.png

用量限制可查看账户的用量和剩余数量。

image.png

图中的相关名字解释如下:

  • 并发: 同一时间内我们最多处理的来自您的请求数
  • RPM: request per minute 指一分钟内您最多向我们发起的请求数
  • TPM: token per minute 指一分钟内您最多和我们交互的token数
  • TPD: token per day 指一天内您最多和我们交互的token数

实现

后端实现

后端是通过nodeExpress框架实现的。
核心实现步骤与代码如下:

1. 初始化与安装依赖

# 创建目录
mkdir kimi-server && cd kimi-server

# 初始化package.json文件
npm init -y

# 安装依赖
npm i express openai -S

2. 修改package.json

修改package.json文件中的scripts节点的内容如下:

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1",
+  "dev": "nodemon ./app.js",
+  "server": "pm2 start ./app.js --name kimi"
},

3. app.js

const express = require("express");
const kimiRouter = require("./router/kimi.js");

const app = express();

// 自定义跨域中间件
const allowCors = function (req, res, next) {
  res.header("Access-Control-Allow-Origin", req.headers.origin);
  res.header("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS");
  res.header("Access-Control-Allow-Headers", "Content-Type");
  res.header("Access-Control-Allow-Credentials", "true");
  next();
};
app.use(allowCors); // 使用跨域中间件

app.use(express.static("public"));

app.use("/kimi", kimiRouter);

app.listen(18080, () => {
  console.log("express server running at http://127.0.0.1:18080");
});

4. kimi.js

const express = require("express");
const OpenAI = require("openai");
const R = require("../R");
const router = express.Router();

let r = new R();

const client = new OpenAI({
  apiKey: "你的key",
  baseURL: "https://api.moonshot.cn/v1",
});

let history = [];

async function chat(prompt = '') {
  console.time("prompt", prompt);
  let response = "";
  if (prompt) {
    history.push({
      role: "user",
      content: prompt,
    });
    const completion = await client.chat.completions.create({
      model: "moonshot-v1-8k",
      messages: history,
    });
    history = history.concat(completion.choices[0].message);
    response = completion.choices[0].message.content;
  } else {
    response = "哈喽,你好!我是Kimi,由 Moonshot AI 提供的人工智能助手。";
    history.push({
      role: "system",
      content: response,
    });
  }
  console.log({
    prompt,
    response,
  });
  console.timeEnd("prompt");
  return response;
}

router.get("/chat", async function (req, res) {
  const { prompt } = req.query;
  const reply = await chat(prompt);
  res.send(
    r.success({
      reply: reply,
    })
  );
});

module.exports = router;

前端页面

1. index.html

前端页面通过CDN引入VueElement Plusmarkdown.js,实现代码如下:

<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>chat</title>
  <!-- Import style -->
  <link rel="stylesheet" href="//unpkg.com/element-plus/dist/index.css" />
  <link rel="stylesheet" href="./md.css" />
  <!-- Import Vue 3 -->
  <script src="//unpkg.com/vue@3"></script>
  <!-- Import component library -->
  <script src="//unpkg.com/element-plus"></script>
  <script src="//cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
  <style>
    html,
    body,
    #app {
      height: 100%;
      margin: 0;
      padding: 0;
    }

    ::-webkit-scrollbar {
      width: 5px;
      height: 5px;
      background-color: #eee;
    }

    ::-webkit-scrollbar-track {
      background-color: #eee;
    }

    ::-webkit-scrollbar-thumb {
      background: #787878;
      border-radius: 10px;
    }

    .chat-container {
      display: flex;
      flex-direction: column;
      padding: 1rem;
      height: calc(100% - 2rem);
    }

    .chat-messages {
      flex-grow: 1;
      overflow-y: auto;
      padding: 10px;
    }

    .message {
      overflow: hidden;
      margin-bottom: 1rem;
    }

    .message:last-child {
      margin-bottom: 0;
    }

    .message p {
      margin: 0;
    }

    .message-bubble {
      padding: 10px;
      border-radius: 10px;
      display: inline-block;
      position: relative;
      max-width: 80%;
      text-align: justify;
      line-height: 1.5;
    }

    .message-bubble:after {
      content: ' ';
      border: 10px solid transparent;
      position: absolute;
      top: 0.5rem;
    }

    .message-bubble.received {
      background-color: #f0f0f0;
      margin-left: 0.3rem;
    }

    .message-bubble.received:after {
      border-right-color: #f0f0f0;
      left: -16px;
    }

    .message-bubble.sent {
      background-color: #007bff;
      color: white;
      float: right;
      margin-right: 1rem;
    }

    .message-bubble.sent:after {
      border-left-color: #007bff;
      right: -16px;
    }

    .chat-input {
      margin-top: 1rem;
      display: flex;
    }

    .chat-input input {
      flex: 1;
      padding: 1.5rem 0.3rem;
    }

    .chat-input button {
      padding: 1.5rem 1rem;
      border: none;
      border-radius: 5px;
      background-color: #007bff;
      color: white;
      cursor: pointer;
    }
  </style>
</head>

<body>
  <div id="app">
    <div class="chat-container">
      <div class="chat-messages" ref="messages">
        <div v-for="message in messages" :key="message.id" class="message">
          <div :class="['message-bubble', message.type]" v-html="message.text">
          </div>
        </div>
      </div>
      <div class="chat-input">
        <el-input :disabled="loading" v-model="newMessage" placeholder="请输入您的问题..."
          @keyup.enter="sendMessage"></el-input>
        <el-button style="margin-left: 0.5rem;" type="primary" :loading="loading" @click="sendMessage">{{
          loading ? '回答...' : '点击发送'
          }}</el-button>
      </div>
    </div>
  </div>
  <script>
    let url = 'http://127.0.0.1:18080/kimi/chat?prompt='
    const storageKey = 'history-messages'
    const App = {
      data() {
        return {
          messages: [],
          newMessage: '',
          loading: false
        };
      },
      mounted() {
        const messages = JSON.parse(localStorage.getItem(storageKey) || '[]')
        if (messages.length > 0) {
          this.messages = messages
          this.scrollToBottom()
        } else {
          this.sendMessage(true)
        }
      },
      methods: {
        getMessage(msg = '') {
          return new Promise(resolve => {
            this.loading = true
            fetch(`${url}${msg}`).then(res => res.json()).then(res => {
              this.loading = false
              if (res.code == 200) {
                resolve(res.data.reply)
              } else {
                resolve(res.msg)
              }
            })
          })
        },
        scrollToBottom() {
          setTimeout(() => {
            this.$refs.messages.scrollTop = this.$refs.messages.scrollHeight
          }, 100)
        },
        sendMessage(init = false) {
          if (this.newMessage.trim() !== '') {
            this.messages.push({
              id: this.messages.length + 1,
              text: this.newMessage,
              type: 'sent',
            });
            this.scrollToBottom()
            this.getMessage(this.newMessage).then(msg => {
              this.messages.push({
                id: this.messages.length + 1,
                text: marked.parse(msg),
                type: 'received',
              });
              localStorage.setItem(storageKey, JSON.stringify(this.messages));
              this.newMessage = '';
              this.scrollToBottom()
            })
          } else {
            this.getMessage().then(msg => {
              this.messages.push({
                id: this.messages.length + 1,
                text: marked.parse(msg),
                type: 'received',
              });
              localStorage.setItem(storageKey, JSON.stringify(this.messages));
              this.scrollToBottom()
            })
          }
        },
      },
    };
    const app = Vue.createApp(App);
    app.use(ElementPlus);
    app.mount("#app");
  </script>
</body>

</html>

2. md.css

md.css为优化markdown样式的外部引用,代码如下:

body {
  font-family: "Microsoft Yahei", Helvetica, arial, sans-serif;
  font-size: 14px;
  line-height: 1.6;
  padding-top: 10px;
  padding-bottom: 10px;
  background-color: white;
  padding: 30px;
  color: #516272;
}

body>*:first-child {
  margin-top: 0 !important;
}

body>*:last-child {
  margin-bottom: 0 !important;
}

a {
  color: #4183C4;
}

a.absent {
  color: #cc0000;
}

a.anchor {
  display: block;
  padding-left: 30px;
  margin-left: -30px;
  cursor: pointer;
  position: absolute;
  top: 0;
  left: 0;
  bottom: 0;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 20px 0 10px;
  padding: 0;
  font-weight: bold;
  -webkit-font-smoothing: antialiased;
  cursor: text;
  position: relative;
}

h1:hover a.anchor,
h2:hover a.anchor,
h3:hover a.anchor,
h4:hover a.anchor,
h5:hover a.anchor,
h6:hover a.anchor {
  background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA09pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoMTMuMCAyMDEyMDMwNS5tLjQxNSAyMDEyLzAzLzA1OjIxOjAwOjAwKSAgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OUM2NjlDQjI4ODBGMTFFMTg1ODlEODNERDJBRjUwQTQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OUM2NjlDQjM4ODBGMTFFMTg1ODlEODNERDJBRjUwQTQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5QzY2OUNCMDg4MEYxMUUxODU4OUQ4M0REMkFGNTBBNCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5QzY2OUNCMTg4MEYxMUUxODU4OUQ4M0REMkFGNTBBNCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsQhXeAAAABfSURBVHjaYvz//z8DJYCRUgMYQAbAMBQIAvEqkBQWXI6sHqwHiwG70TTBxGaiWwjCTGgOUgJiF1J8wMRAIUA34B4Q76HUBelAfJYSA0CuMIEaRP8wGIkGMA54bgQIMACAmkXJi0hKJQAAAABJRU5ErkJggg==) no-repeat 10px center;
  text-decoration: none;
}

h1 tt,
h1 code {
  font-size: inherit;
}

h2 tt,
h2 code {
  font-size: inherit;
}

h3 tt,
h3 code {
  font-size: inherit;
}

h4 tt,
h4 code {
  font-size: inherit;
}

h5 tt,
h5 code {
  font-size: inherit;
}

h6 tt,
h6 code {
  font-size: inherit;
}

h1 {
  font-size: 28px;
  color: #2B3F52;
}

h2 {
  font-size: 24px;
  border-bottom: 1px solid #DDE4E9;
  color: #2B3F52;
}

h3 {
  font-size: 18px;
  color: #2B3F52;
}

h4 {
  font-size: 16px;
  color: #2B3F52;
}

h5 {
  font-size: 14px;
  color: #2B3F52;
}

h6 {
  color: #2B3F52;
  font-size: 14px;
}

p,
blockquote,
ul,
ol,
dl,
li,
table,
pre {
  margin: 15px 0;
  color: #516272;
}

hr {
  background: transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAYAAACtBE5DAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBNYWNpbnRvc2giIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OENDRjNBN0E2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OENDRjNBN0I2NTZBMTFFMEI3QjRBODM4NzJDMjlGNDgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4Q0NGM0E3ODY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4Q0NGM0E3OTY1NkExMUUwQjdCNEE4Mzg3MkMyOUY0OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PqqezsUAAAAfSURBVHjaYmRABcYwBiM2QSA4y4hNEKYDQxAEAAIMAHNGAzhkPOlYAAAAAElFTkSuQmCC) repeat-x 0 0;
  border: 0 none;
  color: #cccccc;
  height: 4px;
  padding: 0;

}

body>h2:first-child {
  margin-top: 0;
  padding-top: 0;
}

body>h1:first-child {
  margin-top: 0;
  padding-top: 0;
}

body>h1:first-child+h2 {
  margin-top: 0;
  padding-top: 0;
}

body>h3:first-child,
body>h4:first-child,
body>h5:first-child,
body>h6:first-child {
  margin-top: 0;
  padding-top: 0;
}

a:first-child h1,
a:first-child h2,
a:first-child h3,
a:first-child h4,
a:first-child h5,
a:first-child h6 {
  margin-top: 0;
  padding-top: 0;
}

h1 p,
h2 p,
h3 p,
h4 p,
h5 p,
h6 p {
  margin-top: 0;
}

li p.first {
  display: inline-block;
}

li {
  margin: 0;
}

ul,
ol {
  padding-left: 30px;
}

ul :first-child,
ol :first-child {
  margin-top: 0;
}

dl {
  padding: 0;
}

dl dt {
  font-size: 14px;
  font-weight: bold;
  font-style: italic;
  padding: 0;
  margin: 15px 0 5px;
}

dl dt:first-child {
  padding: 0;
}

dl dt> :first-child {
  margin-top: 0;
}

dl dt> :last-child {
  margin-bottom: 0;
}

dl dd {
  margin: 0 0 15px;
  padding: 0 15px;
}

dl dd> :first-child {
  margin-top: 0;
}

dl dd> :last-child {
  margin-bottom: 0;
}

blockquote {
  border-left: 4px solid #ECF0F3;
  /*padding: 0 15px;*/
  padding: 15px;
  background-color: #F7F9FA;
  color: #2B3F52;
}

blockquote> :first-child {
  margin-top: 0;
}

blockquote> :last-child {
  margin-bottom: 0;
}

table {
  padding: 0;
  border-collapse: collapse;
}

table tr {
  border-top: 1px solid #cccccc;
  background-color: white;
  margin: 0;
  padding: 0;
}

table tr:nth-child(2n) {
  background-color: #f8f8f8;
}

table tr th {
  font-weight: bold;
  border: 1px solid #cccccc;
  margin: 0;
  padding: 6px 13px;
}

table tr td {
  border: 1px solid #cccccc;
  margin: 0;
  padding: 6px 13px;
}

table tr th :first-child,
table tr td :first-child {
  margin-top: 0;
}

table tr th :last-child,
table tr td :last-child {
  margin-bottom: 0;
}

img {
  max-width: 100%;
}

span.frame {
  display: block;
  overflow: hidden;
}

span.frame>span {
  border: 1px solid #dddddd;
  display: block;
  float: left;
  overflow: hidden;
  margin: 13px 0 0;
  padding: 7px;
  width: auto;
}

span.frame span img {
  display: block;
  float: left;
}

span.frame span span {
  clear: both;
  color: #333333;
  display: block;
  padding: 5px 0 0;
}

span.align-center {
  display: block;
  overflow: hidden;
  clear: both;
}

span.align-center>span {
  display: block;
  overflow: hidden;
  margin: 13px auto 0;
  text-align: center;
}

span.align-center span img {
  margin: 0 auto;
  text-align: center;
}

span.align-right {
  display: block;
  overflow: hidden;
  clear: both;
}

span.align-right>span {
  display: block;
  overflow: hidden;
  margin: 13px 0 0;
  text-align: right;
}

span.align-right span img {
  margin: 0;
  text-align: right;
}

span.float-left {
  display: block;
  margin-right: 13px;
  overflow: hidden;
  float: left;
}

span.float-left span {
  margin: 13px 0 0;
}

span.float-right {
  display: block;
  margin-left: 13px;
  overflow: hidden;
  float: right;
}

span.float-right>span {
  display: block;
  overflow: hidden;
  margin: 13px auto 0;
  text-align: right;
}

code,
tt {
  margin: 0 2px;
  padding: 0 5px;
  white-space: nowrap;
  border: 1px solid #eaeaea;
  background-color: #f8f8f8;
  border-radius: 3px;
}

pre code {
  margin: 0;
  padding: 0;
  white-space: pre;
  border: none;
  background: transparent;
}

.highlight pre {
  background-color: #f8f8f8;
  border: 1px solid #cccccc;
  font-size: 13px;
  line-height: 19px;
  overflow: auto;
  padding: 6px 10px;
  border-radius: 3px;
}

pre {
  background-color: #f8f8f8;
  border: 1px solid #cccccc;
  font-size: 13px;
  line-height: 19px;
  overflow: auto;
  padding: 6px 10px;
  border-radius: 3px;
}

pre code,
pre tt {
  background-color: transparent;
  border: none;
}

sup {
  font-size: 0.83em;
  vertical-align: super;
  line-height: 0;
}

code {
  white-space: pre-wrap;
  word-break: break-all;
  display: inline-block;
}

* {
  -webkit-print-color-adjust: exact;
}

@media screen and (min-width: 914px) {
  body {
    width: 960px;
    margin: 0 auto;
  }
}

@media print {

  table,
  pre {
    page-break-inside: avoid;
  }

  pre {
    word-wrap: break-word;
  }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,290评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,107评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,872评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,415评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,453评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,784评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,927评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,691评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,137评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,472评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,622评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,289评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,887评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,316评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,490评论 2 348

推荐阅读更多精彩内容